From bc186be0f922328294033258cf3094d45fb4dda4 Mon Sep 17 00:00:00 2001 From: Konstantin Fickel Date: Fri, 6 Mar 2026 07:01:15 +0100 Subject: [PATCH 1/2] docs: add CLAUDE.md for Claude Code guidance Provide build commands, architecture overview, and tech stack summary to help future Claude Code instances be productive quickly. Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b2bda21 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,54 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +CV Generator - A Python tool that generates polished PDF CVs from Markdown files with YAML frontmatter. Reads structured CV data and cover letter content, renders them into professionally styled PDFs using Jinja2 templates and WeasyPrint. + +## Commands + +```bash +# Run the application +uv run cv path/to/your_cv.md # Generate CV to default output +uv run cv path/to/your_cv.md -o out.pdf # Specify custom output path + +# Development environment (with Nix) +nix develop # Enter dev shell with all dependencies + +# Code quality (run in nix develop shell or via pre-commit) +basedpyright # Type checking +ruff check # Linting +ruff format # Code formatting + +# Testing +pytest # Run test suite +``` + +## Architecture + +**Data Flow:** +1. **Input:** Markdown file with YAML frontmatter + optional image/signature assets +2. **Parsing:** `main.py` uses Typer CLI, calls `generator.py` which parses with `python-frontmatter` +3. **Validation:** YAML frontmatter validated via Pydantic models in `models.py` +4. **Rendering:** Jinja2 template (`cv.html.j2`) populated with data, styled by `cv.css` +5. **Output:** WeasyPrint converts HTML+CSS to PDF + +**Key Files:** +- `main.py` - CLI entry point (Typer) +- `src/cv_generator/generator.py` - Core PDF generation logic +- `src/cv_generator/models.py` - Pydantic data models (CVData, ContactInfo, Experience, etc.) +- `src/cv_generator/cv.html.j2` - Jinja2 HTML template (two-column layout) +- `src/cv_generator/cv.css` - WeasyPrint-compatible CSS styling +- `example/alberteinstein.md` - Reference example showing complete YAML structure + +**Template Structure:** +- Two-column layout: sidebar (photo, contact, skills) + main content (cover letter, experience) +- Separate `@page` rules for letter vs CV pages +- Uses Fira Sans Condensed fonts and Bootstrap Icons + +## Tech Stack + +- Python 3.13, Typer (CLI), Pydantic (validation), Jinja2 (templates), WeasyPrint (PDF) +- Build: Nix Flakes with uv2nix, uv package manager +- Quality: BasedPyright, Ruff, Commitizen (commit messages) From 03b8c509c3d2a2ebc10ac9d9b2c1e6c12ab51244 Mon Sep 17 00:00:00 2001 From: Konstantin Fickel Date: Fri, 6 Mar 2026 07:02:10 +0100 Subject: [PATCH 2/2] fix: add error handling for missing input files Display human-readable errors when the markdown file, photo, or signature cannot be found instead of failing with a stack trace. Co-Authored-By: Claude Opus 4.5 --- main.py | 11 ++++++++++- src/cv_generator/generator.py | 12 ++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index e03d47c..b1dd1ea 100644 --- a/main.py +++ b/main.py @@ -15,7 +15,16 @@ def generate( ), ) -> None: """Generate a PDF CV from a Markdown file with YAML frontmatter.""" - result = generate_pdf(input_file, output_file) + if not input_file.exists(): + typer.echo(f"Error: Markdown file not found: {input_file}", err=True) + raise typer.Exit(1) + + try: + result = generate_pdf(input_file, output_file) + except FileNotFoundError as e: + typer.echo(f"Error: {e}", err=True) + raise typer.Exit(1) + typer.echo(f"Generated {result}") diff --git a/src/cv_generator/generator.py b/src/cv_generator/generator.py index 4fc33e9..b13ae98 100644 --- a/src/cv_generator/generator.py +++ b/src/cv_generator/generator.py @@ -17,8 +17,16 @@ def generate_pdf(input_file: Path, output_file: Path | None = None) -> Path: letter_body: str = markdown.markdown(post.content) input_dir = Path(input_file).resolve().parent - cv.photo = str(input_dir / cv.photo) - cv.signature = str(input_dir / cv.signature) + photo_path = input_dir / cv.photo + signature_path = input_dir / cv.signature + + if not photo_path.exists(): + raise FileNotFoundError(f"Photo not found: {photo_path}") + if not signature_path.exists(): + raise FileNotFoundError(f"Signature not found: {signature_path}") + + cv.photo = str(photo_path) + cv.signature = str(signature_path) env = Environment(loader=FileSystemLoader(str(SRC_DIR))) template = env.get_template("cv.html.j2")