Skip to content

Commit

Permalink
--code output to produce triple backtick markdown, closes simonw#42
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Feb 19, 2025
1 parent 630bbf9 commit cfc9fa5
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 4 deletions.
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ This will output the contents of every file, with each file preceded by its rela
files-to-prompt path/to/directory --cxml
```

- `--code`: Output as fenced code blocks.

```bash
files-to-prompt path/to/directory --code
```

- `-o/--output <file>`: Write the output to a file instead of printing it to the console.

```bash
Expand Down Expand Up @@ -208,6 +214,40 @@ Contents of file2.txt
</documents>
```

## --code fenced code block output

The `--code` option will output the files as fenced code blocks, which can be useful for pasting into Markdown documents.

```bash
files-to-prompt path/to/directory --code
```
The language tag will be guessed based on the filename.

If the code itself contains triple backticks the wrapper around it will use one additional backtick.

Example output:
`````
myfile.py
```python
def my_function():
return "Hello, world!"
```
other.js
```javascript
function myFunction() {
return "Hello, world!";
}
```
file_with_triple_backticks.md
````markdown
This file has its own
```
fenced code blocks
```
Inside it.
````
`````

## Development

To contribute to this tool, first checkout the code. Then create a new virtual environment:
Expand Down
55 changes: 51 additions & 4 deletions files_to_prompt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@

global_index = 1

EXT_TO_LANG = {
"py": "python",
"c": "c",
"cpp": "cpp",
"java": "java",
"js": "javascript",
"ts": "typescript",
"html": "html",
"css": "css",
"xml": "xml",
"json": "json",
"yaml": "yaml",
"yml": "yaml",
"sh": "bash",
"rb": "ruby",
}


def should_ignore(path, gitignore_rules):
for rule in gitignore_rules:
Expand Down Expand Up @@ -35,9 +52,11 @@ def add_line_numbers(content):
return "\n".join(numbered_lines)


def print_path(writer, path, content, xml, line_numbers):
if xml:
def print_path(writer, path, content, cxml, code, line_numbers):
if cxml:
print_as_xml(writer, path, content, line_numbers)
elif code:
print_as_code(writer, path, content, line_numbers)
else:
print_default(writer, path, content, line_numbers)

Expand Down Expand Up @@ -65,6 +84,20 @@ def print_as_xml(writer, path, content, line_numbers):
global_index += 1


def print_as_code(writer, path, content, line_numbers):
lang = EXT_TO_LANG.get(path.split(".")[-1], "")
# Figure out how many backticks to use
backticks = "```"
while backticks in content:
backticks += "`"
writer(path)
writer(f"{backticks}{lang}")
if line_numbers:
content = add_line_numbers(content)
writer(content)
writer(f"{backticks}")


def process_path(
path,
extensions,
Expand All @@ -75,12 +108,13 @@ def process_path(
ignore_patterns,
writer,
claude_xml,
code,
line_numbers=False,
):
if os.path.isfile(path):
try:
with open(path, "r") as f:
print_path(writer, path, f.read(), claude_xml, line_numbers)
print_path(writer, path, f.read(), claude_xml, code, line_numbers)
except UnicodeDecodeError:
warning_message = f"Warning: Skipping file {path} due to UnicodeDecodeError"
click.echo(click.style(warning_message, fg="red"), err=True)
Expand Down Expand Up @@ -124,7 +158,7 @@ def process_path(
try:
with open(file_path, "r") as f:
print_path(
writer, file_path, f.read(), claude_xml, line_numbers
writer, file_path, f.read(), claude_xml, code, line_numbers
)
except UnicodeDecodeError:
warning_message = (
Expand Down Expand Up @@ -185,6 +219,9 @@ def read_paths_from_stdin(use_null_separator):
is_flag=True,
help="Output in XML-ish format suitable for Claude's long context window.",
)
@click.option(
"--code", is_flag=True, help="Output in triple backtick fenced code blocks"
)
@click.option(
"line_numbers",
"-n",
Expand All @@ -208,6 +245,7 @@ def cli(
ignore_patterns,
output_file,
claude_xml,
code,
line_numbers,
null,
):
Expand Down Expand Up @@ -236,6 +274,14 @@ def cli(
</document>
...
</documents>
If the `--code` flag is provided, the output will be structured as follows:
\b
path/to/file1.py
```python
Contents of file1.py
```
"""
# Reset global_index for pytest
global global_index
Expand Down Expand Up @@ -270,6 +316,7 @@ def cli(
ignore_patterns,
writer,
claude_xml,
code,
line_numbers,
)
if claude_xml:
Expand Down
36 changes: 36 additions & 0 deletions tests/test_files_to_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,39 @@ def test_paths_from_arguments_and_stdin(tmpdir):
assert "Contents of file1" in result.output
assert "test_dir2/file2.txt" in result.output
assert "Contents of file2" in result.output


def test_code(tmpdir):
runner = CliRunner()
with tmpdir.as_cwd():
os.makedirs("test_dir")
with open("test_dir/python.py", "w") as f:
f.write("This is python")
with open("test_dir/python_with_quad_backticks.py", "w") as f:
f.write("This is python with ```` in it already")
with open("test_dir/code.js", "w") as f:
f.write("This is javascript")
with open("test_dir/code.unknown", "w") as f:
f.write("This is an unknown file type")
result = runner.invoke(cli, ["test_dir", "--code"])
assert result.exit_code == 0
actual = result.output
expected = (
"test_dir/code.js\n"
"```javascript\n"
"This is javascript\n"
"```\n"
"test_dir/code.unknown\n"
"```\n"
"This is an unknown file type\n"
"```\n"
"test_dir/python.py\n"
"```python\n"
"This is python\n"
"```\n"
"test_dir/python_with_quad_backticks.py\n"
"`````python\n"
"This is python with ```` in it already\n"
"`````\n"
)
assert expected.strip() == actual.strip()

0 comments on commit cfc9fa5

Please sign in to comment.