English version | Versione italiana
This repository is the single source of truth for my Curriculum Vitae, Cover Letter, and personal website content. Everything - work experience, education, skills, projects, publications - lives once, under data/, and four different outputs are generated from it automatically:
- 4 PDFs (CV + Cover Letter, Italian + English) via LaTeX/LuaLaTeX.
resume.json, a JSON Resume-format file that feeds the/cv/page on eneamanzi.github.io.papers.bib, a BibTeX file that feeds the website's dedicated/publications/page.- New project pages on the website, auto-scaffolded from the same data whenever a project doesn't have one yet.
No content is ever typed twice.
The PDFs are automatically generated and hosted on the pdf-release branch.
| Document | Version | View |
|---|---|---|
| Curriculum Vitae | Italian | cv_italian.pdf |
| Curriculum Vitae | English | cv_english.pdf |
| Cover Letter | Italian | cover_letter_italian.pdf |
| Cover Letter | English | cover_letter_english.pdf |
data/*.json (single source of truth, bilingual, Markdown emphasis)
│
├──► commons/lua_data_loader.tex ──► LuaLaTeX ──► 4 PDFs ──► pushed to
│ eneamanzi.github.io
│ (assets/pdf/)
├──► scripts-website-data/generate_resume_json.py
│ (validated: resume_pydantic_schema.py)
│ └──► resume.json ─────────────────────────────────┐
│ │
├──► scripts-website-data/generate_socials_yml.py │
│ └──► socials.yml ─────────────────────────────────┤
│ │
└──► scripts-website-data/generate_papers_bib.py │ pushed together to
└──► papers.bib ───────────────────────────────── │ eneamanzi.github.io
│ (assets/json/, _data/,
scripts/scaffold_project_pages.py ◄───────────────── _bibliography/)
(the website repo's own script, run against the
checked-out website repo before the commit above,
so a new project page - if any - lands in the
same push; see Website Integration)
Two independent GitHub Actions workflows drive the CV-repo side (see CI/CD) - one for the PDFs, one for everything else. papers.bib's bibtex field per publication is citation text as-is (hand-written while a paper is only submitted, copied verbatim from the publisher once it's indexed) - the generator does not parse or reconstruct BibTeX from separate fields.
data/: the database. One JSON file per domain, numbered to reflect render order (step of 10, leaving room for future insertions):10-personal.json- name, contact info, location, social profiles, the website bio/tagline20-education.json- degrees, thesis, grades30-work.json- work experience40-research.json- research activities (has no PDF/JSON-Resume standard equivalent - seecommons/lua_data_loader.texandresume_pydantic_schema.pyfor how it's handled)50-projects.json- personal/academic projects (each needs a stableid,category, andimportance- see Common Tasks)60-publications.json- papers (bibtexfield is the citation text as-is - see Common Tasks)70-languages.json- spoken languages80-skills.json- skills, including the "Soft Skills" category (rendered as its own section everywhere)90-meta.json- GDPR/legal boilerplate (PDF-only, never reaches the website)
cv.tex/cover_letter.tex: the LaTeX templates. They call the Lua loader.commons/lua_data_loader.tex: the Lua script that readsdata/*.json, converts Markdown emphasis (**bold**/*italic*) to LaTeX, and populates the document sections in the selected language.scripts-website-data/generate_resume_json.py: buildsresume.jsonfromdata/, validated againstscripts-website-data/resume_pydantic_schema.pybefore being written.scripts-website-data/generate_socials_yml.py: builds the website's_data/socials.yml(email, LinkedIn/GitHub usernames, CV PDF link) fromdata/10-personal.json- only the fields that are genuine CV facts; site-only settings (Google Scholar ID, WeChat QR, etc.) stay manually maintained on the website side.scripts-website-data/generate_papers_bib.py: builds the website's_bibliography/papers.bibfromdata/60-publications.json, concatenating each entry'sbibtexfield as-is and injectingabstract(from the structuredabstractfield, so it's never typed twice) andabbr(fromvenueAbbr, if set).scripts-website-data/dump_data_markdown.py: not part of the automated pipeline - run by hand locally (see Run Locally) whenever you need a single readable reference document (e.g. to fill out a LinkedIn/Indeed profile by hand). Dumps everydata/*.jsonfile, in filename order, fully in English then fully in Italian - generic and complete (nothing hand-picked or stripped), so a field added to any data file later shows up automatically.img/: static assets (profile picture, signature).
Where to make each kind of change - every one of these is just editing a JSON file and pushing; the PDFs and the website update themselves.
Update your bio, contact info, or social links
Edit data/10-personal.json: email, phone, location, profiles (LinkedIn/GitHub/WhatsApp), summary (the website's "Professional Summary" bio), websiteLabel (the tagline shown on the website, distinct from label which is the PDF's document title).
Add a job or internship
Add an entry to the array in data/30-work.json: name, position, location, startDate/endDate (MM/YYYY), summary, highlights (bullet list, Markdown allowed).
Add a research activity
Same shape as work, in data/40-research.json. Renders as its own "Research" section in both the PDF and the website (a website-only template override - see Website Integration).
Add a degree
Add an entry to data/20-education.json: institution, location, area, studyType, startDate/endDate, score (e.g. "Final Grade: 110/110 cum laude" - the "Final Grade:"/"Voto:" prefix is stripped automatically for the website's dedicated score field). Add a thesis: { title, supervisors } object if there is one - it becomes two extra highlight lines ("Thesis: ...", "Supervisors: ...") automatically.
Add a project
Add an entry to data/50-projects.json:
{
"id": "some-stable-slug", // assigned once, never changed - even if "name" changes later
"importance": 50, // website display order (step of 10) - independent of this array's own order
"category": "Academic",
"name": { "en": "...", "it": "..." },
"course": { "en": "...", "it": "..." },
"description": { "en": "...", "it": "..." },
"highlights": { "en": ["..."], "it": ["..."] },
"keywords": ["..."]
}Push it, and a new _projects/<id>.md is auto-created on the website with no further action - see Website Integration for how the matching works and what to do if you want to expand that page with a full write-up afterward.
Add a publication
Add an entry to data/60-publications.json: type, status ({"en": "submitted", "it": "sottomesso"} or similar), venue, year, date, authors (plain string, e.g. "Enea Manzi, Marco Anisetti"), title, abstract (feeds both the PDF/resume.json and, injected automatically, the website's papers.bib).
Also write a bibtex field: the literal citation text, e.g.
"bibtex": "@unpublished{manzi2026itadata,\n author = {Manzi, Enea and Anisetti, Marco},\n title = {...},\n year = {2026},\n note = {Submitted to ITADATA 2026}\n}"Write it by hand while the paper is only submitted (no DOI/pages/venue yet); once it's actually published somewhere, replace it with the ready-made BibTeX citation copied from the publisher (IEEE/Springer/ACM/...) instead of retyping it - don't reconstruct fields that are already given to you. Add extra fields the website understands (doi, pdf, video, code, selected, bibtex_show, ...) directly inside that same bibtex text when you actually have a real value for them - see the header of a generated papers.bib for the full list, or CUSTOMIZATIONS.md in the website repo. Optionally add "venueAbbr": "SOME-ABBR" for a venue badge - it needs a matching entry in the website's _data/venues.yml (maintained by hand there, not generated - see Website Integration).
Add or update a skill category / language
data/80-skills.json (keywords support Markdown emphasis, stripped automatically for the website since that template doesn't render it) / data/70-languages.json. The "Soft Skills" category name is special-cased everywhere (own section, per-item bold label rendering) - don't rename it.
Emphasize text anywhere above
Use **bold** / *italic* in any free-text field - never raw LaTeX or HTML (see Data Conventions).
Check your changes before pushing
See Run Locally - compile the PDF, and/or run the generate_*.py scripts locally to see the exact resume.json/socials.yml/papers.bib that would be pushed (the first two fail loudly, listing every problem, if something in data/ is malformed).
- Bilingual fields are
{"en": "...", "it": "..."}objects; a plain string means "same in both languages" (e.g. a person's name, "APIGuard Assurance"). - Emphasis inside free text uses Markdown -
**bold**,*italic*- never raw LaTeX or HTML. Each consumer converts it to whatever it needs (LaTeX via the Lua loader, HTML via Jekyll'smarkdownify, or stripped to plain text where a template doesn't support it - seescripts-website-data/generate_resume_json.py'sstrip_md). - Numbering (
10-,20-, ...): reflects render order, not creation order. The step of 10 leaves room to insert a new domain later (e.g.45-awards.json) without renumbering everything.
Two independent GitHub Actions workflows watch data/** - independent so that a failure in one never blocks the other:
build-latex.yml- compiles all 4 PDFs, publishes them to thepdf-releasebranch, and pushes the two CVs (not the cover letters) to the website'sassets/pdf/.generate-website-data.yml- runsgenerate_resume_json.py,generate_socials_yml.py, andgenerate_papers_bib.py; validates the JSON Resume output (Pydantic) and formats it with Prettier (matching the website's own.prettierrcvia--print-width 150, since that config file lives in the other repo); then, in the same job, checks out the website repo, copies the three generated files in, runs the website's ownscripts/scaffold_project_pages.pyagainst that checkout (so a newly-needed project page lands in the same commit instead of triggering a separate push from the website side), and pushes everything in one commit toassets/json/,_data/, and_bibliography/. On a pull request it only validates, it never touches the website.
Both need the API_TOKEN_GITHUB repository secret (a PAT with write access to eneamanzi.github.io) to push cross-repo.
eneamanzi.github.io is a separate repository; this one only pushes files into it, it never contains its logic. Things worth knowing if you're debugging the website side:
_includes/cv/render.liquid,skills.liquid, andlanguages.liquidare local overrides of theal_folio_cvgem's templates (Jekyll'sincludes_load_pathsmechanism lets a site-local file take precedence). They add a "Research" section (no standard JSON Resume equivalent) and fix a couple of upstream cosmetic bugs (empty()/icons when a field is blank). If the gem updates upstream, these need a manual diff/re-sync.scripts/scaffold_project_pages.py(in the website repo's ownscripts/folder, unrelated to this repo'sscripts-website-data/) is invoked directly by this repo'sgenerate-website-data.yml(see CI/CD), against the website checkout, before the commit - so it never needs a separate push of its own. For any project with no matching page yet, it creates one under_projects/<id>.md, seeded with the CV's summary and highlights under a "Summary (from CV)" heading. It matches by the project's stablecv_idfrontmatter field, not by title, so renaming a project's display name or a page's title later never causes a duplicate page. The website's ownscaffold-projects.ymlworkflow (triggered by a push touchingresume.json) still exists as a dormant safety net - in the normal case it finds nothing left to do._bibliography/papers.bibis generated (see above) - never edit it in the website repo._data/coauthors.ymland_data/venues.ymlare the opposite: they stay manually maintained in the website repo, since they're rendering preferences (which URL to link a coauthor's name to, what color a venue badge gets), not CV facts. SeeCUSTOMIZATIONS.mdin the website repo (section "Publications") for a real build-crash this surfaced (a commented-out BibTeX-shaped example is not actually inert to the parser in use) and the full list of extra fields the publications template supports.
Requires a TeX distribution (TeX Live, MiKTeX, or MacTeX) that includes LuaLaTeX.
git clone https://github.com/eneamanzi/curriculum-vitae.git
cd curriculum-vitaeEdit the JSON files under data/ with your own information (start with data/10-personal.json).
English (default):
latexmk -lualatex -jobname=cv_english cv.tex
latexmk -lualatex -jobname=cover_letter_english cover_letter.texItalian: the Italian translation is triggered by injecting \def\makeitalian{1} before the file is read.
latexmk -lualatex -jobname=cv_italian -e '$lualatex="lualatex %O \def\makeitalian{1} \input{%S}"' cv.tex
latexmk -lualatex -jobname=cover_letter_italian -e '$lualatex="lualatex %O \def\makeitalian{1} \input{%S}"' cover_letter.texRequires Python 3 and the packages in scripts-website-data/requirements.txt (just pydantic):
pip install -r scripts-website-data/requirements.txt
python3 scripts-website-data/generate_resume_json.py /tmp/resume.json
python3 scripts-website-data/generate_socials_yml.py /tmp/socials.yml
python3 scripts-website-data/generate_papers_bib.py /tmp/papers.bibThe first two exit with a non-zero status and a full list of validation errors if data/ produces something malformed - nothing is ever written half-broken. generate_papers_bib.py has no schema to validate against (each bibtex field is free text) - inspect /tmp/papers.bib by eye, or run it through a real BibTeX parser (e.g. Python's bibtexparser) if you want to double-check it.
There's no clean, ToS-compliant way to auto-publish to LinkedIn/Indeed (their APIs require formal partner approval for profile writes, and automating logins/scraping breaks their Terms of Service - real risk of account suspension). The pragmatic alternative: generate a plain-text reference, then paste by hand.
python3 scripts-website-data/dump_data_markdown.pyWrites cv_dump_linkedin.md at the repo root (gitignored, regenerate on demand - never committed) - every data/*.json file, in order, fully in English then fully in Italian, with nothing hand-picked or reworded. Open it in any Markdown viewer and copy whichever section you need into a third-party site's form by hand.