Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Projects/repo-mapper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Repo-to-Knowledge Mapper
A lightweight Python static analysis tool that generates a structured Markdown map of your project's architecture.

## Purpose
Onboarding to a new codebase is hard. This script scans a directory and extracts a high-level overview of classes and public methods using Python's Abstract Syntax Tree (AST), helping developers understand "what lives where" without manual auditing.

## Installation
1. Clone the repository.
2. Install testing dependencies:

```bash
pip install -r requirements.txt
51 changes: 51 additions & 0 deletions Projects/repo-mapper/mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import ast
import os
import argparse
from typing import Dict, List

class RepoAnalyzer(ast.NodeVisitor):
def __init__(self):
self.stats = {"classes": [], "functions": []}

def visit_ClassDef(self, node: ast.ClassDef):
self.stats["classes"].append(node.name)
self.generic_visit(node)

def visit_FunctionDef(self, node: ast.FunctionDef):
# Ignore private methods/functions
if not node.name.startswith('_'):
self.stats["functions"].append(node.name)
self.generic_visit(node)

def analyze_file(filepath: str) -> Dict[str, List[str]]:
with open(filepath, "r", encoding="utf-8") as f:
try:
tree = ast.parse(f.read())
analyzer = RepoAnalyzer()
analyzer.visit(tree)
return analyzer.stats
except (SyntaxError, UnicodeDecodeError):
return {"classes": [], "functions": []}

def run_mapper(target_dir: str):
print(f"# Project Map: {os.path.abspath(target_dir)}\n")
for root, _, files in os.walk(target_dir):
for file in files:
if file.endswith(".py"):
path = os.path.join(root, file)
rel_path = os.path.relpath(path, target_dir)
data = analyze_file(path)

if data["classes"] or data["functions"]:
print(f"### `{rel_path}`")
if data["classes"]:
print(f"- **Classes**: {', '.join(data['classes'])}")
if data["functions"]:
print(f"- **Public Methods**: {', '.join(data['functions'])}")
print()

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate a Markdown map of a Python repository.")
parser.add_argument("path", nargs="?", default=".", help="Directory to analyze (default: current)")
args = parser.parse_args()
run_mapper(args.path)
1 change: 1 addition & 0 deletions Projects/repo-mapper/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pytest==8.0.0
23 changes: 23 additions & 0 deletions Projects/repo-mapper/tests/test_mapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import pytest
import os
from mapper import analyze_file

def test_analyze_simple_code(tmp_path):
# Create a temporary python file
d = tmp_path / "sub"
d.mkdir()
p = d / "hello.py"
p.write_text("class MyClass:\n def my_method(self):\n pass\n\ndef my_function():\n pass")

results = analyze_file(str(p))

assert "MyClass" in results["classes"]
assert "my_method" in results["functions"]
assert "my_function" in results["functions"]

def test_ignore_private_methods(tmp_path):
p = tmp_path / "private.py"
p.write_text("def _hidden():\n pass")

results = analyze_file(str(p))
assert "_hidden" not in results["functions"]