Published by FR Studios

Resume Pipeline

How I Automated My Resumes with Rust and Typst.

Active Project

The Job Hunt Workflow

Automating the most soul-crushing part of the job search: resume‑pipeline.

The Problem
Manually adjusting Word or LaTeX files 20 times a day is soul-crushing. General "master" resumes lead to quick rejections in a competitive market.
The Solution
A Rust-powered pipeline that separates data from presentation. A 5-second CLI wizard generates meticulously typeset, tailored resumes.

How I Automated My Resumes with Rust and Typst

As a software engineer facing an incredibly competitive job market, I quickly realized that the spray-and-pray method of applying to jobs doesn't work. Recruiters look for specific technical stacks and experiences tailored to the very exact role they are hiring for. This means maintaining a single "master" Resume is a recipe for quick rejections. You need a dedicated tailored resume for every application.

Manually adjusting a Word Document or LaTeX file 20 times a day is soul-crushing. Thus, my Terminal UI Resume Pipeline was born.

Instead of dealing with visual editors, I built a pipeline that separates Data from Presentation. Here is a look at the internal architecture of how it fundamentally works.

1. The Data Layer (YAML)

My entire professional history—every job title I've held, every varied professional summary, every project, and every isolated bullet point—is stored cleanly in multiple YAML files (experience.yaml, education.yaml, profile.yaml, etc.).

Using Rust and the serde crate, the application parses this vast repository of data into strongly-typed internal Rust structs on application startup. This is the single source of truth.

2. The Interactive Selection Engine (Ratatui)

Because not every application needs to showcase the same skills, I built an interactive Terminal User Interface (TUI) using Ratatui. The application guides me through a sequence of screens where I simply hit Space to toggle exactly what data I want to include for the current job application.

I added granular controls allowing me to:

  • Swap between different Job Titles and Professional Summaries to match the job description.
  • Toggle entire roles or drill deep into an experience block to toggle individual bullet points on or off.
  • Omit contact information (like my phone number or email) by pressing e or p.

Once I traverse the wizard, the application creates a pruned instance of my data containing strictly what is visible.

3. The Pure Typst Compilation Boundary

Creating PDFs dynamically is historically painful (LaTeX is powerful but slow and complex to automate safely from Rust without full system dependencies). I turned to Typst—a modern, blazing-fast, Rust-based typesetting system.

However, Typst’s compiler is entirely pure. It does not blindly reach into the filesystem to load fonts or check the system time. To integrate it securely into the pipeline, I implemented the World trait for my application context (ResumeWorld). This sandboxed environment acts as a tightly controlled bridge into the Typst compiler:

  • Fonts: It permanently embeds the LiberationSans font bytes into memory so the resulting binary is completely self-contained.
  • Time: It injects a frozen timestamp so #datetime.today() is completely deterministic.
  • Source Files: It intercepts any #import / #include attempts made by the template and explicitly returns NotFound unless it's requesting our exact layout file.

4. Bridging Rust and Typst Parameters

Instead of injecting my dynamically pruned data by generating temporary intermediate JSON files on the hard drive, I bypass the filesystem altogether.

Using derive_typst_intoval, I recursively serialize my filtered Rust structs into Typst Dict objects. I bind this dictionary to the Typst compiler's internal Library setup.

Inside my Typst layout template, traversing this data is as simple as:


#import sys: inputs
#let resume_data = inputs

#for job in resume_data.experience [
  #work_item(job.role, job.company, job.date)
  #for point in job.bullets [ - #point ]
]
		

The template leverages advanced functional array manipulation to handle omitted fields cleanly dynamically (preventing ugly hanging separators like Phone | | LinkedIn).



Core Capabilities

FeatureDescription
YAML‑basedSingle source of truth for all professional history.
Interactive TUIRatatui-powered wizard for granular data selection.
Live TogglingToggle bullet points, profile summaries, and contact info.
Sandboxed TypstDeterministic, fast compilation with embedded fonts.
Zero DependenciesEverything compiled into a single, portable Rust binary.
Instant PDFMeticulously typeset output in milliseconds.
Project Structure
data/ YAML source files (experience, profile, etc.)
src/ TUI logic (Ratatui) and Typst integration
templates/ The pure Typst resume layout
output/ The final tailored PDF

The Result

A robust, highly opinionated, incredibly fast resume generator. With zero dependencies other than a single compiled Rust binary, I can traverse a 5-second CLI wizard and have a meticulously typeset PDF waiting in my output/ folder, directly aligned with the specific job description I am targeting.

No AI hallucinations, no broken formatting, and most importantly, no wasted time.

Ready to try it?

Download the latest release and the resume-data.zip, place them in the same directory, and fire it up.

$ ./resume-pipeline-linux-x86_64

Navigation & Control

KeyAction
EnterContinue / Generate PDF
SpaceToggle an item
J / K / ArrowsMove up/down
E / PToggle Email / Phone (screen specific)
Eto select bullets (screen specific)
BackspaceGo back
QQuit

Once you generate a resume, it appears in output/resume.pdf instantly.

Feel free to fork the repo and make it your own.