Overview
My CV is generated by code, meaning everything is in plaintext and easily manageable by version control.
The final output I am looking for is a single document consisting of two pages. The first document is a single page text consisting of a brief introduction, commercial experience, other relevant experience and education; followed by second document, a skill tree.
Document 1 | Document 2 |
---|---|
Skills
I have created a roadmap.sh inspired skill tree/graph, it can be seen on my homepage.
This is generated using graphviz
with my tree/graph markup as a dot
file.
My build process uses taskfile
and it will generate various assets from dot
files as a pdf
, svg
, and png
.
Unfortunately dot
doesn’t have variables, so I use an intermediate dot
as source file, and run envsubst
to build a true dot
file.
skills.sh
:
#!/usr/bin/env bash
export SKILLS_FONT_NAME="Cousine"
# etc.
envsubst < src/skills.dot > dot/skills.dot
Unfortunately I have decided to use a non-standard font here too… Obviously this is to be containerised… So I will need to install a font in a container for graphviz
to be able to work with.
I have used a multi-stage build to accomplish this.
FROM bitnami/git:latest as font
RUN git -C /tmp clone https://github.com/googlefonts/cousine.git font
FROM nshine/dot:latest
# path inside repo with trailing slash
ARG font_path=fonts/ttf/unhinted/variable_ttf/
USER 0
RUN mkdir /usr/share/fonts/newfont
COPY --from=font /tmp/font /tmp/font
RUN find /tmp/font/$font_path -name '*.ttf' \
-exec install -v -m644 {} /usr/share/fonts/newfont \;
USER 1000
RUN fc-cache -v -f
CMD ["sh"]
- First container is just a lightweight container to
git clone
a font from Google fonts Github repository. - Then I will copy the
git
repo from the first stage into the basedot
image used to build the assets and install the font and update font cache.
Then the image, and skills tree assets are built like using a file like this using task
:
---
version: 3
output: group
vars:
WEB_COLOR: '#41403e'
DOCKER_IMAGE_SKILLS: alistaircol/skills
DOCKER_IMAGE_GRAPHVIZ: dot
DOCKER_RUN: |
docker run \
--rm \
--tty \
--volume="$(pwd):/app" \
--workdir="/app" \
--user=$(id -u):$(id -g)
DOCKER_RUN_SKILLS: "{{.DOCKER_RUN}} {{.DOCKER_IMAGE_SKILLS}}"
tasks:
lint:
cmds:
- "{{.DOCKER_RUN}} {{.DOCKER_IMAGE_YAMLLINT}} ."
interactive: true
default:
cmds:
- task: docker_pull
- task: build_dot_image
- task: clean
- task: build_dot
- task: build_png
- task: build_svg
- task: build_pdf
# the rest is omitted
The PDF output looks like this:
I use pdftk
to modify the PDF metadata too:
This is a stand-alone repository here and it has CI/CD.
Experience
This is a single page (with some great difficulty squeezing everything in) that is built in PHP
by using TCPDF
.
Using TCPDF
is relatively well documented (though not the easiest to use with the enormous amount of arguments in most method calls) so I won’t explain in great detail.
This is a relatively straightforward process, although I use a non-standard font, so this involves converting a ttf
using tools/tcpdf_addfont.php
.
The main entry looks like this:
public function build()
{
$this->addHeaderDecoration();
$this->addHeaderContent();
$this->addSummary();
$this->addTechnicalSkills();
$this->addCommercialExperience();
$this->addOtherRelevantExperience();
$this->addEducation();
$this->pdf->setPDFVersion('1.4');
}
- The
addHeaderContent
includes some personal information, so this is read fromenv
. - I explicitly use
setPDFVersion
, so I can manipulate the document later on, the default is1.7
.
This is one of the more laborious ways to generate such a document, but the main benefits of generating this way over, say, an HTML/CSS page that is converted to a PDF using a headless browser such as by puppeteer
:
- File size is usually much smaller.
- Much greater control of layout.
- Adding bookmarks.
- Customising certain meta attributes.
- Some security features and other things I haven’t investigated.
Modifying the metadata with pdftk
again:
Concatenation
This is a relatively simple process. I use mnuessler/pdftk
to combine the separate documents into a single document.
Document 1 | Document 2 |
---|---|
Metadata
Likely no one will care, but I use mnuessler/pdftk
to get and update the metadata of the documents with dump_data_utf8
and update_info_utf8
respectively.
Get Metadata
Basically I dump the metadata of the document and delete all lines starting with Info
and merge them with a file of the metadata Info
attributes I want to change.
This file will be used in the next step to update a document’s metadata.
# Reusable docker run command
docker_run = docker run --rm \
$(shell tty -s && echo "-i" || echo) \
-v "$(shell pwd):/app" \
-w "/app"
get_meta_skills_pdf:
${docker_run} -t mnuessler/pdftk build/skills.pdf dump_data_utf8 \
| sed '/^Info/d' \
| cat .pdf/skills.txt - > build/skills.meta
.pdf.skills.txt
:
InfoBegin
InfoKey: Creator
InfoValue: https://ac93.dev
InfoBegin
InfoKey: Producer
InfoValue: Alistair Collins - with Graphviz
InfoBegin
InfoKey: Title
InfoValue: Alistair Collins - Skills
InfoBegin
InfoKey: Subject
InfoValue: Skills
InfoBegin
InfoKey: Author
InfoValue: Alistair Collins
Update Metadata
Just update the document’s metadata to be that which we dumped previously in to the build/skills.meta
file.
You may need to move/copy the file since this update cannot be done ‘in-place’ (I append the .tmp
suffix to the file after dumping the metadata).
# Reusable docker run command
docker_run = docker run --rm \
$(shell tty -s && echo "-i" || echo) \
-v "$(shell pwd):/app" \
-w "/app"
set_meta_skills_pdf:
${docker_run} mnuessler/pdftk build/skills.pdf.tmp \
update_info_utf8 build/skills.meta \
output build/skills.pdf
Before | After |
---|---|
How it could look after updating the metadata after concatenating the documents:
Local Development
If this isn’t overkill enough then strap in. I primarily develop on Mac, so I use pm2
and fswatch
to rebuild pre-requisites when things are changed.
brew install fswatch
npm i -g pm2
The ecosystem.config.js
will watch:
Dockerfile
tomake skills_docker
- i.e. rebuild docker container that is used to build skills tree
skills.tmpl
,skills.sh
tomake skills_dot skills_png skills_svg skills_pdf
- i.e. the
dot
file used to build the skills tree
- i.e. the
.env
,src/
tomake build
- i.e. to make the CV and combines CV and skill into a single document
// pm2 start ecosystem.config.js
// pm2 stop ecosystem.config.js
// pm2 status all
module.exports = {
apps: [
{
name: "resume-docker",
script: "fswatch --one-event --event=Updated Dockerfile && make skills_docker",
},
{
name: "resume-skills",
script: "fswatch --one-event --event=Updated skills.sh skills.tmpl && make skills_dot skills_png skills_svg skills_pdf",
},
{
name: "resume-cv",
script: "fswatch --one-event --event=Updated .env src/ && make build",
}
]
};
CI/CD
A workflow is configured to run when a tag
is pushed.
I use this command below to see the most recent tags, and to see which will be the next logical release.
git --no-pager tag | grep -v "^v" | sort -V
Once I’m happy with the local build, I will tag
and push
:
git tag 1.2.0 -m 'good enough'
git push origin 1.2.0
The workflow will build all these assets, and included they are then included in a new release. Then a certain subset of these assets are uploaded to an s3
bucket for use on my website and github profile.
Pretty cool (I think) 😎.
Next step… dark mode CV…
Samples
- one page: Skills
- one page: CV
- two pages: CV & Skills