How to totally over-engineer your CV in a few easy steps
Tags: latex, githubactions, perl
Published: 2021-03-19 09:32:20

---
title: How to totally over-engineer your CV in a few easy steps
published: true
description: Applying all kinds of workflows to automatically generate your CV from source and data
tags: LaTeX, GithubActions, Perl
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ao7re0mra0oks7izssjp.jpg
---
Once you need (or don't really need) to create a CV, do it real good, making it show off a few skills. Instead of a static file, make it a dynamic showcase of a few things that you have learned in your career and that might be interesting for the job you've applied to.

> Or simply show that you like overengineering something that's mostly done using a word processor. Anyway.

Anyway, here are the yaks I shaved when creating [this CV](https://jj.github.io/cv), which, just in case you wondered, is my (more or less real) brief resume, practically up to date. It's got a free license, so feel free to fork the repo (and also add a few pieces more of over-engineered stuff).

## Use (or adapt) your own CV template

There are many LaTeX templates out there you can use. What they essentially do is pump up headers, and of course give the finished CV a specific appearance. Programming LaTeX is exactly in 0 job postings, but picking up something in a totally unknown language and adapting it to your needs is indeed a nice skill. Anyway, [here's the result](https://github.com/JJ/cv/blob/master/friggeri-jj-cv.cls) where basically a few fonts and colors were changed, so that they're adapted to modern Ubuntu packages, mostly.

> Check out [git blame](https://github.com/JJ/cv/blame/master/friggeri-jj-cv.cls) just in case you want to see exactly what was changed, and maybe [use the original](https://www.overleaf.com/latex/templates/friggeri-cv-template/hmnchbfmjgqh).

## Use XeLaTeX and biber, not LaTeX

Well, that came with the original template, so, well, it wasn't really much of a choice. What's nice about using `biber` is that it's a Perl script, and you need to get the correct CPAN package and Perl version to get it working.

## Generate the PDF automatically

It would be a waste of computing time to type `make pdf` or anything like that to actually generate it, right? Actually, it's more complicated than that. Best practices advise you to *not* include generated stuff in your repo (mostly), and this PDF would clearly be a *product* of your *application* (the CV generator). So we could as well create real releases, that have the CV attached to them. We do so with this [Github action](https://github.com/JJ/cv/blob/master/.github/workflows/generate.yaml):

```yaml
name: "Generate CV"

on:
  push:
    tags:
      - '*'

jobs:
  generate-cv:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Install tools
        run: .github/workflows/latex-install.sh
      - name: Generate pdf
        run: ./generate_cv.sh
      - name: Go to main branch and generate thumbnail
        run: git status && git checkout master && thumbpdf --makepng --verbose --noclean cv.pdf && mv thb1.png cv.png
      - name: Publishes CV
        uses: ncipollo/release-action@v1
        with:
          artifacts: "cv.pdf"
          body: Fresh CV generated
          token: ${{ secrets.GITHUB_TOKEN }}

```
It works only when you tag a repository (and remember to `git push && git push --tags` so that the actual commit that's tagged is used). It needs to check out the whole repo, and not only download the tagged code, for reasons that will be clear a bit later. But we need to [install the tools](https://github.com/JJ/cv/blob/master/.github/workflows/latex-install.sh):

```sh
sudo apt-get install texlive-xetex biber fonts-goudybookletter texlive-fonts-extra  texlive-science-doc texlive-science fonts-adf-accanthis fonts-smc-gayathri ghostscript
```

Essentially it's installing a bunch of packages, including the fonts that were declared in the LaTeX class file at the beginning. And `ghostscript`, whose need we'll see a bit later. This takes a good while, around 4 minutes. So it's ripe for optimization, one way or the other. We could dockerize it, but we'll save dockerization for later. We could also use a cache. And that we tried.

> We really *over-engineered* this one...

[This version](https://github.com/JJ/cv/blob/ab58acfbdc88a1b1e94d5d8eaa55dfc3308a1c2f/.github/workflows/generate.yaml) uses [a caching github action step](https://github.com/marketplace/actions/cache-anything-new). Wasn't really worth the while. It took like a minute to check for cache hits, and around two minutes to actually download the cache in case of cache misses, not to mention around half an hour to actually set up the cache in the first place. All that to shave a bare minute... Not really worth the while. That way we can change more things in the Latex setup if we want.

Actually generating the PDF is [relatively straightforward](https://github.com/JJ/cv/blob/ab58acfbdc88a1b1e94d5d8eaa55dfc3308a1c2f/generate_cv.sh):


```sh
xelatex cv.tex
biber cv
xelatex cv.tex
```

Except that, remember, `biber` uses Perl. So how come this work? Well, perl is installed in all Linux runners in GitHub actions. We'll see more of that later on... So this works without a glitch.

After that, we can create the release. Jump over the next step (which is there for historical reasons) and go to the next one, which simply `Publishes CV` using another external github action. Very simply, it picks the generated artifact, `cv.pdf`, creates the release and attaches it to it.

But we need to generate a thumbnail, because of course, we need a thumbnail to link to the actual CV. I said this was going to be overengineered, right? This will be used in the index page to point to the actual page that will be used to download the CV. This does the trick:

```sh
git status 
  && git checkout master 
  && thumbpdf --makepng --verbose --noclean cv.pdf 
  && mv thb1.png cv.png
```


Actually, the last two lines do; the first are there to go from the tag to the main branch. After exploring lots of different utilities, like `convert`, part of ImageMagick, which is actually broken, `thumbpdf`, which is installed alongside LaTeX but actually uses `ghostscript` underneath, works nicely, generating a PNG for each page. The last command calls it the way we want.

> `thumbpdf` is also written in Perl. Now you start to understand why Perl is installed everywhere (and why it's always a *good thimg* (TM) to know how to use it. As a matter of fact, I had to check out the source to find out what was missing after having it fail with an obscure error message.

This is actually a generated file, and we've included it in the repo. Well, why not. It's a small file, images (like this, which is actually an icon) will be needed for the page (and you can see it above). But just *having* it in this local copy of the repo is not enough. We will need to push it to the repo.

But before, we'll also create other things we need to push to the repo. The CV is going to be uploaded to a different URL every time a release is created. Although the template for such URL is straightforward, and can be found by simply navigating the site, we need something much simpler: generate a page with a link that you can simply click to download de CV. Perl again to the rescue, in these two final steps of the action:

```yaml
      - name: Generate URL
        run: .github/workflows/generate-download-url.pl > DOWNLOAD.md
      - name: Pushes thumbnail and URL file.
        env:
          USER_NAME: JJCVBot
          EMAIL: jjmerelo@gmail.com
          MY_COMMIT_MSG: Updates download doc
        run: curl https://raw.githubusercontent.com/JJ/perl-GitHub-Actions/main/bin/commit-push.pl | perl
```

The first script effectively generates the markdown file that will be checked in, and that will be rendered to web and used effectively as a signpost to download the CV. Here's that iw does:

```perl
use strict;
use warnings;
use constant URL_FILE => "download_cv_url.txt";

use Git;

use v5.14; # For say
my $repo = Git->repository (Directory => '.');
my @tags = $repo->command("tag");

my $tag = pop @tags;

my $download_url = "https://github.com/JJ/cv/releases/download/$tag/cv.pdf";

open my $url_file, ">", URL_FILE;
print $url_file $download_url;
close $url_file;

say <<EOC;
# Download CV
[Download latest version of CV in PDF]($download_url)
EOC
```
First interesting thing about this is that it's run directly on the Github Actions runner, no external dependencies. It `use`s `Git`, a Perl module installed alongside `git` proper. That's used to get the tags in the repo (remember, we checked out the whole repo) and use the last one to generate the URL that's printed to a file, and also to the `DOWNLOAD.md` markdown file. 

> The tag that triggered the action is actually available as an environment variable, so we could have used that. However, we didn't know in principle if we were going to use that kind of trigger. This script will work even if we trigger the action in some other way.

Finally, we're checking this in to the repository; there are three files: the download URL (which we could, in principle, use like this: `wget $(wget raw_download_url)`), the thumbnail and `DOWNLOAD.md`. We will use an external Perl script to do so.

This script is part of the [`GitHub::Actions`](https://metacpan.org/release/GitHub-Actions) perl module, which contains a few utility functions (and scripts) to use within your, you guessed it, Github Actions. Since GHAs use environment variables to communicate, it takes the username and email (used to set the git configuration locally for the commit; you should use your own email if you want the commit to be identified as yours in GitHub) and the commit message. Here's the (rater uncomplicated) script:

```perl
use strict;
use warnings;
use v5.14;

use Git;

my $repo = Git->repository (Directory => '.');

my $user_name = $ENV{'USER_NAME'} // $ENV{'GITHUB_ACTOR'};
my $email = $ENV{'EMAIL'};
my $commit_msg = $ENV{'MY_COMMIT_MSG'} // "No message";
if ( $repo->command( 'status', '-s' ) ) {
  $repo->command('config', '--global', 'user.email', $email);
  $repo->command('config', '--global', 'user.name', $user_name );
  $repo->command('commit', '-am', $commit_msg );
  $repo->command('push');
}
```

Again, it's perusing the `Git`  module which we know is available, there are a whole lot of [Perl modules](https://gist.github.com/JJ/edf3a39d68525439978da2a02763d42b) availables there, doing a whole lot of things. If you can use them, you will save some time because you will not need to *pack* them somehow. Anyway, it configures the email and name, commits and push. Couldn't be more straightforward. You could actually do that easily with a shell script, pretty much this way:

```sh
if [[ $(git status -s) ]]; then
              git config --global user.email "jjmerelo@gmail.com"
              git config --global user.name "ÁgilDataBot"
              .github/workflows/commit-msg.pl | git commit -aF -
              git push
          else
              echo "🟏 No changes"
          fi
```

Essentially, you will need to avoid making a commit and push if there are no changes, because the step would error (it could happen if you have pushed to an existing tag, for instance, and the cv has not changed).

## That's it for the resumé

However, there's an important part of the resumé which is your programming timeline. That will need a whole 'nother article, I guess. You can check it out [in part II of this series](https://dev.to/jj/how-to-totally-over-engineer-your-cv-part-two-1oip)