Repository: simonw/files-to-prompt

Issue:
not ignoring my venv folder
i use this command `files-to-prompt . --ignore "venv"` but it still trying to get files inside my venv/
i tried again with ` files-to-prompt . --ignore "./venv/*` but getting the same result

Discussion:
@minthemiddle: I'm having the same issue with `node_module` folders.


Not working:
`files-to-prompt . --ignore-gitignore`
`files-to-prompt . --ignore "node_modules"`

The tool does not seem to support ignoring folders yet, it seems.@dh1011: hi Martin, i made a PR that addressed this issue [here](https://github.com/simonw/files-to-prompt/pull/8). it worked for me@jluna: @simonw Also having challenges with the inability to ignore directories. Can we get the fix merged in?@jefftriplett: This doesn't work for me either. I was testing on a Django project and trying to figure out how to ignore migrations and some other files. @Barabazs: FYI, you can just rely on the content of .gitignore to ignore node_modules or venv. Put it seems like files-to-prompt can't properly handle path-like entries: https://github.com/simonw/files-to-prompt/issues/3#issuecomment-2340695309


Solution patch:
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 7461881..b76a8aa 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -12,7 +12,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
+        python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
     steps:
     - uses: actions/checkout@v4
     - name: Set up Python ${{ matrix.python-version }}
@@ -38,7 +38,7 @@ jobs:
     - name: Set up Python
       uses: actions/setup-python@v5
       with:
-        python-version: "3.12"
+        python-version: "3.13"
         cache: pip
         cache-dependency-path: pyproject.toml
     - name: Install dependencies
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 31d0b57..fc44250 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -10,7 +10,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
+        python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
     steps:
     - uses: actions/checkout@v4
     - name: Set up Python ${{ matrix.python-version }}
diff --git a/README.md b/README.md
index 332c663..10ae2de 100644
--- a/README.md
+++ b/README.md
@@ -41,13 +41,19 @@ This will output the contents of every file, with each file preceded by its rela
   files-to-prompt path/to/directory --include-hidden
   ```
 
+- `--ignore-files-only`: Include directory paths which would otherwise be ignored by an `--ignore` pattern.
+
+  ```bash
+  files-to-prompt path/to/directory --ignore-files-only --ignore "*dir*"
+  ```
+
 - `--ignore-gitignore`: Ignore `.gitignore` files and include all files.
 
   ```bash
   files-to-prompt path/to/directory --ignore-gitignore
   ```
 
-- `--ignore <pattern>`: Specify one or more patterns to ignore. Can be used multiple times.
+- `--ignore <pattern>`: Specify one or more patterns to ignore. Can be used multiple times. Patterns may match file names and directory names, unless you also specify `--ignore-files-only`.
   ```bash
   files-to-prompt path/to/directory --ignore "*.log" --ignore "temp*"
   ```
@@ -138,6 +144,19 @@ Contents of file3.txt
 ---
 ```
 
+If you run `files-to-prompt my_directory --ignore "sub*"`, the output will exclude all files in `subdirectory/` (unless you also specify `--ignore-files-only`):
+
+```
+my_directory/file1.txt
+---
+Contents of file1.txt
+---
+my_directory/file2.txt
+---
+Contents of file2.txt
+---
+```
+
 ### Claude XML Output
 
 Anthropic has provided [specific guidelines](https://docs.anthropic.com/claude/docs/long-context-window-tips) for optimally structuring prompts to take advantage of Claude's extended context window.
diff --git a/files_to_prompt/cli.py b/files_to_prompt/cli.py
index 3ce3f19..ad4a0e7 100644
--- a/files_to_prompt/cli.py
+++ b/files_to_prompt/cli.py
@@ -68,6 +68,7 @@ def process_path(
     path,
     extensions,
     include_hidden,
+    ignore_files_only,
     ignore_gitignore,
     gitignore_rules,
     ignore_patterns,
@@ -102,6 +103,12 @@ def process_path(
                 ]
 
             if ignore_patterns:
+                if not ignore_files_only:
+                    dirs[:] = [
+                        d
+                        for d in dirs
+                        if not any(fnmatch(d, pattern) for pattern in ignore_patterns)
+                    ]
                 files = [
                     f
                     for f in files
@@ -133,6 +140,11 @@ def process_path(
     is_flag=True,
     help="Include files and folders starting with .",
 )
+@click.option(
+    "--ignore-files-only",
+    is_flag=True,
+    help="--ignore option only ignores files",
+)
 @click.option(
     "--ignore-gitignore",
     is_flag=True,
@@ -171,6 +183,7 @@ def cli(
     paths,
     extensions,
     include_hidden,
+    ignore_files_only,
     ignore_gitignore,
     ignore_patterns,
     output_file,
@@ -223,6 +236,7 @@ def cli(
             path,
             extensions,
             include_hidden,
+            ignore_files_only,
             ignore_gitignore,
             gitignore_rules,
             ignore_patterns,


Test patch:
diff --git a/tests/test_files_to_prompt.py b/tests/test_files_to_prompt.py
index 24d7234..07dd53e 100644
--- a/tests/test_files_to_prompt.py
+++ b/tests/test_files_to_prompt.py
@@ -88,7 +88,7 @@ def test_multiple_paths(tmpdir):
 def test_ignore_patterns(tmpdir):
     runner = CliRunner()
     with tmpdir.as_cwd():
-        os.makedirs("test_dir")
+        os.makedirs("test_dir", exist_ok=True)
         with open("test_dir/file_to_ignore.txt", "w") as f:
             f.write("This file should be ignored due to ignore patterns")
         with open("test_dir/file_to_include.txt", "w") as f:
@@ -100,12 +100,27 @@ def test_ignore_patterns(tmpdir):
         assert "This file should be ignored due to ignore patterns" not in result.output
         assert "test_dir/file_to_include.txt" not in result.output
 
-        result = runner.invoke(cli, ["test_dir", "--ignore", "file_to_ignore.*"])
+        os.makedirs("test_dir/test_subdir", exist_ok=True)
+        with open("test_dir/test_subdir/any_file.txt", "w") as f:
+            f.write("This entire subdirectory should be ignored due to ignore patterns")
+        result = runner.invoke(cli, ["test_dir", "--ignore", "*subdir*"])
         assert result.exit_code == 0
-        assert "test_dir/file_to_ignore.txt" not in result.output
-        assert "This file should be ignored due to ignore patterns" not in result.output
+        assert "test_dir/test_subdir/any_file.txt" not in result.output
+        assert (
+            "This entire subdirectory should be ignored due to ignore patterns"
+            not in result.output
+        )
         assert "test_dir/file_to_include.txt" in result.output
         assert "This file should be included" in result.output
+        assert "This file should be included" in result.output
+
+        result = runner.invoke(
+            cli, ["test_dir", "--ignore", "*subdir*", "--ignore-files-only"]
+        )
+        assert result.exit_code == 0
+        assert "test_dir/test_subdir/any_file.txt" in result.output
+
+        result = runner.invoke(cli, ["test_dir", "--ignore", ""])
 
 
 def test_specific_extensions(tmpdir):