feat: created CV using ConTeXt, Go and JSON

- ConTeXt is used for the CV template.
- CV data is stored in a JSON document.
- A small Go script is used to parse the JSON data and render
  the final cv.tex file.
- ConTeXt is then used to render the CV in PDF (other formats to be
  supported soon).
This commit is contained in:
Dan Anglin 2019-07-30 23:52:56 +01:00
parent 044cba4d39
commit a7b1d7d2cd
No known key found for this signature in database
GPG key ID: 7AC2B18EC1D09F27
8 changed files with 444 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
__output/
tags
notes.txt

14
Makefile Normal file
View file

@ -0,0 +1,14 @@
OUTPUT_DIR = __output/
.PHONY: all tex pdf clean
all: pdf
tex:
@go run .
pdf: tex
@mtxrun --path=$(OUTPUT_DIR) --script context cv.tex
clean:
@rm -rf $(OUTPUT_DIR)

174
data/cv.json Normal file
View file

@ -0,0 +1,174 @@
{
"firstName": "Dan",
"lastName": "Anglin",
"jobTitle": "Software Engineer",
"contact": {
"email": "d.n.i.anglin@gmail.com",
"phone": "07XXX-XXX-XXX",
"location": "Hertfordshire, UK"
},
"links": {
"gitlab": "gitlab.com/dananglin",
"github": "github.com/dananglin"
},
"summary": "To be completed.",
"technologies": [
{
"category": "Programming Languages",
"values": [
"Go",
"Bash",
"Python"
]
},
{
"category": "Containerisation",
"values": [
"Docker",
"Kubernetes",
"LXD"
]
},
{
"category": "Continuous Integration",
"values": [
"Git",
"GitLab",
"Gitea",
"GitLab CI",
"Drone",
"Nexus",
"Jenkins"
]
},
{
"category": "Monitoring and Alerting",
"values": [
"Prometheus",
"Alertmanager",
"Grafana"
]
},
{
"category": "Other Tools, Services or Frameworks",
"values": [
"AWS",
"Openstack",
"UKCloud",
"GNU/Linux",
"Elasticsearch",
"Logstash",
"Fluentd",
"Kibana",
"Ansible",
"Puppet",
"OpenAPI v3",
"PostgreSQL",
"HAProxy",
"Kanban",
"Scrum",
"JIRA"
]
}
],
"employment": [
{
"company": "Ocado Technology",
"location": "Hatfield, Hertfordshire",
"jobTitle": "Platform Automation Engineer",
"duration": {
"start": "January, 2017",
"end": "Present"
},
"details": [
"To be completed..."
]
},
{
"company": "QA Consulting (Contracted to CACI)",
"location": "London",
"jobTitle": "DevOps Consultant",
"duration": {
"start": "March, 2015",
"end": "November, 2016"
},
"details": [
"I've built and supported Production infrastructure hosted in UKCloud (for public sector projects) and AWS (for commercial projects).",
"My general tasks included creating Ansible playbooks for network configuration, hardening of RedHat/CentOS VMs and software installation and configuration.",
"I've set up resilient and fault-tolerant infrastructure using tools such as HAProxy and Keepalive.",
"I've built a cross-domain protective monitoring solution using the Elastic stack with Redis.",
"I've installed and configured Nagios/Icinga for infrastructure monitoring.",
"I was involved in setting up the production Docker environment for a government website."
]
},
{
"company": "QA Consulting (Contracted to IBM)",
"location": "Home Office, London",
"jobTitle": "DevOps Consultant",
"duration": {
"start": "October, 2014",
"end": "March, 2015 "
},
"details": [
"I've built and supported new development, CIT and SIT environments for new projects at the Home Office.",
"These environments were built on UKCloud using Jenkins and UKCloud's REST API.",
"Linux (RedHat/CentOS) virtual machines were provisioned and hardenend using Puppet and Jenkins.",
"I learnt how to work in an Agile environment with SCRUM; using JIRA for keeping track of my Sprint tasks.",
"Confluence was used for internal documentation and I used it to create documentation for new starters in the DevOps space."
]
},
{
"company": "QA Consulting",
"location": "Worthing",
"jobTitle": "Trainee DevOps Consultant",
"duration": {
"start": "September, 2014",
"end": "October, 2014"
},
"details": [
"I've spent eight weeks in training to become a DevOps Consultant on behalf of QA Consulting.",
"I've completed a two week intensive training course on business analysis. Trainig course covered client engagement, businnes process modelling, use case modelling and process and evaluation improvements.",
"I then completed a six week hands-on training course on the fundamentals of DevOps and Continuous Integration.",
"The DevOps training course consisted of learning about various software such as Puppet, Jenkins, Nexus, Maven, Git, Tomcat and the Atlassian tools.",
"The training also included using Vagrant and Puppet to automate the provisioning of small CI and development environments for the continuous delivery of internal Java projects."
]
},
{
"company": "School of Systems Engineering, University of Reading",
"location": "Reading",
"jobTitle": "Undergraduate Researcher",
"duration": {
"start": "July, 2012",
"end": "September, 2012"
},
"details": [
"This was a summer internship where I helped developed an interactive Java application that processes Terahertz signals in order to reveal hidden details behind walls.",
"This was aimed at those who were not specialised in Terahertz signals to use an application to aid in their research in archaeology.",
"The hidden details were captured in images or videos that could be viewed in a 2D or a 3D virtual environment.",
"During this internship I learnt new techniques for processing Terahertz signals.",
"I was able to use sample Terahertz data to reveal and display hidden details within a 3D virtual environment."
]
}
],
"education": [
{
"school": "University of Reading",
"location": "Reading",
"qualification": "MEng (Hons) in Robotics, 2:1",
"duration": {
"start": "October, 2009",
"end": "July, 2013"
}
},
{
"school": "St Angela's & St Bonaventure's Sixth Form College",
"location": "London",
"qualification": "A-Levels in Maths, Physics and Product Design",
"duration": {
"start": "September, 2007",
"end": "July, 2009"
}
}
],
"interests": "To be completed..."
}

3
go.mod Normal file
View file

@ -0,0 +1,3 @@
module gitlab.com/dananglin/cv
go 1.12

67
main.go Normal file
View file

@ -0,0 +1,67 @@
package main
import (
"encoding/json"
"io/ioutil"
"log"
"os"
"text/template"
)
var (
cvDataPath string = "data/cv.json"
cvTemplateDir string = "template/"
cvOutputDir string = "__output/"
cvOutputFileName string = "cv.tex"
)
func main() {
var cv Cv
cvOutputPath := cvOutputDir + cvOutputFileName
fmap := template.FuncMap{
"notLastElement": notLastElement,
}
// Read the JSON data
log.Printf("INFO: Attempting to read data from %s...", cvDataPath)
data, err := ioutil.ReadFile(cvDataPath)
if err != nil {
log.Fatalf("ERROR: Unable to read data from file. %s", err.Error())
}
log.Print("INFO: Successfully read data.")
log.Printf("INFO: Attempting to unmarshal JSON data...")
if err = json.Unmarshal(data, &cv); err != nil {
log.Fatalf("ERROR: Unable to unmarshal JSON data. %s", err.Error())
}
log.Println("INFO: JSON unmarshalling was successful.")
// Create the Output tex file
log.Printf("INFO: Attempting to create output file %s...", cvOutputPath)
if err = os.MkdirAll(cvOutputDir, 0750); err != nil {
log.Fatalf("ERROR: Unable to create output directory %s. %s", cvOutputDir, err.Error())
}
output, err := os.Create(cvOutputPath)
if err != nil {
log.Fatalf("ERROR: Unable to create output file %s. %s.", cvOutputPath, err.Error())
}
defer output.Close()
log.Printf("INFO: Successfully created output file %s.", cvOutputPath)
// Execute template engine and produce the resulting TEX file
log.Println("INFO: Attempting template execution...")
t := template.Must(template.New("cv.tex.tmpl").Funcs(fmap).Delims("<<", ">>").ParseGlob(cvTemplateDir + "*.tex.tmpl"))
if err = t.Execute(output, cv); err != nil {
log.Fatalf("ERROR: Unable to execute the CV template. %s", err.Error())
}
log.Println("INFO: Template execution successful.")
}
// notLastElement returns true if an element of an array
// or a slice is not the last.
func notLastElement(pos, length int) bool {
return pos < length-1
}

46
model.go Normal file
View file

@ -0,0 +1,46 @@
package main
type Cv struct {
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
JobTitle string `json:"jobTitle"`
Contact Contact `json:"contact"`
Links Links `json:"links"`
Summary string `json:"summary"`
Technologies []Technologies `json:"technologies"`
Employment []Experience `json:"employment"`
Education []Experience `json:"education"`
Interests string `json:"interests"`
}
type Contact struct {
Email string `json:"email"`
Phone string `json:"phone"`
Location string `json:"location"`
}
type Links struct {
GitLab string `json:"gitlab"`
GitHub string `json:"github"`
Website string `json:"website,omitempty"`
}
type Technologies struct {
Category string `json:"category"`
Values []string `json:"values"`
}
type Experience struct {
Company string `json:"company,omitempty"`
School string `json:"school,omitempty"`
Location string `json:"location"`
JobTitle string `json:"jobTitle,omitempty"`
Qualification string `json:"qualification,omitempty"`
Duration Duration `json:"duration"`
Details []string `json:"details"`
}
type Duration struct {
Start string `json:"start"`
End string `json:"end"`
}

53
template/cv.tex.tmpl Normal file
View file

@ -0,0 +1,53 @@
<<- /* Prepend the setup area */ ->>
<< template "cv_setup.tex.tmpl" .>>
\starttext
\starttitleAndContact
\cvtitle{<<.FirstName>> <<.LastName>>}{<<.JobTitle>>}
\titleAndContact
{\bf Email:} <<.Contact.Email>>\blank[none]
{\bf Phone:} <<.Contact.Phone>>\blank[none]
{\bf Location:} <<.Contact.Location>>\blank[medium]
{\bf GitLab:} \goto{<<.Links.GitLab>>}[url(https://<<.Links.GitLab>>)]\blank[none]
{\bf GitHub:} \goto{<<.Links.GitHub>>}[url(https://<<.Links.GitHub>>)]\blank[none]
\stoptitleAndContact
\section{SUMMARY}
<<.Summary>>
\section{SKILLS SUMMARY}
\starttabulate[|w(0.3\textwidth)lB|lp(0.7\textwidth)|]
<<$lenTech := len .Technologies>>
<<range $i, $tech := .Technologies>>
<<$lenValues := len $tech.Values>>
\NC <<$tech.Category>> \NC <<range $j, $val := $tech.Values>><<$val>><<if notLastElement $j $lenValues>>, <<end>><<end>>\NC\NR
<<if notLastElement $i $lenTech>>\TB[1mm]<<end>>
<<end>>
\stoptabulate
\section{EXPERIENCE}
<<- range .Employment>>
\jobsection{<<.Company>>}{<<.Location>>}{<<.JobTitle>>}{<<.Duration.Start>> - <<.Duration.End>>}
\startitemize
<<range .Details>>
\item <<.>>
<<end>>
\stopitemize
<<end>>
\section{EDUCATION}
<<range .Education ->>
\jobsection{<<.School>>}{<<.Location>>}{<<.Qualification>>}{<<.Duration.Start>> - <<.Duration.End>>}
\startitemize
<<range .Details>>
\item <<.>>
<<end>>
\stopitemize
<<end>>
\section{OTHER INTERESTS}
<<.Interests>>
\section{REFERENCES}
References are available upon request.
\stoptext

View file

@ -0,0 +1,84 @@
\version [final]
% set main language to English
\mainlanguage [en]
% set paper size to A4
\setuppapersize [A4]
% define the page layout
\setuplayout
[backspace=20mm,
bottomspace=20mm,
cutspace=0mm,
footer=0mm,
header=0mm,
height=middle,
topspace=10mm,
width=middle]
% \showframe
% remove page numbers by setting an empty location
\setuppagenumbering[location=]
% Colour definitions for headings
\definecolor [bluetheme][h=1155cc]
\definecolor [greytheme][h=434343]
% Font setup
% https://wiki.contextgarden.net/Simplefonts
\definefontfamily [cvfontfamily][rm][LiberationSerif]
\definefontfamily [cvfontfamily][ss][Carlito]
\definefontfamily [cvfontfamily][tt][LiberationMono]
\definefontfamily [cvfontfamily][mm][LibertinusMath]
\setupbodyfont [cvfontfamily,ss,10pt]
% define the font scaling when using commands such as \tfa, \tfb, etc
\definebodyfontenvironment [10pt][a=11pt,b=20pt,c=32pt]
% cvtitle outputs the title of the CV
% arguments:
% #1 - name: name of the individual
% #2 - role: job role of the individual
\define[2]\cvtitle{
{\bfc#1}\blank[small]{\tfb#2}
}
% jobsection outputs the summary of a work experience
% arguments:
% #1 - company: the company where the work experience took place
% #2 - location: the location of the work experience
% #3 - role: the role of the work experience
% #4 - duration: the duration of the work experience (e.g. March, 2017 - April, 2018)
\define[4]\jobsection{
{\bfa#1,} {\tfa#2} — {\tfa\em#3}\blank[small]\color[greytheme]{#4}\blank[line]
}
\definehead [skillssection][subsection]
\setuphead [section][color=bluetheme]
\setuphead [skillssection][color=black]
\setuphead [section, skillssection][number=no, align=right, style=\bfa]
% setup the format of items (bullet points)
\setupitemize
[1]
[packed]
[align=right,
symbol=1,
margin=no,
distance=1mm]
% defineparagraphs and setupparagraphs allows us to create custom
% columns. Here we define and configure a paragraph environment for the CV title
% and contact columns.
% https://wiki.contextgarden.net/Command/defineparagraphs
% https://wiki.contextgarden.net/Command/setupparagraphs
\defineparagraphs[titleAndContact][n=2,after={\blank}]
\setupparagraphs [titleAndContact][1][width=0.6\textwidth]
\setupparagraphs [titleAndContact][2][width=0.4\textwidth]
% use Interaction to create hyperlinks to external profiles
\setupinteraction
[state=start,
color=bluetheme,
click=]