chore: remove CV builder code from repository

The code is now managed in the cv-builder repository in apollo.
This commit is contained in:
Dan Anglin 2023-02-14 08:21:46 +00:00
parent 95cf8dbcd6
commit 86215c2e57
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
14 changed files with 0 additions and 621 deletions

View file

@ -1,5 +0,0 @@
*
!magefile.go
!helpers/*
!go.mod
!go.sum

View file

@ -1,2 +0,0 @@
ignored:
- DL3015

21
LICENSE
View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2019 Dan Anglin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,96 +0,0 @@
# My CV Project
## Table of Contents
- [Overview](#overview)
- [Dependencies](#dependencies)
- [Using Docker as an alternative](#using-docker-as-an-alternative)
- [Generating the PDF Document](#generating-the-pdf-document)
- [Inspirations](#inspirations)
## Overview
This project parses my [CV](./data/cv.json) that is written as
a JSON document and generates a PDF document.
This project contains a Go application and uses the ConTeXt document processor
which are used to parse and generate a PDF document from the CV.
I chose to use ConTeXt as the document processor because it gives me more
control to edit page layouts, configure fonts,
perform additional formatting and add custom functionality.
Support for other formats such as HTML will be available soon.
### View/Download the CV
The latest build of the generated PDF document can be downloaded from the [release](https://gitlab.com/dananglin/cv/-/releases) page of the project in GitLab.
## Dependencies
If you are interested in generating the PDF document on your own machines
or want to use the project to generate your own CV, then below is a list
of dependencies that you'll need to install:
- **Go** - Please go [here](https://golang.org/dl/) to download the latest version of the Go programming language.
- **ConTeXt** - I recommend installing version 1.02 or higher. Please go [here](https://wiki.contextgarden.net/ConTeXt_Standalone) for installation instructions for your distribution.
- **The Carlito font (ttf-carlito)** - In prevous iterations of my CV I used the Calibri font. Carlito is the free, metric compatible alternative to this and is specified in the TEX template.
- For Ubuntu/Debian installation you can use `apt`:
```bash
$ apt-get install font-crosextra-carlito
```
- For Arch Linux you can use `pacman`:
```bash
$ pacman -S ttf-carlito
```
- Alternatively you can download the font from https://fontlibrary.org/en/font/carlito
- Once this font is installed you'll need to update ConTeXt so it can find the font when generating the PDF:
```bash
$ OSFONTDIR=/usr/share/fonts
$ mtxrun --script fonts --reload
```
Once the dependencies are installed you can follow the
[Generating the PDF Document](#generating-the-pdf-document) section below.
## Using Docker as an alternative
If you prefer not to install the dependencies above,
I have created a Docker image installed with the above dependencies.
This image is used to build and publish the CV via the GitLab CI pipleines.
The image is built using this [Dockerfile](./docker/Dockerfile) and is
pullable from [GitLab's container registry](https://gitlab.com/dananglin/cv/container_registry).
To use the image follow the steps below:
1. Make sure you've clone this project to your workspace:
```bash
$ git clone https://gitlab.com/dananglin/cv.git
$ cd cv
```
2. Create the docker container and mount the current directory to the /project directory inside the container:
```bash
$ docker run --rm -it -v ${PWD}:/project registry.gitlab.com/dananglin/cv/cv-builder:master-44f2a31c bash
# Once inside the docker container
$ cd /project
```
3. Follow the [Generating the PDF Document](#generating-the-pdf-document) section below.
## Generating the PDF Document
The PDF document can be generated by running the following command:
```bash
$ go run mage.go pdf
```
The PDF generation is completed in two steps:
1. The Go application will generate a TEX file using the JSON document and the TEX template files located in the [template](./template) directory.
2. ConTeXt is then used to generate the PDF document from the generated TEX file.
## Inspirations
- [The Markdown Resume](https://mszep.github.io/pandoc_resume/) - This project uses ConTeXt and pandoc to convert Markdown based CVs into multiple formats including PDF, HTML and DOCX. This is where I discovered ConTeXt.
- [melkir/resume](https://github.com/melkir/resume) - This project generates CVs using Go and LaTeX.

View file

@ -1,47 +0,0 @@
FROM golang:1.19-buster AS builder
RUN git clone https://github.com/magefile/mage "${GOPATH}/src/mage"
WORKDIR ${GOPATH}/src/mage
RUN go run bootstrap.go
COPY go.mod ${GOPATH}/cv-builder/
COPY go.sum ${GOPATH}/cv-builder/
COPY magefile.go ${GOPATH}/cv-builder/
COPY helpers ${GOPATH}/cv-builder/helpers
WORKDIR ${GOPATH}/cv-builder
RUN mage -compile /usr/local/bin/cv-make
FROM debian:buster
COPY --from=builder /usr/local/bin/cv-make /usr/local/bin
# Install dependencies
RUN \
apt-get update \
&& apt-get install -y \
fonts-crosextra-carlito \
rsync \
curl= \
aspell \
aspell-en \
&& apt-get clean autoclean \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* /tmp/* \
&& mkdir /opt/context
WORKDIR /opt/context
# Install ConTeXt standalone
RUN \
curl -LO http://minimals.contextgarden.net/setup/first-setup.sh \
&& sh first-setup.sh --context=current --engine=luatex \
&& rm -rf /opt/context/tex/texmf-context/doc
ENV PATH=${PATH}:/opt/context/tex/texmf-linux-64/bin \
OSFONTDIR=/usr/share/fonts
RUN mtxrun --script fonts --reload

5
go.mod
View file

@ -1,5 +0,0 @@
module gitlab.com/dananglin/cv
go 1.19
require github.com/magefile/mage v1.14.0

2
go.sum
View file

@ -1,2 +0,0 @@
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=

View file

@ -1,53 +0,0 @@
//go:build mage
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 {
LinkedIn string `json:"linkedin"`
GitHub string `json:"github"`
}
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 Date `json:"start"`
End Date `json:"end"`
Present string `json:"present"`
}
type Date struct {
Year string `json:"year"`
Month string `json:"month"`
}

View file

@ -1,145 +0,0 @@
//go:build mage
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"strings"
"text/template"
)
const (
aspellLang string = "en_GB"
defaultImageName string = "cv-builder"
defaultImageTag string = "latest"
)
// imageName generates the docker image name from the environment
// variables IMAGE_NAME and IMAGE_TAG. Default vaules will be used
// if these variables are not set or empty.
func imageName() string {
name := os.Getenv("IMAGE_NAME")
if len(name) == 0 {
name = defaultImageName
}
tag := os.Getenv("IMAGE_TAG")
if len(tag) == 0 {
tag = defaultImageTag
}
return name + ":" + tag
}
// createCVTex generates the CV docuemnt as a Tex file.
func createCVTex(cvJsonDataFile, cvTemplateDir, cvOutputDir, cvOutputFileName string) error {
fmt.Printf("INFO: Attempting to read data from %s...\n", cvJsonDataFile)
data, err := os.ReadFile(cvJsonDataFile)
if err != nil {
return fmt.Errorf("unable to read data from file. %w", err)
}
fmt.Println("INFO: Successfully read data.")
fmt.Println("INFO: Attempting to unmarshal JSON data...")
var cv Cv
if err = json.Unmarshal(data, &cv); err != nil {
return fmt.Errorf("unable to unmarshal JSON data; %w", err)
}
fmt.Println("INFO: JSON unmarshalling was successful.")
// if CV_CONTACT_PHONE is set then add it to the CV
phone := os.Getenv("CV_CONTACT_PHONE")
if len(phone) > 0 {
cv.Contact.Phone = phone
}
cvOutputPath := cvOutputDir + "/" + cvOutputFileName
fmt.Printf("INFO: Attempting to create output file %s...\n", cvOutputPath)
if err = os.MkdirAll(cvOutputDir, 0750); err != nil {
return fmt.Errorf("unable to create output directory %s; %w", cvOutputDir, err)
}
output, err := os.Create(cvOutputPath)
if err != nil {
return fmt.Errorf("unable to create output file %s; %w", cvOutputPath, err)
}
fmt.Printf("INFO: Successfully created output file %s.\n", cvOutputPath)
defer output.Close()
fmt.Println("INFO: Attempting template execution...")
fmap := template.FuncMap{
"notLastElement": notLastElement,
"join": join,
"durationToString": durationToString,
}
t := template.Must(template.New("cv.tmpl.tex").Funcs(fmap).Delims("<<", ">>").ParseGlob(cvTemplateDir + "*.tmpl.tex"))
if err = t.Execute(output, cv); err != nil {
return fmt.Errorf("unable to execute the CV template. %w", err)
}
fmt.Println("INFO: Template execution successful.")
return nil
}
func spellCheck(dataFile, aspellPersonalWordlist string) error {
fmt.Printf("INFO: Reading data from %s...\n", dataFile)
data, err := os.ReadFile(dataFile)
if err != nil {
return fmt.Errorf("unable to read data from %s, %s", dataFile, err.Error())
}
// declare the aspell command and its standard input pipe.
cmd := exec.Command("aspell", "-d", aspellLang, "-p", aspellPersonalWordlist, "list")
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
// write the CV data to standard input for piping.
go func() {
defer stdin.Close()
io.WriteString(stdin, string(data))
}()
// run aspell and get the list of mispelt words (if any).
// (the output is a string.)
fmt.Println("Running aspell...")
out, err := cmd.CombinedOutput()
if err != nil {
return err
}
list := strings.Split(string(out), "\n")
if list[len(list)-1] == "" {
list = list[:len(list)-1]
}
if len(list) > 0 {
var b strings.Builder
errMsg := fmt.Sprintf("the following spelling errors were found in %s:", dataFile)
b.WriteString(errMsg)
for _, v := range list {
s := "\n- " + v
b.WriteString(s)
}
return fmt.Errorf(b.String())
} else {
fmt.Println("No spelling errors were found.")
}
return nil
}

View file

@ -1,77 +0,0 @@
//go:build mage
package main
import (
"fmt"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)
const (
aspellPersonalWordlist string = "./.aspell/.aspell.en.pws"
cvJsonDataFile string = "./data/cv.json"
cvOutputDir string = "./__output"
cvTemplateDir string = "./template/tex/"
cvOutputFileName string = "cv.tex"
dockerfile string = "./docker/Dockerfile"
)
// Default sets the default target.
var Default = Pdf
// SpellCheck checks the CV JSON file for spelling errors. If there are any
// misspelt words found the function returns a list of those words in an error.
// Any error found while gathering the list is returned.
// This function depends on the aspell program.
func SpellCheck() error {
return spellCheck(cvJsonDataFile, aspellPersonalWordlist)
}
// Tex takes the CV data file and generates the output tex file from
// template.
func Tex() error {
fmt.Println("Generating the tex file...")
mg.Deps(Clean)
return createCVTex(cvJsonDataFile, cvTemplateDir, cvOutputDir, cvOutputFileName)
}
// Pdf takes the output tex file generated by the Tex target and
// generates the output PDF file.
func Pdf() error {
mg.Deps(Tex)
pathArg := "--path=" + cvOutputDir
fmt.Println("Generating the PDF file...")
return sh.Run("mtxrun", pathArg, "--script", "context", "cv.tex")
}
// Image builds the CV builder docker image.
func Image() error {
image := imageName()
return sh.Run("docker", "build", "-f", dockerfile, "-t", image, ".")
}
// PublishImage publishes the CV builder docker image
// to the docker registry.
func PublishImage() error {
mg.Deps(Image)
image := imageName()
return sh.Run("docker", "push", image)
}
// Clean removes the directory where the output files
// are generated.
func Clean() error {
if err := sh.Rm(cvOutputDir); err != nil {
return err
}
return nil
}

View file

@ -1,36 +0,0 @@
//go:build mage
package main
import (
"fmt"
"strings"
)
// 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
}
// join uses strings.Join to join all string elements into
// a single string.
func join(s []string) string {
return strings.Join(s, " ")
}
// durationToString outputs the employment/education
// duration as a formatted string.
func durationToString(d Duration) string {
start := fmt.Sprintf("%s, %s", d.Start.Month, d.Start.Year)
present := strings.ToLower(d.Present)
end := ""
if present == "yes" || present == "true" {
end = "Present"
} else {
end = fmt.Sprintf("%s, %s", d.End.Month, d.End.Year)
}
return start + " - " + end
}

View file

@ -1,54 +0,0 @@
<<- /* Prepend the setup area */ ->>
<< template "cv_setup.tmpl.tex" .>>
\starttext
\starttitleAndContact
\cvtitle{<<.FirstName>> <<.LastName>>}{<<.JobTitle>>}
\titleAndContact
{\bf Email:} <<.Contact.Email>>\blank[none]
<<if .Contact.Phone>>{\bf Phone:} <<.Contact.Phone>>\blank[none]<<end>>
{\bf Location:} <<.Contact.Location>>\blank[medium]
<<if .Links.LinkedIn>>{\bf LinkedIn:} <<.Links.LinkedIn>>\blank[none]<<end>>
<<if .Links.GitHub>>{\bf GitHub:} <<.Links.GitHub>>\blank[none]<<end>>
\stoptitleAndContact
\section{SUMMARY}
<<join .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>>}{<<durationToString .Duration>>}
\startitemize
<<range .Details>>
\item <<.>>
<<end>>
\stopitemize
<<end>>
\section{EDUCATION}
<<range .Education ->>
\jobsection{<<.School>>}{<<.Location>>}{<<.Qualification>>}{<<durationToString .Duration>>}
\startitemize
<<range .Details>>
\item <<.>>
<<end>>
\stopitemize
<<end>>
\section{INTERESTS AND HOBBIES}
<<join .Interests>>
\section{REFERENCES}
References are available upon request.
\stoptext

View file

@ -1,78 +0,0 @@
\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.5\textwidth]
\setupparagraphs [titleAndContact][2][width=0.5\textwidth]