Code the Docs Blog

Resumé-as-Code: Small Data Sourcing for Fun & Profit

What better way to show off what I do as a documentation platform developer (“DocOps engineer”) than by generating my own resumé with the technology stack I use professionally? Several prospective clients have remarked on the demonstrative novelty of this approach. If you also earn a crust from setting up documentation environments for engineering teams and the like, consider building your professional documentation from code to show off your skills.

With its singular source and dual HTML and PDF output, my resumé is a microcosmic model implementation of the AJYL tech stack, consisting of AsciiDoc, Jekyll/JAMstack, YAML, and Liquid.

In this post, I will walk you through how I set it all up, explaining how each component language and utility come into play.

The Sources

Most of the specific content items in my resumé are stored as small data objects arranged in a YAML file, similar to fields in a database. A small-data file is simply a plaintext document made up of keys and values structured such that both people and machines can modify and consume the organized information.

Here is a YAML snippet drawn directly from the single-sourced file.

From data/dominick.yml — Resumé small data source file snippet
    - org: Codewriting, LLC
        - Principal
        - Codewriter
        start: 2017-10-01
      description: maintain open-source technical documentation platform ([LiquiDoc CMF]); install, instruct, and maintain docs platforms for excellent engineering organizations
    - org: Rocana (formerly ScalingData)
        - Technical Documentation Manager
        - Engineering Technical Lead
        start: 2015-03-01
        end: 2017-10-01
      description: responsible for writing, editing, and managing all customer-facing technical docs, as well as overseeing all common internal documentation; create & maintain tooling to facilitate docs

If you inspect the whole file, you will see it contains several clusters of data, each analogous to a table in a database.

One advantage of maintaining small data in flat files is that you can build an entire, perfectly portable datasource with a text editor in mere minutes.

In this case, parameter values contain AsciiDoc markup. Notice on the first line beginning with a destination: key, the value text contains AsciiDoc hyperlink markup.

This data gets parsed with a Liquid template. This particular Liquid template (see complete source) weaves those AsciiDoc-formatted strings from the data file with more AsciiDoc markup.

From resume.asciidoc — Template mixes Liquid with AsciiDoc, generates new AsciiDoc source
== Employment History
{% for p in posts.paid %}
{{ }} {separator} [dateline]#{{ p.dates['start'] | date: "%b %Y" | remove: "-00-00" }} - {% if p.dates['end'] %}{{ p.dates['end'] | date: "%b %Y" | remove: "-00-00" }}{% else %}present{% endif %}#::{% for title in p.titles %}
_{{ title }}_{% unless forloop.last %}, {% endunless %}{% endfor %} +
{{ p.description }}
{% if forloop.index == 2 %}
{% endif -%}
{%- endfor %}

Because Liquid templates are usually a mix of at least two markup languages (Liquid markup itself plus your target format), they can feel pretty busy. Such is the nature of content templating. Nevertheless, we can generate such elegant code this way.

Don’t worry about the details of this code. Basically what it does is iterate (“loop”) through the posts.paid array in the data file, creating an entry for each item it comes across (objects describing my past paid gigs). As it churns, Liquid expresses the details of each object, mixing it in with some AsciiDoc markup you’ll also recognize in the output just below.

From _build/pages/resume.adoc — AsciiDoc source file output after parsing
== Employment History

Codewriting, LLC {separator} [dateline]#Oct 2017 - present#::
_Codewriter_ +
maintain open-source technical documentation platform ([LiquiDoc CMF]); install, instruct, and maintain docs platforms for excellent engineering organizations

Rocana (formerly ScalingData) {separator} [dateline]#Mar 2015 - Oct 2017#::
_Technical Documentation Manager_,
_Engineering Technical Lead_ +
responsible for writing, editing, and managing all customer-facing technical docs, as well as overseeing all common internal documentation; create & maintain tooling to facilitate docs

The above snippet includes just the AsciiDoc generated for the first two entries, though the _build/pages/resume.adoc file is a complete AsciiDoc document source.

Since this file is built and thus not tracked in the Git repository, to see the whole file you would have to clone and build the Codewriting repo, generating a copy into the _build/pages/ directory.)
Because that final, prebuilt source is generated before this blog, the above snippet is directly sourced from the resulting file, accessed while this blog post was being rendered and ensuring it always accurately reflects the current state of the underlying data. (See the bonus explainer at the end of this post.)

Finally, I render that AsciiDoc file into rich-text output. The PDF document is generated by the killer Prawn PDF utility, after which the Jekyll static site generator spits it out as HTML. Both of these processes invoke the Asciidoctor rendering engine, as detailed below in The Build.

The Output

Here are images from the relevant details of both rendered artifacts.

Example 1. HTML version
screenshot detail resume employment html
Example 2. PDF version
screenshot detail resume employment pdf

The Build

Now that we see what happened, let’s explore how it was scripted.

All of this activity is coordinated by LiquiDoc, my free, open-source utility for combining AsciiDoc, YAML, and Liquid with Jekyll and other JAMstack services and utilities. LiquiDoc is a nascent API for the AJYL docstack, designed for building rich-text documents in many editions and formats, all from the same source, by invoking upstream tools (such as Asciidoctor, Liquid, and Jekyll processing engines).

The Codewriting project’s LiquiDoc build configuration file (config) for this site is pretty long, but essentially three relevant stages generate both the HTML and PDF versions of the resumé. Let’s look at the first stage.

From _configs/build-global.yml — LiquiDoc config file resume parsing stage
- stage: parse-resume
  action: parse
  message: Building BD resumé.
  data: data/dominick.yml
  - template: _templates/liquid/resume.asciidoc
    output: _build/pages/resume.adoc

Here LiquiDoc is instructed to parse the dominick.yml data with the resume.asciidoc template. This generates resume.adoc, an AsciiDoc source file containing its own output instructions, saved to the ephemeral _build/pages/ directory.

Whenever the resume.adoc file is processed by Asciidoctor, header information (“frontmatter” in Jekyll parlance) designates the output settings and variables. First, it establishes settings and default variables, which will be used in the Jekyll site generation.

From _build/pages/resume.adoc — Key Jekyll frontmatter settings and content variables
// Jekyll-specific settings
:page-layout: resume
:page-permalink: /brian-dominick-resume
:edition: html
:otheredition: PDF
:includes_path: assets/includes
:header_cols: 1
:doctitle: Brian Dominick, Codewriter
:contact: link:[contact Brian]
:contact_table_width: 100%

This built file’s header also contains information for the PDF rendering.

From _build/pages/resume.adoc — Key PDF settings and content variables
// PDF override settings
:edition: PDF
:otheredition: HTML
:includes_path: ../assets/includes
:contact: 315-254-0342 / [email protected]
:contact_table_width: 80%
:separator: |
:header_cols: 1,3

The first and last lines are conditionals; they ensure the enveloped settings are only applied during rendering procedures if the backend-pdf setting (“attribute” in AsciiDoc parlance) is defined. These will replace any previously set parameters with the same names.

Which gets us back to the LiquiDoc config. The next stage instructs LiquiDoc to actually render the PDF version of the output from the source file prebuilt in the last stage (_build/pages/resume.adoc).

From _configs/build-global.yml — LiquiDoc config file resume PDF render stage
- stage: render-resume
  action: render
  message: Rendering resumé files.
  source: _build/pages/resume.adoc
  data: _configs/asciidoctor.yml
  - output: _build/assets/files/brian-dominick-resume.pdf
    style: theme/pdf/resume-theme.yml
    doctype: article
      edition: pdf
      icons: font
      pdf-fontsdir: theme/fonts
      imagesdir: assets/images
      safe: unsafe

This block invokes a very simple Asciidoctor conversion using the PDF backend extension, which engages Prawn for the final conversion. A customized YAML-formatted theme file enforces the style of the PDF output.

The resulting PDF artifact will later be copied into the Jekyll-generated website so it can be served, but first we have to build the rest of the site, including the HTML version of the resumé. LiquiDoc has us covered here, as well.

From _configs/build-global.yml — Jekyll build execution stage
- stage: build-site
  action: render
    - backend: jekyll
          - _configs/jekyll.yml
          - _configs/asciidoctor.yml
        stylesdir: theme/hyde/css
        source-highlighter: highlightjs

This section of the config script instructs Jekyll to generate a site, one page of which will be the HTML version of the resumé, as dictated by the _build/pages/resume.adoc file’s frontmatter settings referenced above. This conversion does not require a specific instruction, as Jekyll renders all files in _build/pages/ by default, and this prebuilt file is after all just another Jekyll-ready AsciiDoc file like all its siblings.

After the static site is built, LiquiDoc copies the PDF resumé into it, and the whole thing is ready to be served.


These are all the main features of an AJYL platform: small data in YAML flat files, content in AsciiDoc, templating and transformation with Liquid, and Jekyll to put it all together as a proper website.

The rest of the Codewriting site uses similar techniques to massage data and content together, either during prebuilding with Liquid or when rendering final artifacts. Asciidoctor uses parameters (AsciiDoc attributes) when converting AsciiDoc files to HTML, PDF, and other formats. Also during the render build, Jekyll engages Liquid’s template engine to construct the site’s HTML (or even to preprocess AsciiDoc content files containing Liquid markup). All this complementarity is key to the AJYL environment.

While a resumé is merely a novel implementation of this varied and powerful technology stack, imagine what this kind of flexibility can do for conveying complex product data in numerous permutations of editions and output formats.

Meta: How this Post was Made

All of the snippets included in this blog entry are derived from the original source. The Code the Docs blog is sourced in AsciiDoc, enabling dynamic embedding (“inclusion”) of content from the codebase itself.

For instance, look at the source that renders the final code listing above (From _configs/build-global.yml — Jekyll build execution stage).

From the AsciiDoc source for this blog entry
.From _configs/build-global.yml -- Jekyll build execution stage

The original config file (_configs/build-global.yml) is tagged with comment code indicating what to snip for inclusion into another file. (This file is copied into the ephemeral build directory to make it available in this way, hence we are technically reading from a clone saved to _build/assets/includes/ early in the build procedure.) In the blog post source, the AsciiDoc include:: macro includes a request for the section of code tagged build-jekyll-example-snippet.

Note the advantage over conventional blogging, which entails copying and pasting source code samples into a post that gets saved in a database (and forgotten). Once you’ve duplicated the source, it becomes a divergence risk; unless you update any docs referencing it, they will be inaccurate. How many bloggers do you know who update code samples in years-old posts to ensure they stay current? (Not me, I must admit.)

I will add that this system came in handy throughout the writing of this post, during which I manipulated each source multiple times in order to arrange good example snippets.

Imagine if your user docs always reflected current code examples and other canonical information, drawn straight from the product source.