From cd1c7529760f5b62d8dfd7556e7eac6407cb8a22 Mon Sep 17 00:00:00 2001 From: Matthew Thompson Date: Fri, 12 Apr 2024 10:56:30 -0400 Subject: [PATCH] MVP --- README.md | 52 ++++++++++++++++++++++++++- pyproject.toml | 22 ++++++++++++ src/uncoil/__init__.py | 0 src/uncoil/__main__.py | 82 ++++++++++++++++++++++++++++++++++++++++++ test.txt | 1 + tests/__init__.py | 0 tests/test_main.py | 0 7 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 pyproject.toml create mode 100644 src/uncoil/__init__.py create mode 100644 src/uncoil/__main__.py create mode 100644 test.txt create mode 100644 tests/__init__.py create mode 100644 tests/test_main.py diff --git a/README.md b/README.md index 73894f7..8bf022f 100644 --- a/README.md +++ b/README.md @@ -1 +1,51 @@ -# uncoil \ No newline at end of file +# uncoil + +`uncoil` is a command-line tool designed to explore and print contents of directories with options to selectively skip certain file extensions or directories into a flat format either to standard out or a text file. + +It will produce tree visualization of directories and files and will reveal the contents of files not skipped by the user. + +This can be useful for navigating large projects, quickly summarizing as well as thoroughly revealing the structure and contents of directories. + +It may be useful for providing context of a codebase to a Large Language Model (LLM). + +## Installation + +`uncoil` can be installed using pip directly from the GitHub repository or by cloning the repo. Choose the method that best suits your needs. + +### Direct Installation with pip + +To install the latest version of `uncoil` directly from GitHub, run: + +```bash +pip install git+https://github.com/thompsonmj/uncoil.git +``` + +## Usage +```bash +uncoil -d [-o ] [-x ] +``` + +### Options + +- `-d `: **Required.** The directory you want to process. +- `-o `: Optional. Output file to redirect the output into. If not provided, the output will be printed to the console. +- `-x `: Optional. A comma-separated list of file extensions or subdirectories to skip. + +### Examples + +1. **Print the unfiltered structure of a directory to the console:** +```bash +uncoil -d directory +``` +2. **Print the structure of a directory to the console, skipping certain extensions and subdirectories:** +```bash +uncoil -d directory -x .log,.tmp,.git +``` +3. **Save the unfiltered structure of a directory to a file:** +```bash +uncoil -d directory -o output.txt +``` +4. **Save the structure of a directory to a file, skipping certain extensions and subdirectories:** +```bash +uncoil -d directory -o output.txt -x .log,.tmp,.git +``` \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3dd8cd1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "uncoil" +version = "1.0.0" +description = "A command-line tool to explore and print contents of directories with options to skip certain patterns." +authors = [{ name = "Matthew J. Thompson", email = "thompson.m.j@outlook.com" }] +license = {file = "LICENSE"} +readme = "README.md" +requires-python = ">=3.8" +dependencies = [ + "docopt-ng >= 0.9.0", + "rich >= 13.6.0" +] + +[project.scripts] +uncoil = "uncoil.__main__:main" + +[tool.hatch.version] +enabled = true diff --git a/src/uncoil/__init__.py b/src/uncoil/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/uncoil/__main__.py b/src/uncoil/__main__.py new file mode 100644 index 0000000..27cba76 --- /dev/null +++ b/src/uncoil/__main__.py @@ -0,0 +1,82 @@ +""" +Usage: + uncoil -d [-o ] [-x ] + +Options: + -h --help Show this screen. + -d Directory to process. + -o Output file to redirect output into. + -x Comma-separated list of file extensions or directories to skip. +""" + +import os +import sys +from docopt import docopt +from rich.console import Console +from rich.tree import Tree + +def matches_skip_pattern(file_path, skip_patterns): + lower_file = file_path.lower() + return any(skip.lower() in lower_file for skip in skip_patterns) + +def unfurl_directory(directory, skip_list): + for root, dirs, files in os.walk(directory, topdown=True): + dirs[:] = [d for d in dirs if not matches_skip_pattern(os.path.join(root, d), skip_list)] + for file in files: + file_path = os.path.join(root, file) + if not matches_skip_pattern(file_path, skip_list): + yield file_path + +def create_tree(directory, skip_list): + tree = Tree(f"Directory Structure: {directory}") + for root, dirs, files in os.walk(directory, topdown=True): + dirs[:] = [d for d in dirs if not matches_skip_pattern(os.path.join(root, d), skip_list)] + path = root.split(os.sep) + parent = tree + for part in path[1:]: + parent = parent.add(part) + for file in files: + file_path = os.path.join(root, file) + if not matches_skip_pattern(file_path, skip_list): + parent.add(file) + return tree + +def print_file_contents(file_path): + try: + with open(file_path, 'r', encoding='utf-8') as file: + contents = file.read() + print(f"==> {file_path} <==") + print(contents) + print("\n") # Adds an extra newline for readability between files + except Exception as e: + print(f"Error reading {file_path}: {e}") + +def main(): + args = docopt(__doc__) + directory = args['-d'] + output_file = args.get('-o') + extensions_to_skip = args.get('-x', '') + + if extensions_to_skip: + extensions_to_skip = extensions_to_skip.split(',') + else: + extensions_to_skip = [] + + console = Console(file=open(output_file, 'w') if output_file else sys.stdout) + tree = create_tree(directory, extensions_to_skip) + console.print(tree) + + files = unfurl_directory(directory, extensions_to_skip) + + if output_file: + sys.stdout = open(output_file, 'a') + + try: + for file_path in files: + print_file_contents(file_path) + finally: + if output_file: + sys.stdout.close() + +if __name__ == '__main__': + main() diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..b415b9f --- /dev/null +++ b/test.txt @@ -0,0 +1 @@ +Directory Structure: uncoil diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 0000000..e69de29