Skip to content

Commit bc05005

Browse files
Add --line-numbers flag (simonw#38)
* add --line-numbers flag * -n/--line-numbers in README, refs simonw#38 Co-authored-by: Simon Willison <swillison@gmail.com>
1 parent 8c89fe2 commit bc05005

File tree

3 files changed

+76
-7
lines changed

3 files changed

+76
-7
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,22 @@ This will output the contents of every file, with each file preceded by its rela
6464
files-to-prompt path/to/directory -o output.txt
6565
```
6666

67+
- `-n/--line-numbers`: Include line numbers in the output.
68+
69+
```bash
70+
files-to-prompt path/to/directory -n
71+
```
72+
Example output:
73+
```
74+
files_to_prompt/cli.py
75+
---
76+
1 import os
77+
2 from fnmatch import fnmatch
78+
3
79+
4 import click
80+
...
81+
```
82+
6783
### Example
6884

6985
Suppose you have a directory structure like this:

files_to_prompt/cli.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,39 @@ def read_gitignore(path):
2525
return []
2626

2727

28-
def print_path(writer, path, content, xml):
28+
def add_line_numbers(content):
29+
lines = content.splitlines()
30+
31+
padding = len(str(len(lines)))
32+
33+
numbered_lines = [f"{i+1:{padding}} {line}" for i, line in enumerate(lines)]
34+
return "\n".join(numbered_lines)
35+
36+
37+
def print_path(writer, path, content, xml, line_numbers):
2938
if xml:
30-
print_as_xml(writer, path, content)
39+
print_as_xml(writer, path, content, line_numbers)
3140
else:
32-
print_default(writer, path, content)
41+
print_default(writer, path, content, line_numbers)
3342

3443

35-
def print_default(writer, path, content):
44+
def print_default(writer, path, content, line_numbers):
3645
writer(path)
3746
writer("---")
47+
if line_numbers:
48+
content = add_line_numbers(content)
3849
writer(content)
3950
writer("")
4051
writer("---")
4152

4253

43-
def print_as_xml(writer, path, content):
54+
def print_as_xml(writer, path, content, line_numbers):
4455
global global_index
4556
writer(f'<document index="{global_index}">')
4657
writer(f"<source>{path}</source>")
4758
writer("<document_content>")
59+
if line_numbers:
60+
content = add_line_numbers(content)
4861
writer(content)
4962
writer("</document_content>")
5063
writer("</document>")
@@ -60,11 +73,12 @@ def process_path(
6073
ignore_patterns,
6174
writer,
6275
claude_xml,
76+
line_numbers=False,
6377
):
6478
if os.path.isfile(path):
6579
try:
6680
with open(path, "r") as f:
67-
print_path(writer, path, f.read(), claude_xml)
81+
print_path(writer, path, f.read(), claude_xml, line_numbers)
6882
except UnicodeDecodeError:
6983
warning_message = f"Warning: Skipping file {path} due to UnicodeDecodeError"
7084
click.echo(click.style(warning_message, fg="red"), err=True)
@@ -101,7 +115,9 @@ def process_path(
101115
file_path = os.path.join(root, file)
102116
try:
103117
with open(file_path, "r") as f:
104-
print_path(writer, file_path, f.read(), claude_xml)
118+
print_path(
119+
writer, file_path, f.read(), claude_xml, line_numbers
120+
)
105121
except UnicodeDecodeError:
106122
warning_message = (
107123
f"Warning: Skipping file {file_path} due to UnicodeDecodeError"
@@ -143,6 +159,13 @@ def process_path(
143159
is_flag=True,
144160
help="Output in XML-ish format suitable for Claude's long context window.",
145161
)
162+
@click.option(
163+
"line_numbers",
164+
"-n",
165+
"--line-numbers",
166+
is_flag=True,
167+
help="Add line numbers to the output",
168+
)
146169
@click.version_option()
147170
def cli(
148171
paths,
@@ -152,6 +175,7 @@ def cli(
152175
ignore_patterns,
153176
output_file,
154177
claude_xml,
178+
line_numbers,
155179
):
156180
"""
157181
Takes one or more paths to files or directories and outputs every file,
@@ -204,6 +228,7 @@ def cli(
204228
ignore_patterns,
205229
writer,
206230
claude_xml,
231+
line_numbers,
207232
)
208233
if claude_xml:
209234
writer("</documents>")

tests/test_files_to_prompt.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,31 @@ def test_output_option(tmpdir, arg):
279279
---
280280
"""
281281
assert expected.strip() == actual.strip()
282+
283+
284+
def test_line_numbers(tmpdir):
285+
runner = CliRunner()
286+
with tmpdir.as_cwd():
287+
os.makedirs("test_dir")
288+
test_content = "First line\nSecond line\nThird line\nFourth line\n"
289+
with open("test_dir/multiline.txt", "w") as f:
290+
f.write(test_content)
291+
292+
result = runner.invoke(cli, ["test_dir"])
293+
assert result.exit_code == 0
294+
assert "1 First line" not in result.output
295+
assert test_content in result.output
296+
297+
result = runner.invoke(cli, ["test_dir", "-n"])
298+
assert result.exit_code == 0
299+
assert "1 First line" in result.output
300+
assert "2 Second line" in result.output
301+
assert "3 Third line" in result.output
302+
assert "4 Fourth line" in result.output
303+
304+
result = runner.invoke(cli, ["test_dir", "--line-numbers"])
305+
assert result.exit_code == 0
306+
assert "1 First line" in result.output
307+
assert "2 Second line" in result.output
308+
assert "3 Third line" in result.output
309+
assert "4 Fourth line" in result.output

0 commit comments

Comments
 (0)