DRY Résumé Creation with LaTeX, Jinja2, and TOML

Programatically define a way to create, read, update, and delete résumé information.

Specifications

  • One source of data.
  • Facilitate the editing of LaTeX templates into Jinja2 templates via the use of different reserved characters for the Jinja2 language.

System Design

  • "Tom's Obvious, Minimal Language" [TOML] for data organization and storage in text files.
  • LaTeX templates for résumés.
  • Output programs for Portable Document Format [PDF] generation from LaTeX templates.

    pdflatex and xelatex, are LaTeX formats based on the respective engines. All output PDF by default.

  • Python programming language to test and write scripts.

Issues

Maintaining résumé data across various media and platforms is tedious and error prone.

It would be more efficient to maintain one source of data that could be read and then inserted into a given medium.

Quality LaTeX templates are available for download. Editing these templates can also be tedious and error prone. And changing to a new template for a new résumé style means repeating the input of the same information. The Jinja2 templating language is a possible solution to avoiding this repetition.

Nonetheless, it quickly becomes unweidly to use the Jinja2 templating language inside of LaTeX template because of the heavy use of {}% characters in both.

Python code can be used to test and facilitate the process of translation of LaTeX templates into Jinja2 templates and again back to a LaTeX template. This final LaTeX template can be used by output prgrams to generate PDF.

Examples

This LaTeX creates a list in a document.

See Your first LaTeX document tutorial.

\documentclass{article}
\begin{document}
  Hello World!
\begin{itemize}
    \item One
    \item Two
    \item Three
\end{itemize}
\end{document}

This LaTeX template is available here.

It would be ideal to be able to define a data structure with the items and use a templating language to generate the LaTeX template.

Simple Jinja2 template with rendered output

In [15]:
from jinja2 import Template
from faker import Faker

fake = Faker()

context = dict(items=[fake.catch_phrase() for _ in range(3)])
template = Template(
    """Example list in Jinja2
{%- for item in items %}
    - {{ item }}
{%- endfor %}
"""
)
print(template.render(**context))
Example list in Jinja2
    - Extended local database
    - Profit-focused heuristic project
    - Versatile mission-critical synergy

Unwieldy example of attempt to output LaTeX

Everywhere the {} LaTeX characters need preserving require special escaping.

This makes difficult legibility even more difficult.

In [16]:
template = Template(
    r"""Example list in Jinja2
\documentclass{{'{'}}article{{'}'}}
\begin{{'{'}}document{{'}'}}
  Hello World!
\begin{{'{'}}itemize{{'}'}}
{%- for item in items %}
    \item{{ item }}
{%- endfor %}
\end{{'{'}}itemize{{'}'}}
\end{{'{'}}document{{'}'}}
"""
)
print(template.render(**context))
Example list in Jinja2
\documentclass{article}
\begin{document}
  Hello World!
\begin{itemize}
    \itemExtended local database
    \itemProfit-focused heuristic project
    \itemVersatile mission-critical synergy
\end{itemize}
\end{document}

Possible solution: Python str translation method

  • Redefine the reserved {} characters in a LaTeX template to «»
In [17]:
(latex_translation := str.maketrans((original_latex := {"{": "«", "}": "»"})))
Out[17]:
{123: '«', 125: '»'}
In [18]:
(reverse_latex_translation := str.maketrans(dict(zip(original_latex.values(), original_latex.keys()))))
Out[18]:
{171: '{', 187: '}'}
  • Redefine the {}% characters used in Jinja2 to ≤≥|.
In [19]:
(jina_translation := str.maketrans((original_jinja := {"≤": "{", "≥": "}", "|": "%",})))
Out[19]:
{8804: '{', 8805: '}', 124: '%'}
In [20]:
(
    reverse_jinja_translation := str.maketrans(
        dict(zip(original_jinja.values(), original_jinja.keys()))
    )
)
Out[20]:
{123: '≤', 125: '≥', 37: '|'}
In [21]:
print(
    r"""{%- for item in items %}
    \item{{ item }}
{%- endfor %}""".translate(
        reverse_jinja_translation
    )
)
≤|- for item in items |≥
    \item≤≤ item ≥≥
≤|- endfor |≥
In [22]:
print(
    latex_template := Template(
        r"""\documentclass{article}
\begin{document}
  Hello World!
\begin{itemize}
    ≤|- for item in items |≥
    \item ≤≤ item ≥≥
≤|- endfor |≥
\end{itemize}
\end{document}""".translate(
            latex_translation
        )
        .translate(jina_translation)
        .translate(reverse_latex_translation)
    ).render(**context)
)
\documentclass{article}
\begin{document}
  Hello World!
\begin{itemize}
    \item Extended local database
    \item Profit-focused heuristic project
    \item Versatile mission-critical synergy
\end{itemize}
\end{document}

Write the latex template string to a tempfile

In [23]:
import tempfile
import os

fd, name = tempfile.mkstemp()
with os.fdopen(fd, 'w') as fh:
    fh.write(latex_template)

Upload the file to Minio object server.

In [24]:
!mc cp $name dokkuminio/public/simple_list.tex
...pqbej70yd:  222 B / 222 B ┃▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓┃ 100.00% 7.43 KB/s 0s

pipe the output into pdflatex

In [25]:
!curl -s https://minio.apps.selfip.com/public/simple_list.tex | pdflatex -output-directory latex_templates/
This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
 restricted \write18 enabled.
**entering extended mode
LaTeX2e <2017-04-15>
Babel <3.18> and hyphenation patterns for 3 language(s) loaded.

*(/usr/share/texlive/texmf-dist/tex/latex/base/article.cls
Document Class: article 2014/09/29 v1.4h Standard LaTeX document class
(/usr/share/texlive/texmf-dist/tex/latex/base/size10.clo))
(latex_templates//texput.aux)
*
*
*(/usr/share/texlive/texmf-dist/tex/latex/base/omscmr.fd)
*
*
*
*[1{/var/lib/texmf/fonts/map/pdftex/updmap/pdftex.map}]
(latex_templates//texput.aux)</usr/share/texlive/texmf-dist/fonts/type1/public/
amsfonts/cm/cmr10.pfb></usr/share/texlive/texmf-dist/fonts/type1/public/amsfont
s/cm/cmsy10.pfb>
Output written on latex_templates//texput.pdf (1 page, 23848 bytes).
Transcript written on latex_templates//texput.log.

Upload the PDF output to Dokku self-hosted Minio S3-compatible object storage

In [26]:
!mc cp latex_templates/texput.pdf dokkuminio/public/
...exput.pdf:  23.29 KB / 23.29 KB ┃▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓┃ 100.00% 3.46 MB/s 0s

Display the PDF created.

In [27]:
from IPython.display import display, IFrame
In [29]:
IFrame("https://minio.apps.selfip.com/public/texput.pdf", width=900, height=800)
Out[29]:
In [ ]: