Repository: showyourwork/showyourwork

Issue:
Force clean removes only files
When cleaning the data directory I would expect everything but the `.gitignore` files from the template to be removed.

In my workflow, I noticed that data or figure _files_ are indeed removed, but not all of the (sub) directories that are created.

To do this, the subcommand needs to be updated with `Path.rmdir()` after the calls to `Path.unlink()`

Discussion:

Patch:
diff --git a/src/showyourwork/cli/commands/clean.py b/src/showyourwork/cli/commands/clean.py
index ee58dfe..1427bfa 100644
--- a/src/showyourwork/cli/commands/clean.py
+++ b/src/showyourwork/cli/commands/clean.py
@@ -1,3 +1,4 @@
+import os
 import shutil
 
 from ... import paths
@@ -31,12 +32,18 @@ def clean(force, deep, snakemake_args=(), cores=1, conda_frontend="conda"):
     if paths.user().temp.exists():
         shutil.rmtree(paths.user().temp)
     if force:
-        for file in paths.user().figures.rglob("*.*"):
-            if file.name != ".gitignore":
-                file.unlink()
-        for file in paths.user().data.rglob("*.*"):
-            if file.name != ".gitignore":
-                file.unlink()
+        for root, dirs, files in os.walk(paths.user().data, topdown=False):
+            for name in files:
+                if name != ".gitignore":
+                    os.remove(os.path.join(root, name))
+            for name in dirs:
+                os.rmdir(os.path.join(root, name))
+        for root, dirs, files in os.walk(paths.user().figures, topdown=False):
+            for name in files:
+                if name != ".gitignore":
+                    os.remove(os.path.join(root, name))
+            for name in dirs:
+                os.rmdir(os.path.join(root, name))
     if deep:
         if (paths.user().repo / ".snakemake").exists():
             shutil.rmtree(paths.user().repo / ".snakemake")


Test patch:
diff --git a/tests/integration/test_clean.py b/tests/integration/test_clean.py
new file mode 100644
index 0000000..919be9f
--- /dev/null
+++ b/tests/integration/test_clean.py
@@ -0,0 +1,68 @@
+from helpers import TemporaryShowyourworkRepository
+from yaml import safe_dump, safe_load
+
+from showyourwork.subproc import get_stdout
+
+new_directory_script = r"""
+
+import paths
+
+newdir = paths.data / "newdir"
+
+newdir.mkdir(exist_ok=True)
+
+with open(newdir / "newfile.txt", "w") as f:
+    f.write("This is a new file")
+"""
+
+
+class TestCleanForce(TemporaryShowyourworkRepository):
+    """Test that when calling clean with the -f option a subdirectory is removed."""
+
+    local_build_only = True
+
+    def customize(self):
+        """Create and edit all the necessary files for the workflow."""
+        # Create the script
+        print(f"[{self.repo}] Creating new stuff")
+        with open(self.cwd / "src" / "scripts" / "new_data.py", "w") as f:
+            print(new_directory_script, file=f)
+
+        # Make the manuscript depend on this new file
+        with open(self.cwd / "showyourwork.yml") as config:
+            original_cfg = safe_load(config)
+        with open(self.cwd / "showyourwork.yml", mode="w") as config:
+            original_cfg["dependencies"] = {
+                "src/tex/ms.tex": ["src/data/newdir/newfile.txt"]
+            }
+            safe_dump(original_cfg, config)
+
+        # Add a Snakemake rule to run the script
+        with open(self.cwd / "Snakefile") as f:
+            contents = f.read()
+        with open(self.cwd / "Snakefile", "w") as f:
+            print(contents, file=f)
+            print("\n", file=f)
+            print(
+                "\n".join(
+                    [
+                        "rule make_new_data:",
+                        "    output:",
+                        "        'src/data/newdir/newfile.txt'",
+                        "    script:",
+                        "        'src/scripts/new_data.py'",
+                    ]
+                ),
+                file=f,
+            )
+
+    def check_build(self):
+        """Check that the new directory and file were created."""
+        assert (self.cwd / "src" / "data" / "newdir").exists()
+        assert (self.cwd / "src" / "data" / "newdir" / "newfile.txt").exists()
+
+        # Run the clean command with the -f option
+        get_stdout("showyourwork clean -f", shell=True, cwd=self.cwd)
+
+        # Check that the new directory was removed
+        assert not (self.cwd / "src" / "data" / "newdir").exists()