Gestion de mon CV
Pour la gestion de mon CV, je me suis fixé les critères suivants :
- il doit être simple à lire et à écrire
- je dois pouvoir le convertir en PDF et en HTML simplement
Le tout en centralisant les informations, pour éviter des incohérences entre versions.
J’ai donc séparé mon CV en plusieurs fichiers :
- deux en-têtes (
header-pdf.mdetheader-blog.md) - deux fichiers d’informations (
infos.mdetinfos-min.md) - le corps du CV (
cv.md)
Avec trois sorties :
cv.pdf: mon CV completcv-min.pdf: mon CV public (sans photo, adresse, numéro de téléphone)$(BLOG)/cv.md: une version compatible Goldmark1, sans informations, qui se retrouve ici
Les en-têtes
Ils contiennent toutes les métadonnées nécessaires à chaque sortie. J’y ai également rajouté la première ligne du corps de chaque version, puisqu’elle n’est pas commune.
header-blog.md
1---
2title: 'Curriculum Vitæ'
3menu: main
4weight: 10
5---
6
7[[Version PDF](cv.pdf)] : *aucune information personnelle n'y est présente, vous pouvez [me contacter](../contact/) pour un CV complet.*
8
9***
Ce fichier contient le nécessaire pour une page avec Hugo, ainsi qu’une mention quant à la version PDF.
header-pdf.md
1---
2author-meta: Justin Bossard
3title-meta: Curriculum Vitæ de Justin Bossard
4date-meta: 20250911
5keywords:
6- CV
7- ingénieur
8- informatique
9- étudiant
10papersize: a4
11fontfamily: FiraMono
12fontsize: 10pt
13geometry: margin=6.5mm
14pagestyle: empty
15urlcolor: blue
16lang: fr-FR
17header-includes:
18- \renewcommand*\familydefault{\ttdefault}
19- \usepackage[fixed]{fontawesome5}
20- \usepackage{titlesec}
21- \titleformat{\subsection}[block]{\filcenter\normalfont\large\bfseries}{}{0pt}{}
22---
23
24# \centering Justin Bossard
- Les options
*-metaetkeywordscontiennent les métadonnées du document - Les autres options sont pour la mise en forme
\renewcommand*\familydefault{\ttdefault}sert à utiliser la variantetypewriterdeFiraMono(sinon on a la versionserif)
Les informations
Je ne veux pas rendre publiques certaines données personnelles. Le reste des informations est commun aux deux versions.
Ainsi, éditer à la main infos.md et infos-min.md si, par exemple, je change de courriel, n’est pas très élégant. J’ai donc opté pour la solution suivante :
un fichier
infos.yaml, regroupant toutes mes informations1photo: 2 path: ./photo.png 3 height: 115px 4birthday: 2003-07-08 5license: Permis B, véhiculé 6address: 7 street: 15 rue Margemarkdo 8 city: 93006 Bagnolet 9 country: France 10phone: 11 country: US 12 number: 3353656565 13mail: jbossard02@gmail.com 14website: realnitsuj.github.io 15github: realnitsuj 16linkedin: in/justin-bossardun script
gen-infos.pyqui lit et traite le premier fichier, pour générer deux fichiers de sorties via un template Jinja21#!/usr/bin/env python3 2 3import yaml 4import phonenumbers 5from phonenumbers import PhoneNumberFormat 6import locale 7from datetime import datetime, date 8from jinja2 import Environment, FileSystemLoader 9 10locale.setlocale(locale.LC_TIME, "fr_FR.UTF-8") 11 12env = Environment(loader=FileSystemLoader(".")) 13template = env.get_template("infos-template.md") 14 15## Charger le YAML ############################################################# 16with open("infos.yaml") as f: 17 data = yaml.safe_load(f) 18 19## Traiter les données ######################################################### 20 21bday = data["birthday"] 22today = date.today() 23data["birthday"] = bday.strftime("%-d %B %Y") 24data["age"] = today.year - bday.year - ((today.month, today.day) < (bday.month, bday.day)) 25 26parsed = phonenumbers.parse(str(data["phone"]["number"]), data["phone"]["country"]) 27formatted = phonenumbers.format_number(parsed, PhoneNumberFormat.INTERNATIONAL) 28e164 = phonenumbers.format_number(parsed, PhoneNumberFormat.E164) 29data["phone"] = f"[{formatted}](tel:{e164})" 30 31## Écrire résultat ############################################################# 32with open("infos.md", "w") as f: 33 f.write(template.render(**data, mode="full")) 34 35with open("infos-min.md", "w") as f: 36 f.write(template.render(**data, mode="min"))C’est plutôt explicite, on charge les données du YAML, puis on traite l’anniversaire et le numéro de téléphone, avant d’écrire les deux fichiers.
data["birthday"]est bien traité comme une date par PyYAML, pas besoin de le convertir explicitement.le template
infos-template.md1{% if mode == "min" %} 2> *Ceci est une version sans informations trop personnelles, n'hésitez pas à me contacter pour une version complète.* 3{% endif %} 4:::: three-columns 5{% if mode == "full" %} 6```{=latex} 7\begin{center} 8``` 9 10{height={{photo.height}}} 11 12```{=latex} 13\end{center} 14``` 15{% endif %} 16\faIcon{birthday-cake} {{birthday}} ({{age}} ans) 17 18\faIcon{car} {{license}} 19{% if mode == "full" %} 20\faIcon{map-marker-alt} {{address.street}}\newline 21\hspace*{1.5em} {{address.city}}\newline 22\hspace*{1.5em} {{address.country}} 23 24\faIcon{phone-alt} {{phone}} 25{% endif %} 26\faIcon{envelope} <{{mail}}> 27 28\faIcon{globe} [{{website}}](https://{{website}}) 29 30\faIcon{github} [{{github}}](https://github.com/{{github}}) 31 32\faIcon{linkedin} [{{linkedin}}](https://www.linkedin.com/{{linkedin}}) 33 34::::
Le corps du CV
Le reste est dans cv.md.
J’utilise FontAwesome avec LaTeX (e.g. \faIcon{globe}) pour avoir de jolies icônes en PDF. Je réfléchis à une solution pour les intégrer aussi en HTML avec un filtre Lua et cet article.
J’utilise columns.lua pour les doubles colonnes2, le reste est écrit en Markdown (avec beaucoup de listes de définitions).
Makefile
Une fois qu’on a tout ça, on fait un Makefile, avec toutes les recettes qu’il faut, en utilisant Pandoc :
1.PHONY: all
2
3PANDOC=pandoc
4PYTHON=python
5
6BLOG=~/website/content/cv
7OUTPUT=./output
8
9all: $(OUTPUT)/cv.pdf $(OUTPUT)/cv-min.pdf $(BLOG)/index.md
10
11$(OUTPUT)/cv.pdf: cv.md header-pdf.md infos.md
12 $(PANDOC) --lua-filter columns.lua header-pdf.md infos.md cv.md -o $@
13
14$(OUTPUT)/cv-min.pdf: cv.md header-pdf.md infos-min.md
15 $(PANDOC) --lua-filter columns.lua header-pdf.md infos-min.md cv.md -o $@
16 cp $(OUTPUT)/cv-min.pdf $(BLOG)/cv.pdf
17
18$(BLOG)/index.md: cv.md header-blog.md
19 $(PANDOC) --standalone --lua-filter columns.lua header-blog.md cv.md -o $@ -t markdown_strict+definition_lists+yaml_metadata_block
20
21infos.md infos-min.md: gen-infos.py infos.yaml
22 $(PYTHON) gen-infos.py
Pour la conversion en $(BLOG)/cv.md, j’utilise le format markdown_strict, avec en extension les listes de définitions et les en-têtes YAML, tous deux compatibles avec la convention Goldmark.
Conclusion
Et voilà ! Ce système me permet d’avoir un CV centralisé et exportable dans différents formats, sans duplication d’informations.