- Lua 47.3%
- CSS 18.9%
- Shell 17.1%
- HTML 8.9%
- Dockerfile 7.8%
This repository has moved to: https://forgejo.phoenixtrap.com/mjg/resume-remixer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| eg | ||
| scripts | ||
| share/pandoc | ||
| .dockerignore | ||
| .gitignore | ||
| .plugin-versions | ||
| .tool-versions | ||
| CLAUDE.md | ||
| docker-compose.yaml | ||
| Dockerfile | ||
| LICENSE | ||
| README.md | ||
resume-remixer
Warning
This repository has moved to forgejo.phoenixtrap.com/mjg/resume-remixer. This Codeberg repository is archived and no longer maintained. Please update your bookmarks and remotes.
Note
This project is hosted at forgejo.phoenixtrap.com/mjg/resume-remixer. The Codeberg repository is archived. Please open issues and submit contributions there.
A Pandoc-based resume generation system that maintains a single source of truth while producing customized outputs for different job applications and platforms.
The Problem
Job hunting as a software engineer requires:
- Multiple resume versions customized for different roles and companies
- Multiple output formats: print-ready PDFs, web-friendly HTML, plain text for ATSes
- Content that varies by audience: hide irrelevant projects, emphasize different skills, limit bullet points for brevity
- Version control: track what you sent to each company, revert changes when needed
Manually maintaining these variations leads to:
- Copy-paste errors between versions
- Outdated information in some formats but not others
- Lost history of what you told each employer
- Hours spent reformatting instead of applying to jobs
The Solution
resume-remixer uses a single JSON Resume schema-compliant YAML file as your source of truth, then transforms it through a configurable Pandoc filter pipeline to generate exactly the resume you need for each situation.
Key Features
- Single source, multiple outputs: PDF, HTML, Markdown, plain text - all from one YAML file
- Selective visibility: Mark entries with
x-hidden: trueto exclude them without deleting - Date-based filtering: Automatically hide work older than X years or projects started more than Y years ago
- Bullet point limiting: Control highlights per section (e.g., max 2 per job, 1 per project)
- Automatic section titles: "Projects" becomes "Selected Projects" when entries are hidden, "Recent Work Experience" when date-filtered, "Selected Recent Projects" when both
- Version controllable: Branch per company, diff your changes, track what you sent where
- Standards compliant: JSON Resume schema with proper
x-prefixed extensions
Requirements
- Pandoc - Universal document converter
- WeasyPrint - PDF rendering engine
- yq - YAML processor (for filename generation in
save_pdf.sh)
Installation
macOS (Homebrew)
brew install pandoc weasyprint yq
Linux (Debian/Ubuntu)
sudo apt install pandoc weasyprint
# yq installation varies; see https://github.com/mikefarah/yq#install
Other platforms
See Pandoc installation and WeasyPrint installation for your platform.
Docker (all platforms)
No local installation required - just Docker:
# Build the image
docker build -t resume-remixer .
# Or use docker compose
docker compose build
Supports both amd64 and arm64 (Apple Silicon) architectures.
Quick Start
-
Clone this repository:
git clone https://codeberg.org/mjgardner/resume-remixer.git cd resume-remixer -
Edit
eg/mjgardner_resume.yaml(or create your own) following the JSON Resume schema -
Generate your resume:
Using native installation:
# Generate PDF (saves to out/ directory) ./scripts/save_pdf.sh eg/mjgardner_resume.yaml # Generate HTML to stdout ./scripts/stdout_html.sh eg/mjgardner_resume.yaml > resume.html # Generate plain text (for ATS copy-paste) ./scripts/stdout_plain.sh eg/mjgardner_resume.yamlUsing Docker:
# Generate PDF using Docker docker run --rm \ -v "$(pwd)/eg:/app/eg:ro" \ -v "$(pwd)/out:/app/out" \ resume-remixer \ ./scripts/save_pdf.sh eg/mjgardner_resume.yaml # Or with docker compose docker compose run --rm resume-remixer \ ./scripts/save_pdf.sh eg/mjgardner_resume.yaml
Usage
Basic Workflow
All generation scripts follow the pattern: script_name.sh path/to/resume.yaml
Available scripts (scripts/ directory):
save_pdf.sh- Generates PDF and saves toout/with auto-generated filenamestdout_pdf.sh- Outputs PDF to stdout (for piping)stdout_html.sh- Generates standalone HTMLstdout_markdown.sh- Outputs processed Markdownstdout_plain.sh- Generates plain text with wrappingstdout_unwrapped.sh- Plain text without line wrappingstdout_unwrapped_full.sh- Full content plain text without wrapping
Customizing for Different Jobs
Option 1: Branch per company (recommended)
# Create branch for Company X focusing on DevOps
git checkout -b acme-corp-devops
# Hide irrelevant projects
vim eg/mjgardner_resume.yaml # Add x-hidden: true to projects
# Adjust highlights limit for conciseness
vim share/pandoc/metadata/highlights_limit.yaml
./scripts/save_pdf.sh eg/mjgardner_resume.yaml
git commit -am "Customize for Acme Corp DevOps role"
# Later, for Company Y full-stack role
git checkout main
git checkout -b bigco-fullstack
# Different customizations...
Option 2: Multiple YAML files
cp eg/mjgardner_resume.yaml eg/resume_devops.yaml
# Edit resume_devops.yaml with role-specific changes
./scripts/save_pdf.sh eg/resume_devops.yaml
Using Docker for Customization
The Docker setup mounts your local files, so you can customize configs and resume data without rebuilding:
# Edit your resume or metadata configs locally
vim eg/mjgardner_resume.yaml
vim share/pandoc/metadata/highlights_limit.yaml
# Generate with Docker (picks up your changes)
docker compose run --rm resume-remixer \
./scripts/save_pdf.sh eg/mjgardner_resume.yaml
# Or using docker run
docker run --rm \
-v "$(pwd)/eg:/app/eg:ro" \
-v "$(pwd)/share/pandoc/metadata:/app/share/pandoc/metadata:ro" \
-v "$(pwd)/out:/app/out" \
resume-remixer \
./scripts/save_pdf.sh eg/mjgardner_resume.yaml
Volume mounts explained:
/app/eg- Your resume YAML files (read-only)/app/share/pandoc/metadata- Filter configs (read-only)/app/out- Generated files (read-write)
All scripts work the same in Docker as native - just prefix commands with the docker run/compose wrapper.
Configuration
Hiding Individual Entries
Add x-hidden: true to any entry to exclude it from output:
projects:
- name: Relevant Project
startDate: 2024-01-01
highlights:
- This appears in the resume
- name: Outdated Project
startDate: 2015-01-01
x-hidden: true # This won't appear
highlights:
- Hidden but preserved in source
When entries are hidden, section titles automatically get "Selected" prefix (e.g., "Selected Projects").
Date-Based Filtering
Edit share/pandoc/metadata/date_past.yaml:
x-date-past:
work: { endDate: { year: 16 } } # Hide jobs ended >16 years ago
projects: { startDate: { year: 12 } } # Hide projects started >12 years ago
When date filtering is active, section titles get "Recent" prefix (e.g., "Recent Work Experience").
Limiting Bullet Points
Edit share/pandoc/metadata/highlights_limit.yaml:
x-highlights-limit:
work: 2 # Max 2 highlights per job
projects: 1 # Max 1 highlight per project
Keeps the first N highlights, discards the rest. Ensure your most important highlights come first in your YAML.
Removing Metadata Fields
Edit share/pandoc/metadata/meta_remove.yaml:
x-meta-remove:
certificates: credits # Remove 'credits' field from certificates
education: score # Remove 'score' (GPA) from education
publications: summary # Remove summary from publications
projects: # Remove entire projects section (empty value)
Date Formatting
Edit share/pandoc/metadata/date_formats.yaml to customize date display using strftime patterns:
x-date-formats:
work:
startDate: { template: "%b %Y" } # "Jan 2024"
endDate: { template: "%b %Y" }
How It Works
resume-remixer uses a two-pass Pandoc architecture:
-
First pass: YAML → Markdown with Lua filters applied
- Remove unwanted fields (
meta_remove.lua) - Hide marked entries (
entry_hidden.lua) - Filter by date (
date_past.lua) - Limit bullet points (
highlights_limit.lua) - Format dates (
date_formats.lua)
- Remove unwanted fields (
-
Second pass: Markdown → final format (PDF/HTML/etc.)
- Apply templates
- Generate format-specific output
Each filter operates on Pandoc's AST (Abstract Syntax Tree) and can be configured independently. Filter order matters and is specified in share/pandoc/defaults/markdown.yaml.
For implementation details, see CLAUDE.md.
Project Structure
resume-remixer/
├── eg/ # Example resume files
│ └── mjgardner_resume.yaml
├── scripts/ # Generation scripts
│ ├── save_pdf.sh
│ ├── stdout_html.sh
│ └── ...
├── share/pandoc/
│ ├── defaults/ # Pandoc configuration
│ │ ├── common.yaml
│ │ ├── markdown.yaml
│ │ └── html_pdf.yaml
│ ├── filters/ # Lua filters
│ │ ├── entry_hidden.lua
│ │ ├── date_past.lua
│ │ ├── highlights_limit.lua
│ │ └── ...
│ ├── metadata/ # Filter configuration
│ │ ├── date_past.yaml
│ │ ├── highlights_limit.yaml
│ │ └── ...
│ └── templates/ # Output templates
│ ├── resume.html
│ ├── resume.markdown
│ └── resume.plain
└── out/ # Generated files (gitignored)
Tips for Job Hunting
-
Keep your source complete: Don't delete anything from your YAML. Use
x-hidden: trueinstead. -
Branch per application: Helps you remember what you told each company.
-
Commit before customizing: Easy to revert to your "standard" resume.
-
Use multiple formats:
- PDF for email/upload
- HTML for portfolio website
- Plain text for ATS copy-paste
- Markdown for processing into other tools
-
Order matters in YAML: Highlights are limited by taking the first N, so put your strongest bullets first.
-
Test your output: Run
./scripts/save_pdf.shafter changes to ensure filters work as expected.
Contributing
This is primarily a personal tool, but if you find it useful and want to contribute improvements, feel free to open issues or pull requests on Codeberg.
License
See LICENSE file for details.
Author
Mark Gardner - phoenixtrap.com
Built during a 10-month job search when maintaining multiple resume versions manually became untenable.