From 2c0db240320834a181da68108d196503e394f9a7 Mon Sep 17 00:00:00 2001 From: Tony Smoragiewicz <83112082+Tsmorz@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:25:03 +0100 Subject: [PATCH 1/5] Create repo_tree.py --- repo_tree.py | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 repo_tree.py diff --git a/repo_tree.py b/repo_tree.py new file mode 100644 index 0000000..3f35260 --- /dev/null +++ b/repo_tree.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""Generate a Markdown tree of the project directory. + +Usage: + python repo_tree.py + python repo_tree.py --update-readme +""" + +from __future__ import annotations + +import argparse +import fnmatch +from pathlib import Path + +from loguru import logger + +START_MARKER = "" +END_MARKER = "" + +# Manual ignores are still allowed +MANUAL_IGNORE = { + ".git", + ".venv", + "__pycache__", + ".mypy_cache", + ".pytest_cache", + ".ruff_cache", + ".DS_Store", + "data", + "simulink", + ".github", +} + + +def load_gitignore_patterns(path: Path) -> list[str]: + """Load patterns from .gitignore.""" + gitignore = path / ".gitignore" + if not gitignore.exists(): + return [] + + patterns: list[str] = [] + for line in gitignore.read_text().splitlines(): + line_str = line.strip() + if not line_str or line_str.startswith("#"): + continue + patterns.append(line_str) + return patterns + + +def is_ignored(path: Path, patterns: list[str]) -> bool: + """Return True if path matches any ignore pattern. + + Supports: + - literal names + - wildcard patterns + - directory patterns (e.g. logs/) + """ + name = path.name + + # Manual ignores first + if name in MANUAL_IGNORE: + return True + + for pattern in patterns: + # Directory ignore + if pattern.endswith("/") and path.is_dir(): + if fnmatch.fnmatch(name + "/", pattern): + return True + + # File/directory wildcard + if fnmatch.fnmatch(name, pattern): + return True + + return False + + +def build_tree(path: Path, patterns: list[str], prefix: str = "") -> list[str]: + """Recursively build a tree representation.""" + entries = sorted( + [p for p in path.iterdir() if not is_ignored(p, patterns)], + key=lambda x: (x.is_file(), x.name), + ) + + lines: list[str] = [] + + for i, entry in enumerate(entries): + connector = "└── " if i == len(entries) - 1 else "├── " + + lines.append(f"{prefix}{connector}{entry.name}") + + if entry.is_dir(): + extension = " " if i == len(entries) - 1 else "│ " + lines.extend(build_tree(entry, patterns, prefix + extension)) + + return lines + + +def generate_markdown_tree() -> str: + """Return the full markdown tree as a formatted code block.""" + root = Path(".").resolve() + + gitignore_patterns = load_gitignore_patterns(root) + tree_lines = build_tree(root, gitignore_patterns) + tree_text = "\n".join(tree_lines) + + return f"```\n{tree_text}\n```" + + +def update_readme_block(readme_path: Path) -> None: + """Replace the section between markers with the generated tree.""" + readme = readme_path.read_text().splitlines() + + if START_MARKER not in readme or END_MARKER not in readme: + raise RuntimeError( + f"README.md must contain '{START_MARKER}' and '{END_MARKER}' markers." + ) + + start = readme.index(START_MARKER) + 1 + end = readme.index(END_MARKER) + + tree = generate_markdown_tree().splitlines() + + new_readme = readme[:start] + tree + readme[end:] + readme_path.write_text("\n".join(new_readme)) + + logger.success( + "✅ README updated with latest repo tree (gitignored files excluded)." + ) + + +def main() -> None: + """Create a Markdown tree of the project directory.""" + parser = argparse.ArgumentParser() + parser.add_argument("--update-readme", action="store_true") + args = parser.parse_args() + + if args.update_readme: + update_readme_block(Path("README.md")) + else: + logger.info(generate_markdown_tree()) + + +if __name__ == "__main__": + main() From 42d828213f4345103e38cb8193d06c4aeeff06e6 Mon Sep 17 00:00:00 2001 From: Tony Smoragiewicz <83112082+Tsmorz@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:26:31 +0100 Subject: [PATCH 2/5] Update Makefile with tree command --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 83b5c82..7db7421 100644 --- a/Makefile +++ b/Makefile @@ -46,3 +46,6 @@ docker: app: uv run python -m change_me + +tree: + uv run python repo_tree.py --update-readme From 667492bea16082e49becca50481f85a40cc1f082 Mon Sep 17 00:00:00 2001 From: Tony Smoragiewicz <83112082+Tsmorz@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:27:12 +0100 Subject: [PATCH 3/5] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index aadca7f..6d654db 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,7 @@ if __name__ == "__main__": ```bash uv run python -m change_me ``` + +## Structure + + From 576954c9b29698193008a803cc02261000670fa9 Mon Sep 17 00:00:00 2001 From: Tony Smoragiewicz <83112082+Tsmorz@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:27:59 +0100 Subject: [PATCH 4/5] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6d654db..c282131 100644 --- a/README.md +++ b/README.md @@ -56,5 +56,6 @@ uv run python -m change_me ``` ## Structure +The following tree shows the important permament files. From d98de9b2fba72f920dcc655b84554b75618d1341 Mon Sep 17 00:00:00 2001 From: tsmorz Date: Thu, 27 Nov 2025 00:45:50 +0100 Subject: [PATCH 5/5] update readme with tree --- README.md | 27 ++++++++++++++++++++++++++- src/change_me/config/__init__.py | 1 - 2 files changed, 26 insertions(+), 2 deletions(-) delete mode 100644 src/change_me/config/__init__.py diff --git a/README.md b/README.md index c282131..3a8c6db 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,31 @@ uv run python -m change_me ``` ## Structure -The following tree shows the important permament files. +The following tree shows the important permanent files. +``` +├── src +│ └── change_me +│ ├── __init__.py +│ ├── __main__.py +│ ├── definitions.py +│ └── utils.py +├── tests +│ ├── __init__.py +│ ├── conftest.py +│ ├── main_test.py +│ └── utils_test.py +├── .dockerignore +├── .gitignore +├── .pre-commit-config.yaml +├── .python-version +├── CONTRIBUTING.md +├── Dockerfile +├── LICENSE +├── Makefile +├── README.md +├── pyproject.toml +├── repo_tree.py +└── uv.lock +``` diff --git a/src/change_me/config/__init__.py b/src/change_me/config/__init__.py deleted file mode 100644 index a22f654..0000000 --- a/src/change_me/config/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Sample doc string."""