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
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

> Just show me the code.

Tired of clicking around complex file hierarchies of GitHub repos? Do you just want to see all of the code on a single page? Enter `rendergit`. Flatten any GitHub repository into a single, static HTML page with syntax highlighting, markdown rendering, and a clean sidebar navigation. Perfect for code review, exploration, and an instant Ctrl+F experience.
Tired of clicking around complex file hierarchies of GitHub repos? Do you just want to see all of the code on a single page? Enter `rendergit`. Flatten any GitHub repository or local directory into a single, static HTML page with syntax highlighting, markdown rendering, and a clean sidebar navigation. Perfect for code review, exploration, and an instant Ctrl+F experience.

## Basic usage

Install and use easily with [uv](https://docs.astral.sh/uv/):

```bash
uv tool install git+https://github.com/karpathy/rendergit
rendergit https://github.com/karpathy/nanogpt
rendergit https://github.com/karpathy/nanogpt # Remote repository
rendergit /path/to/local/repo # Local repository
```

Alternatively, more manual pip install example:
Expand All @@ -19,11 +20,12 @@ Alternatively, more manual pip install example:
git clone https://github.com/karpathy/rendergit
cd rendergit
pip install -e .
rendergit https://github.com/karpathy/nanoGPT
rendergit https://github.com/karpathy/nanoGPT # Remote repository
rendergit /path/to/local/repo # Local repository
```

The code will:
1. Clone the repo to a temporary directory
1. Clone the repo (if URL) or use the local directory (if path)
2. Render its source code into a single static temporary HTML file
3. Automatically open the file in your browser

Expand All @@ -35,6 +37,7 @@ There's a few other smaller options, see the code.

## Features

- **Local & Remote Repository Support** - works with GitHub URLs and local directories
- **Dual view modes** - toggle between Human and LLM views
- **πŸ‘€ Human View**: Pretty interface with syntax highlighting and navigation
- **πŸ€– LLM View**: Raw CXML text format - perfect for copying to Claude/ChatGPT for code analysis
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rendergit"
version = "0.1.0"
description = "Flatten a GitHub repo into a single static HTML page for fast skimming and Ctrl+F"
description = "Flatten a GitHub repo or local directory into a single static HTML page for fast skimming and Ctrl+F"
readme = "README.md"
license = "0BSD"
authors = [
Expand Down
37 changes: 26 additions & 11 deletions rendergit.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,34 +471,48 @@ def derive_temp_output_path(repo_url: str) -> pathlib.Path:


def main() -> int:
ap = argparse.ArgumentParser(description="Flatten a GitHub repo to a single HTML page")
ap.add_argument("repo_url", help="GitHub repo URL (https://github.com/owner/repo[.git])")
ap = argparse.ArgumentParser(description="Flatten a GitHub repo or local directory to a single HTML page")
ap.add_argument("repo_source", help="GitHub repo URL or local directory path")
ap.add_argument("-o", "--out", help="Output HTML file path (default: temporary file derived from repo name)")
ap.add_argument("--max-bytes", type=int, default=MAX_DEFAULT_BYTES, help="Max file size to render (bytes); larger files are listed but skipped")
ap.add_argument("--no-open", action="store_true", help="Don't open the HTML file in browser after generation")
args = ap.parse_args()

# Set default output path if not provided
if args.out is None:
args.out = str(derive_temp_output_path(args.repo_url))
args.out = str(derive_temp_output_path(args.repo_source))

tmpdir = tempfile.mkdtemp(prefix="flatten_repo_")
repo_dir = pathlib.Path(tmpdir, "repo")
source_path = pathlib.Path(args.repo_source)
is_local_repo = source_path.is_dir()
tmpdir = None # Initialize tmpdir to None

try:
print(f"πŸ“ Cloning {args.repo_url} to temporary directory: {repo_dir}", file=sys.stderr)
git_clone(args.repo_url, str(repo_dir))
if is_local_repo:
repo_dir = source_path.resolve()
# For a local repo, use the directory name as the "URL" in the report
repo_url = repo_dir.name
print(f"πŸ“ Using local repository: {repo_dir}", file=sys.stderr)
head = git_head_commit(str(repo_dir))
print(f"βœ“ Local repo identified (HEAD: {head[:8]})", file=sys.stderr)
else:
# If not a local dir, assume it's a URL to be cloned
repo_url = args.repo_source
tmpdir = tempfile.mkdtemp(prefix="flatten_repo_")
repo_dir = pathlib.Path(tmpdir, "repo")
print(f"πŸ“ Cloning {repo_url} to temporary directory: {repo_dir}", file=sys.stderr)
git_clone(repo_url, str(repo_dir))
head = git_head_commit(str(repo_dir))
print(f"βœ“ Clone complete (HEAD: {head[:8]})", file=sys.stderr)

try:

print(f"πŸ“Š Scanning files in {repo_dir}...", file=sys.stderr)
infos = collect_files(repo_dir, args.max_bytes)
rendered_count = sum(1 for i in infos if i.decision.include)
skipped_count = len(infos) - rendered_count
print(f"βœ“ Found {len(infos)} files total ({rendered_count} will be rendered, {skipped_count} skipped)", file=sys.stderr)

print(f"πŸ”¨ Generating HTML...", file=sys.stderr)
html_out = build_html(args.repo_url, repo_dir, head, infos)
html_out = build_html(repo_url, repo_dir, head, infos)

out_path = pathlib.Path(args.out)
print(f"πŸ’Ύ Writing HTML file: {out_path.resolve()}", file=sys.stderr)
Expand All @@ -510,10 +524,11 @@ def main() -> int:
print(f"🌐 Opening {out_path} in browser...", file=sys.stderr)
webbrowser.open(f"file://{out_path.resolve()}")

print(f"πŸ—‘οΈ Cleaning up temporary directory: {tmpdir}", file=sys.stderr)
return 0
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
if tmpdir:
print(f"πŸ—‘οΈ Cleaning up temporary directory: {tmpdir}", file=sys.stderr)
shutil.rmtree(tmpdir, ignore_errors=True)


if __name__ == "__main__":
Expand Down