mirror of
https://github.com/simonw/files-to-prompt.git
synced 2025-10-23 00:02:47 +03:00
Add support for reading paths from stdin (#44)
* Add support for reading paths from stdin Fixes #43 * Refactor tests a bit --------- Co-authored-by: Simon Willison <swillison@gmail.com>
This commit is contained in:
28
README.md
28
README.md
@@ -86,6 +86,12 @@ This will output the contents of every file, with each file preceded by its rela
|
||||
...
|
||||
```
|
||||
|
||||
- `-0/--null`: Use NUL character as separator when reading paths from stdin. Useful when filenames may contain spaces.
|
||||
|
||||
```bash
|
||||
find . -name "*.py" -print0 | files-to-prompt --null
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
Suppose you have a directory structure like this:
|
||||
@@ -157,6 +163,28 @@ Contents of file2.txt
|
||||
---
|
||||
```
|
||||
|
||||
### Reading from stdin
|
||||
|
||||
The tool can also read paths from standard input. This can be used to pipe in the output of another command:
|
||||
|
||||
```bash
|
||||
# Find files modified in the last day
|
||||
find . -mtime -1 | files-to-prompt
|
||||
```
|
||||
|
||||
When using the `--null` (or `-0`) option, paths are expected to be NUL-separated (useful when dealing with filenames containing spaces):
|
||||
|
||||
```bash
|
||||
find . -name "*.txt" -print0 | files-to-prompt --null
|
||||
```
|
||||
|
||||
You can mix and match paths from command line arguments and stdin:
|
||||
|
||||
```bash
|
||||
# Include files modified in the last day, and also include README.md
|
||||
find . -mtime -1 | files-to-prompt README.md
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import sys
|
||||
from fnmatch import fnmatch
|
||||
|
||||
import click
|
||||
@@ -30,7 +31,7 @@ def add_line_numbers(content):
|
||||
|
||||
padding = len(str(len(lines)))
|
||||
|
||||
numbered_lines = [f"{i+1:{padding}} {line}" for i, line in enumerate(lines)]
|
||||
numbered_lines = [f"{i + 1:{padding}} {line}" for i, line in enumerate(lines)]
|
||||
return "\n".join(numbered_lines)
|
||||
|
||||
|
||||
@@ -132,6 +133,19 @@ def process_path(
|
||||
click.echo(click.style(warning_message, fg="red"), err=True)
|
||||
|
||||
|
||||
def read_paths_from_stdin(use_null_separator):
|
||||
if sys.stdin.isatty():
|
||||
# No ready input from stdin, don't block for input
|
||||
return []
|
||||
|
||||
stdin_content = sys.stdin.read()
|
||||
if use_null_separator:
|
||||
paths = stdin_content.split("\0")
|
||||
else:
|
||||
paths = stdin_content.split() # split on whitespace
|
||||
return [p for p in paths if p]
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("paths", nargs=-1, type=click.Path(exists=True))
|
||||
@click.option("extensions", "-e", "--extension", multiple=True)
|
||||
@@ -178,6 +192,12 @@ def process_path(
|
||||
is_flag=True,
|
||||
help="Add line numbers to the output",
|
||||
)
|
||||
@click.option(
|
||||
"--null",
|
||||
"-0",
|
||||
is_flag=True,
|
||||
help="Use NUL character as separator when reading from stdin",
|
||||
)
|
||||
@click.version_option()
|
||||
def cli(
|
||||
paths,
|
||||
@@ -189,6 +209,7 @@ def cli(
|
||||
output_file,
|
||||
claude_xml,
|
||||
line_numbers,
|
||||
null,
|
||||
):
|
||||
"""
|
||||
Takes one or more paths to files or directories and outputs every file,
|
||||
@@ -219,6 +240,13 @@ def cli(
|
||||
# Reset global_index for pytest
|
||||
global global_index
|
||||
global_index = 1
|
||||
|
||||
# Read paths from stdin if available
|
||||
stdin_paths = read_paths_from_stdin(use_null_separator=null)
|
||||
|
||||
# Combine paths from arguments and stdin
|
||||
paths = [*paths, *stdin_paths]
|
||||
|
||||
gitignore_rules = []
|
||||
writer = click.echo
|
||||
fp = None
|
||||
|
||||
@@ -322,3 +322,56 @@ def test_line_numbers(tmpdir):
|
||||
assert "2 Second line" in result.output
|
||||
assert "3 Third line" in result.output
|
||||
assert "4 Fourth line" in result.output
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,extra_args",
|
||||
(
|
||||
("test_dir1/file1.txt\ntest_dir2/file2.txt", []),
|
||||
("test_dir1/file1.txt\ntest_dir2/file2.txt", []),
|
||||
("test_dir1/file1.txt\0test_dir2/file2.txt", ["--null"]),
|
||||
("test_dir1/file1.txt\0test_dir2/file2.txt", ["-0"]),
|
||||
),
|
||||
)
|
||||
def test_reading_paths_from_stdin(tmpdir, input, extra_args):
|
||||
runner = CliRunner()
|
||||
with tmpdir.as_cwd():
|
||||
# Create test files
|
||||
os.makedirs("test_dir1")
|
||||
os.makedirs("test_dir2")
|
||||
with open("test_dir1/file1.txt", "w") as f:
|
||||
f.write("Contents of file1")
|
||||
with open("test_dir2/file2.txt", "w") as f:
|
||||
f.write("Contents of file2")
|
||||
|
||||
# Test space-separated paths from stdin
|
||||
result = runner.invoke(cli, args=extra_args, input=input)
|
||||
assert result.exit_code == 0
|
||||
assert "test_dir1/file1.txt" in result.output
|
||||
assert "Contents of file1" in result.output
|
||||
assert "test_dir2/file2.txt" in result.output
|
||||
assert "Contents of file2" in result.output
|
||||
|
||||
|
||||
def test_paths_from_arguments_and_stdin(tmpdir):
|
||||
runner = CliRunner()
|
||||
with tmpdir.as_cwd():
|
||||
# Create test files
|
||||
os.makedirs("test_dir1")
|
||||
os.makedirs("test_dir2")
|
||||
with open("test_dir1/file1.txt", "w") as f:
|
||||
f.write("Contents of file1")
|
||||
with open("test_dir2/file2.txt", "w") as f:
|
||||
f.write("Contents of file2")
|
||||
|
||||
# Test paths from arguments and stdin
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
args=["test_dir1"],
|
||||
input="test_dir2/file2.txt",
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert "test_dir1/file1.txt" in result.output
|
||||
assert "Contents of file1" in result.output
|
||||
assert "test_dir2/file2.txt" in result.output
|
||||
assert "Contents of file2" in result.output
|
||||
|
||||
Reference in New Issue
Block a user