| Title: | Data-Driven CVs with 'Quarto' and 'Typst' |
| Version: | 0.3.0 |
| Date: | 2026-05-04 |
| Description: | Provides tools for producing data-driven curriculum vitae documents from structured data stored in an Excel workbook. The core workflow reads CV content from a workbook, converts it into 'Typst' layout blocks, and renders a polished PDF via the 'Quarto' publishing system. Includes functions for reading and cleaning CV data, building 'Typst' section headings and entries, rendering CV sections from data frames, and scaffolding new CV projects with a standard folder structure and template workbook. Designed to separate content from layout: CV data lives in the spreadsheet, rendering configuration lives in 'Quarto', and transformation logic lives in small, reusable R functions. See the 'Typst' typesetting system at https://typst.app and the 'Quarto' publishing system at https://quarto.org. Inspired by the 'vitae' package https://CRAN.R-project.org/package=vitae and the 'Awesome CV' LaTeX template https://github.com/posquit0/Awesome-CV. |
| License: | MIT + file LICENSE |
| Encoding: | UTF-8 |
| Language: | en-US |
| URL: | https://github.com/erwinlares/curriculr, https://erwinlares.github.io/curriculr/, https://doi.org/10.5281/zenodo.19930400 |
| BugReports: | https://github.com/erwinlares/curriculr/issues |
| Depends: | R (≥ 4.2.0) |
| Imports: | cli, fs, lifecycle, quarto, readr, openxlsx2 |
| Suggests: | knitr, rmarkdown, spelling, withr, pdftools, testthat (≥ 3.0.0) |
| Config/testthat/edition: | 3 |
| VignetteBuilder: | knitr |
| Config/roxygen2/version: | 8.0.0 |
| NeedsCompilation: | no |
| Packaged: | 2026-05-08 18:19:24 UTC; lares |
| Author: | Erwin Lares |
| Maintainer: | Erwin Lares <erwin.lares@wisc.edu> |
| Repository: | CRAN |
| Date/Publication: | 2026-05-13 08:00:08 UTC |
curriculr: Data-Driven CVs with 'Quarto' and 'Typst'
Description
Provides tools for producing data-driven curriculum vitae documents from structured data stored in an Excel workbook. The core workflow reads CV content from a workbook, converts it into 'Typst' layout blocks, and renders a polished PDF via the 'Quarto' publishing system. Includes functions for reading and cleaning CV data, building 'Typst' section headings and entries, rendering CV sections from data frames, and scaffolding new CV projects with a standard folder structure and template workbook. Designed to separate content from layout: CV data lives in the spreadsheet, rendering configuration lives in 'Quarto', and transformation logic lives in small, reusable R functions. See the 'Typst' typesetting system at https://typst.app and the 'Quarto' publishing system at https://quarto.org. Inspired by the 'vitae' package https://CRAN.R-project.org/package=vitae and the 'Awesome CV' LaTeX template https://github.com/posquit0/Awesome-CV.
Author(s)
Maintainer: Erwin Lares erwin.lares@wisc.edu (ORCID) [copyright holder]
Authors:
Erwin Lares erwin.lares@wisc.edu (ORCID) [copyright holder]
See Also
Useful links:
Report bugs at https://github.com/erwinlares/curriculr/issues
Add a new section to a curriculr workbook
Description
Adds a new sheet to an existing curriculr-formatted Excel workbook and
registers it in the sections control sheet. The new sheet is pre-populated
with the standard column spine so the user can start entering data
immediately without worrying about column names.
Usage
add_section(
workbook,
section,
label = section,
date_fun = "year_only",
title_col = "title",
org_col = "unit",
detail_col = NA,
where_col = "where",
overwrite = FALSE
)
Arguments
workbook |
A character string. Path to an existing curriculr Excel workbook. |
section |
A character string. Internal name of the new section. Must
be a valid Excel sheet name (no more than 31 characters, no special
characters). This name must match the sheet name exactly when referenced
elsewhere in the workbook or in |
label |
A character string. Display label shown as the section heading
in the rendered CV. Defaults to |
date_fun |
A character string. Token controlling date formatting for
this section. One of |
title_col |
A character string. Name of the column used as the primary
entry label in the rendered CV. Defaults to |
org_col |
A character string or |
detail_col |
A character string or |
where_col |
A character string or |
overwrite |
A logical. Whether to overwrite an existing sheet of the
same name. Defaults to |
Details
add_section() performs the following steps:
Validates that
workbookexists and thatsectionis not already present (unlessoverwrite = TRUE).Appends a new sheet named
sectionwith the standard column spine:title | unit | startMonth | startYear | endMonth | endYear | where | detail | include_in_resume.Appends a new row to the
sectionssheet registering the new section with the supplied metadata. Whenoverwrite = TRUE, any existing row for this section is replaced.Writes the modified workbook back to
workbookin place.
The workbook is modified in place. There is no undo. Consider keeping a
backup copy before calling add_section() if the workbook contains data
you cannot reconstruct.
Control sheets (profile, sections, theme, readme) cannot be used
as section names.
Value
Invisibly returns workbook. Called primarily for its side effect
of modifying the workbook on disk.
Examples
# Copy the template to a temp directory and add a section
tmp <- file.path(tempdir(), "cv-data.xlsx")
file.copy(
system.file("extdata", "cv-data-template.xlsx", package = "curriculr"),
tmp
)
add_section(tmp, section = "patents")
## Not run:
# Add a section with a display label that differs from the sheet name
add_section("cv-data.xlsx",
section = "invited_talks",
label = "Invited Talks",
date_fun = "month_year")
# Add a section without an organization or location line
add_section("cv-data.xlsx",
section = "languages",
label = "Languages",
date_fun = "none",
org_col = NA,
where_col = NA)
## End(Not run)
Generate a CV document from a curriculr workbook
Description
Called with no arguments, create_cv() runs in scaffold mode: it
copies the template Excel workbook and placeholder profile image to the
current working directory and prints instructions for the next step. No
rendering takes place.
Usage
create_cv(
data = NULL,
photo = NULL,
output_file = "CV.pdf",
overwrite = FALSE,
variant = "cv",
use_icons = "fontawesome"
)
Arguments
data |
A character string or |
photo |
A character string or |
output_file |
A character string. Name of the output PDF file.
Defaults to |
overwrite |
A logical. Whether to overwrite existing files. Defaults
to |
variant |
A character string. Controls content scope. |
use_icons |
A character string. |
Details
Called with data and photo arguments, create_cv() runs in render
mode: it reads the workbook, generates CV.qmd, and renders it to PDF
using Quarto's Typst engine. Both CV.qmd and CV.pdf are written to
the same directory as the workbook.
Scaffold mode (no arguments):
Copies
cv-data-template.xlsxtogetwd().Copies
placeholder.pngtogetwd().Prints instructions for editing the workbook and rendering the CV.
Render mode (data supplied):
Resolves and validates the workbook and photo paths.
Reads the workbook with
read_cv_data(), applyingvariantfiltering.Resolves theme values from the workbook or built-in defaults.
Writes
CV.qmdby injecting all resolved values into the package template via sentinel substitution.Calls
quarto::quarto_render()to produce the PDF.
When photo = NULL, the CV header renders as a single full-width column
containing the name, contact line, address, and profile statement. When
a photo path is supplied, the header uses a two-column layout with the
photo on the left.
When variant = "resume", row-level filtering is controlled entirely by
the include_in_resume column in each section sheet. Check the rows you
want included in the resume and leave the rest unchecked.
Theme values (fonts, colors, page layout) are read from the theme sheet
in the workbook. If the theme sheet is absent, built-in defaults are
used. Individual keys missing from a partial theme sheet are filled from
defaults.
Value
In scaffold mode, invisibly returns the path to the directory where files were copied. In render mode, invisibly returns the path to the rendered PDF.
Examples
# Scaffold mode — copy template files to a temp directory
withr::with_dir(tempdir(), create_cv())
## Not run:
# Render mode — requires cv-data.xlsx, Quarto, and Typst
create_cv(
data = "~/my_cv/cv-data.xlsx",
photo = "~/my_cv/me.jpeg"
)
# Render mode — no photo, single-column header
create_cv(
data = "~/my_cv/cv-data.xlsx"
)
# Render mode — resume variant
create_cv(
data = "~/my_cv/cv-data.xlsx",
photo = "~/my_cv/me.jpeg",
variant = "resume",
output_file = "resume.pdf"
)
# Render mode — plain text contact line, custom output filename
create_cv(
data = "~/my_cv/cv-data.xlsx",
photo = "~/my_cv/me.jpeg",
use_icons = "none",
output_file = "erwin-lares-cv.pdf"
)
## End(Not run)
Build the contact line for the CV header
Description
Assembles a Typst-formatted contact line from a profile vector. When
use_icons = "fontawesome", known contact fields are rendered with their
Font Awesome icon via the Typst @preview/fontawesome package. Fields with
no icon equivalent fall back to plain text with a warning. When
use_icons = "none", all fields render as plain text.
Usage
cv_contact_line(profile, use_icons = "fontawesome")
Arguments
profile |
A named character vector as returned by the |
use_icons |
A character string. |
Details
This function is called inside CV.qmd and is exported so that users
building custom Quarto templates can call it directly.
Value
A character string of raw Typst markup for the contact line.
Render a CV section from a data frame
Description
Iterates over a CV data frame and writes each row as a Typst CV entry by
calling .cv_entry() for each row and passing the result to base::cat().
Usage
cv_render_section(
data,
title_col,
org_col = NULL,
detail_col = NULL,
date_fun = .cv_date_range,
where_col = "where"
)
Arguments
data |
A data frame containing CV entries. Typically one element of the
list returned by read_cv_data(), e.g. |
title_col |
A character string. Name of the column to use as the entry title. Required. |
org_col |
A character string or |
detail_col |
A character string or |
date_fun |
A function or |
where_col |
A character string or |
Details
This function is intended to be called inside a Quarto document chunk with
results = 'asis'. The cat() call writes raw Typst blocks directly into
the document output stream. Nothing is returned — the function is called
entirely for its side effect.
For sections that use dates, pass one of .cv_date_range() or
.cv_year_range() as date_fun. For sections where dates are not relevant
(skills, affiliations), pass date_fun = NULL.
Value
Invisibly returns NULL. Called for its side effect of writing
Typst blocks to the Quarto document output stream.
Examples
# Load sample data and render the experience section
cv <- read_cv_data(
system.file("extdata", "cv-data-template.xlsx", package = "curriculr")
)
cv_render_section(cv$experience,
title_col = "title",
org_col = "unit",
detail_col = "detail")
## Not run:
# The following examples are intended to be called inside a Quarto chunk
# with results = 'asis'. They require internal helpers and a loaded cv object.
# Year-only dates
cat(.cv_section("Education"))
cv_render_section(cv$education,
title_col = "title",
org_col = "institution",
detail_col = "detail",
date_fun = .cv_year_range)
# No dates, no location
cat(.cv_section("Skills"))
cv_render_section(cv$skills,
title_col = "title",
org_col = "unit",
date_fun = NULL,
where_col = NULL)
# Custom inline date function
cat(.cv_section("Presentations"))
cv_render_section(cv$presentations,
title_col = "unit",
org_col = "title",
date_fun = function(row) {
trimws(paste(.cv_value(row, "startMonth"),
.cv_value(row, "startYear")))
})
## End(Not run)
Create a Typst CV section heading
Description
Generates a raw Typst block for a CV section heading. The first letter of the section title is styled with the CV accent color. The heading is followed by a horizontal rule that fills the remaining line width.
Usage
cv_section(title)
Arguments
title |
A character string. The section title to display, e.g.
|
Details
This function is called inside CV.qmd to emit section headings. It is
exported so that users building custom Quarto templates can call it
directly without using :::.
Value
A character string of raw Typst markup.
Read CV data from an Excel workbook
Description
Reads all sheets from a curriculr-formatted Excel workbook and returns them
as a named list of data frames. Each sheet becomes one list element, named
after the sheet. The profile sheet is returned as a named character vector
for convenient scalar access. The theme sheet is returned as a named
character vector keyed by the key column. The sections sheet is returned
as a data frame in the order the rows appear in the workbook — row order
controls section render order and must not be sorted.
Usage
read_cv_data(path = "data/cv-data.xlsx", variant = "cv")
Arguments
path |
A character string. Path to the Excel workbook. Defaults to
|
variant |
A character string. Controls which rows are included from
each section sheet. |
Details
Sheets containing a startYear column are sorted in descending order by
startYear so that the most recent entries appear first. The profile,
theme, and sections sheets are exempt from sorting.
The workbook must follow the curriculr schema. Every section sheet should
contain a title column as the primary entry label. The profile sheet
must contain field and value columns. The sections sheet must contain
at minimum section and label columns. The theme sheet, if present,
must contain key and value columns.
All cell values are read as character strings after import. Numeric columns
such as startYear and endYear are coerced to character so that
downstream rendering treats them uniformly. The include_in_resume column
is read as a logical before coercion and used for row filtering when
variant = "resume".
Empty cells and cells containing the literal string "NA" are both
converted to NA.
If the theme sheet is absent, cv$theme is NULL and create_cv()
will fall back to built-in defaults.
Value
A named list with one element per sheet in the workbook. The
profile element is a named character vector; the theme element is a
named character vector (or NULL if the theme sheet is absent); all
other elements are data frames. The include_in_resume column is dropped
from returned data frames — it is used for filtering only and is not
passed to the rendering pipeline. Access sections as cv$education,
cv$experience, etc. Access profile fields as
cv$profile[["first_name"]]. Access theme values as
cv$theme[["accent_color"]]. Access the sections control sheet as
cv$sections.
Examples
# Read the sample data shipped with the package
cv <- read_cv_data(
system.file("extdata", "cv-data-template.xlsx", package = "curriculr")
)
cv$education
cv$profile[["first_name"]]
## Not run:
# Read a user-supplied file
cv <- read_cv_data("~/my_cv/cv-data.xlsx")
# Resume variant
cv <- read_cv_data("~/my_cv/cv-data.xlsx", variant = "resume")
## End(Not run)
Resolve a date_fun token to a function
Description
Maps a string token from the sections sheet to the corresponding date
formatting function used by cv_render_section(). This allows date
formatting behaviour to be controlled from the Excel workbook rather than
hardcoded in the Quarto template.
Usage
resolve_date_fun(token)
Arguments
token |
A character string. One of |
Value
A function suitable for passing to the date_fun argument of
cv_render_section(), or NULL when token is "none".
Escape text for safe use in Typst markup
Description
Converts an input value to a Typst-safe character string. Removes simple HTML line break tags, collapses repeated whitespace, escapes Typst special characters, and trims leading and trailing whitespace.
Usage
typst_escape(x)
Arguments
x |
A value or vector to escape. |
Details
CV content comes from Excel and may contain characters that Typst treats as
markup: #, $, %, &, ~, _, ^, {, }, [, ], or @.
The @ character is included because Typst may interpret email addresses as
references to labels.
Value
A character vector with Typst-sensitive characters escaped.