Model: claude-3-opus-20240229
import os
import click
from pathlib import Path
from fnmatch import fnmatch
def should_ignore(path, gitignore_rules):
for rule in gitignore_rules:
if fnmatch(os.path.basename(path), rule):
return True
if os.path.isdir(path) and fnmatch(os.path.basename(path) + "/", rule):
return True
return False
def read_gitignore(path):
gitignore_path = os.path.join(path, ".gitignore")
if os.path.isfile(gitignore_path):
with open(gitignore_path, "r") as f:
return [
line.strip() for line in f if line.strip() and not line.startswith("#")
]
return []
@click.command()
@click.argument(
"path",
type=click.Path(exists=True, file_okay=False, dir_okay=True, readable=True),
)
@click.option(
"--include-hidden",
is_flag=True,
help="Include files and folders starting with .",
)
@click.option(
"--ignore-gitignore",
is_flag=True,
help="Ignore .gitignore files and include all files",
)
@click.version_option()
def cli(path, include_hidden, ignore_gitignore):
"""
Takes a path to a folder and outputs every file in that folder,
recursively, each one preceded with its filename like this:
path/to/file.py
----
Contents of file.py goes here
---
path/to/file2.py
---
...
"""
gitignore_rules = [] if ignore_gitignore else read_gitignore(path)
for root, dirs, files in os.walk(path):
if not include_hidden:
dirs[:] = [d for d in dirs if not d.startswith(".")]
files = [f for f in files if not f.startswith(".")]
if not ignore_gitignore:
gitignore_rules.extend(read_gitignore(root))
dirs[:] = [
d
for d in dirs
if not should_ignore(os.path.join(root, d), gitignore_rules)
]
files = [
f
for f in files
if not should_ignore(os.path.join(root, f), gitignore_rules)
]
for file in files:
file_path = os.path.join(root, file)
with open(file_path, "r") as f:
file_contents = f.read()
click.echo(file_path)
click.echo("---")
click.echo(file_contents)
click.echo()
click.echo("---")
Modify this file. It should take multiple arguments in a variable called paths. Each of those argumets might be a path to a file or it might be a path to a directory - if any of the arguments do not correspoind to a file or directory it should raise a click error.
It should then do what it does already but for all files or files-recursively-contained-within-folders that are passed to it.
It should still obey the gitignore logic.
Here's the modified code that takes multiple paths as arguments and handles both files and directories:
import os
import click
from pathlib import Path
from fnmatch import fnmatch
def should_ignore(path, gitignore_rules):
for rule in gitignore_rules:
if fnmatch(os.path.basename(path), rule):
return True
if os.path.isdir(path) and fnmatch(os.path.basename(path) + "/", rule):
return True
return False
def read_gitignore(path):
gitignore_path = os.path.join(path, ".gitignore")
if os.path.isfile(gitignore_path):
with open(gitignore_path, "r") as f:
return [
line.strip() for line in f if line.strip() and not line.startswith("#")
]
return []
def process_path(path, include_hidden, ignore_gitignore, gitignore_rules):
if os.path.isfile(path):
with open(path, "r") as f:
file_contents = f.read()
click.echo(path)
click.echo("---")
click.echo(file_contents)
click.echo()
click.echo("---")
elif os.path.isdir(path):
for root, dirs, files in os.walk(path):
if not include_hidden:
dirs[:] = [d for d in dirs if not d.startswith(".")]
files = [f for f in files if not f.startswith(".")]
if not ignore_gitignore:
gitignore_rules.extend(read_gitignore(root))
dirs[:] = [
d
for d in dirs
if not should_ignore(os.path.join(root, d), gitignore_rules)
]
files = [
f
for f in files
if not should_ignore(os.path.join(root, f), gitignore_rules)
]
for file in files:
file_path = os.path.join(root, file)
with open(file_path, "r") as f:
file_contents = f.read()
click.echo(file_path)
click.echo("---")
click.echo(file_contents)
click.echo()
click.echo("---")
@click.command()
@click.argument("paths", nargs=-1, type=click.Path(exists=True))
@click.option(
"--include-hidden",
is_flag=True,
help="Include files and folders starting with .",
)
@click.option(
"--ignore-gitignore",
is_flag=True,
help="Ignore .gitignore files and include all files",
)
@click.version_option()
def cli(paths, include_hidden, ignore_gitignore):
"""
Takes one or more paths to files or directories and outputs every file,
recursively, each one preceded with its filename like this:
path/to/file.py
----
Contents of file.py goes here
---
path/to/file2.py
---
...
"""
gitignore_rules = []
for path in paths:
if not os.path.exists(path):
raise click.BadArgumentUsage(f"Path does not exist: {path}")
if not ignore_gitignore:
gitignore_rules.extend(read_gitignore(os.path.dirname(path)))
process_path(path, include_hidden, ignore_gitignore, gitignore_rules)
if __name__ == "__main__":
cli()
The main changes in this modified version are:
- The
path
argument is now namedpaths
and accepts multiple arguments usingnargs=-1
. - The
process_path
function is introduced to handle both files and directories. It checks if the path is a file or directory and processes accordingly. - In the
cli
function, each path from thepaths
argument is checked for existence, and if it doesn't exist, aclick.BadArgumentUsage
error is raised. - The
process_path
function is called for each path, passing the necessary arguments.
Now, you can pass multiple paths to the script, and it will process each path, whether it's a file or a directory, while still obeying the gitignore logic. If any of the provided paths do not correspond to a file or directory, it will raise a click error.