From 1fd045a8279f5ccb36d766306e0e3cb25193945a Mon Sep 17 00:00:00 2001 From: Theo-Hafsaoui Date: Sat, 31 Aug 2024 13:58:35 +0200 Subject: [PATCH] WIP --- .gitignore | 1 + Makefile | 3 ++ assets/latex/template/template.tex | 3 +- cmd/generate.go | 53 ++++++++++++++++++++++++++++ cmd/root.go | 19 ++++++++++ cv/eng/Professional.md | 20 +++++++++++ cv/eng/education.md | 12 ++++--- cv/eng/project.md | 13 ++++--- cv/eng/work.md | 20 ----------- cv/fr/{work.md => Professional.md} | 0 go.mod | 8 ++++- go.sum | 12 +++++++ internal/parser/latex.go | 46 +++++++++++++++++------- internal/parser/latex_test.go | 11 +++--- internal/parser/markdown.go | 8 ++++- internal/parser/template_sections.go | 12 +++---- internal/walker/cv.go | 18 +++++++--- internal/walker/cv_test.go | 38 +++++++++++++------- main.go | 9 +++++ 19 files changed, 231 insertions(+), 75 deletions(-) create mode 100644 cmd/generate.go create mode 100644 cmd/root.go create mode 100644 cv/eng/Professional.md delete mode 100644 cv/eng/work.md rename cv/fr/{work.md => Professional.md} (100%) create mode 100644 main.go diff --git a/.gitignore b/.gitignore index f40e964..4ba4eb9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Binaries for programs and plugins +anemon *.exe *.exe~ *.dll diff --git a/Makefile b/Makefile index 1b8c007..2a6dfa8 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ run: echo "Not yet complete" +build: + go build + lint: golangci-lint run ./... diff --git a/assets/latex/template/template.tex b/assets/latex/template/template.tex index 1e25d3b..91f4548 100644 --- a/assets/latex/template/template.tex +++ b/assets/latex/template/template.tex @@ -155,8 +155,7 @@ \section{Projects} %-----------PROGRAMMING SKILLS----------- \section{Technical Skills} \begin{itemize}[leftmargin=0.15in, label={}] - \small{\item{ - }} + %SKILL_SECTIONS% \end{itemize} %------------------------------------------- diff --git a/cmd/generate.go b/cmd/generate.go new file mode 100644 index 0000000..73daed6 --- /dev/null +++ b/cmd/generate.go @@ -0,0 +1,53 @@ +package cmd + +import ( + "anemon/internal/parser" + "anemon/internal/walker" + "errors" + "os" + "github.com/spf13/cobra" +) + +var generateCmd = &cobra.Command{ + Use: "generate", + Short: "Generate a CV", + Long: `Generate a CV using the CV at the current work directory`, + RunE: func(cmd *cobra.Command, args []string) error{ + dir, err := os.Getwd() + if err != nil{ + return err + } + cv_path := dir+"/cv" + _, err = os.Stat(cv_path) + if err != nil{ + if os.IsNotExist(err) { return errors.New("No `cv` directory found at:"+cv_path) } + return err + } + result, err := walker.WalkCV(cv_path) + if err != nil{ + return err + } + for lang := range result{ + err := parser.Init_output(lang+"-CV",dir) + if err != nil{ + return err + } + for sec_name := range result[lang]{ + sec, err := parser.Parse(result[lang][sec_name]) + if err != nil { + return err + } + _,err = parser.ApplyToSection(sec,sec_name,dir+"/assets/latex/output/"+lang+"-CV.tex") + if err != nil { + return err + } + //apply section to output file + } + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(generateCmd) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..8ae1e7e --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "os" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "anemon", + Short: "a CV genrator", + Long: `This CLI tool, written in Go, automates the generation of customized CVs from Markdown files based on a specified configuration. It parses CV sections in + multiple languages, prioritizes key skills or features as defined in an output.yml file, and outputs LaTeX files for each CV version, ready for compilation.`, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/cv/eng/Professional.md b/cv/eng/Professional.md new file mode 100644 index 0000000..05d2852 --- /dev/null +++ b/cv/eng/Professional.md @@ -0,0 +1,20 @@ +# Back-End Intern +## February 2024 -- August 2024 +### Qonto +#### End of studies internship + +- Contributed within a Cross Functional Team (CFT) to the standardization and extension of the onboarding process for four new markets (Austria, Netherlands, Belgium, Portugal). From design to implementation, in coordination with stakeholders (Risk, AML, Product), to ensure a smooth and compliant integration, with deployment scheduled for August. +- Led the extraction of back-office logic from a Ruby monolith into a microservice, deploying in a trunk-based development environment. Collaborated closely with the Ops teams throughout the process. Monitored production using Grafana, Sentry, ArgoCD, and Kibana to ensure quality and reliability, enhancing system maintainability by decoupling microservices. +- Improved the reliability and observability of microservices by completing the tracing mechanisms and implementing Service Level Objectives (SLOs). + +# Full Stack Developer +## June 2023 -- September 2023 +### Coexel +#### Internship then Fixed-Term Contract + +- Designed and developed a Python service for Named Entity Recognition (NER) using spaCy to efficiently identify named entities in 92M+ documents. Utilization of Elasticsearch for storage and optimized document search, enabling users to gain further insights into monitored themes. +- Rewrote an old website monitoring system created in Java to Python, enhancing maintainability and system performance. +- Implemented a RESTful API in Python using FastAPI, providing increased flexibility and scalability for data management and requests. +- Created a deployment pipeline with Docker and Github Actions, streamlining the deployment process and enabling faster and more robust updates. +- Significantly improved performance by reducing the number of GET requests from 5 to 1, resulting in an 85% faster display + diff --git a/cv/eng/education.md b/cv/eng/education.md index 3c0cb49..faf3d70 100644 --- a/cv/eng/education.md +++ b/cv/eng/education.md @@ -1,5 +1,9 @@ -- [Master's in Computer Science, Software and Data Engineering](https://www.univ-tln.fr/Master-Informatique-parcours-Developpement-et-Ingenierie-des-Donnees.html) - - University of Toulon, 2024 +# Master's in Computer Science, Software and Data Engineering +## https://www.univ-tln.fr/Master-Informatique-parcours-Developpement-et-Ingenierie-des-Donnees.html +### University of Toulon +#### 2024 -- [Bachelor's in Computer Science](https://www.univ-tln.fr/Licence-Informatique-parcours-Informatique.html) - - University of Toulon, 2022 +# Bachelor's in Computer Science +## https://www.univ-tln.fr/Licence-Informatique-parcours-Informatique.html +### University of Toulon +#### 2024 diff --git a/cv/eng/project.md b/cv/eng/project.md index 1147380..0a033e4 100644 --- a/cv/eng/project.md +++ b/cv/eng/project.md @@ -1,15 +1,14 @@ -**Agenda** -*Java, JavaFX, PostgreSQL, Github [GitHub Repository](https://github.com/MasterDID2022/sopra-project-LHD)* - +# Agenda +## Java, JavaFX, PostgreSQL, Github +### https://github.com/MasterDID2022/sopra-project-LHD - Developed in Java and JavaFX, using PostgreSQL for database management. - Project executed in an agile environment, emphasizing GitHub Flow, Pull Requests, Kanban, and Continuous Integration for effective collaboration. -**Epigraphy Tools** -*Java, Hibernate, Docker, REST, JWT [GitHub Repository](https://github.com/MasterDID2022/epicTool)* - +# Epigraphy Tools +## Java, Hibernate, Docker, REST, JWT +### https://github.com/MasterDID2022/epicTool - Designed and developed an epigraphy annotation tool for deep learning using Java, Docker, REST, and JWT. - Separated the front-end and back-end using Docker with container orchestration via Docker-Compose. - Established secure communication between the front-end and back-end through a RESTful API with JWT tokens. - Worked as a team in an agile context, utilizing the SCRUM method and applying DevOps practices for efficient and collaborative development. - diff --git a/cv/eng/work.md b/cv/eng/work.md deleted file mode 100644 index 7c95d50..0000000 --- a/cv/eng/work.md +++ /dev/null @@ -1,20 +0,0 @@ -**Back-End Intern** -*February 2024 -- August 2024* -[Qonto](https://qonto.com/en) -*End of studies internship* - -- Contributed within a Cross Functional Team (CFT) to the standardization and extension of the onboarding process for four new markets (Austria, Netherlands, Belgium, Portugal). From design to implementation, in coordination with stakeholders (Risk, AML, Product), to ensure a smooth and compliant integration, with deployment scheduled for August. -- Led the extraction of back-office logic from a *Ruby* monolith into a microservice, deploying in a trunk-based development environment. Collaborated closely with the Ops teams throughout the process. Monitored production using **Grafana, Sentry, ArgoCD, and Kibana** to ensure quality and reliability, enhancing system maintainability by decoupling microservices. -- Improved the reliability and observability of microservices by completing the tracing mechanisms and implementing Service Level Objectives (SLOs). - -**Full Stack Developer** -*June 2023 -- September 2023* -[Coexel](https://www.coexel.com/en) -*Internship then Fixed-Term Contract* - -- Designed and developed a Python service for Named Entity Recognition (**NER**) using spaCy to efficiently identify named entities in 92M+ documents. Utilization of **Elasticsearch** for storage and optimized document search, enabling users to gain further insights into monitored themes. -- Rewrote an old website monitoring system created in Java to **Python**, enhancing maintainability and system performance. -- Implemented a **RESTful** API in Python using **FastAPI**, providing increased flexibility and scalability for data management and requests. -- Created a deployment pipeline with **Docker** and Github Actions, streamlining the deployment process and enabling faster and more robust updates. -- Significantly improved performance by reducing the number of GET requests from 5 to 1, resulting in an 85% faster display - diff --git a/cv/fr/work.md b/cv/fr/Professional.md similarity index 100% rename from cv/fr/work.md rename to cv/fr/Professional.md diff --git a/go.mod b/go.mod index f9b9f73..1b93505 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,10 @@ module anemon go 1.22.0 -require github.com/google/go-cmp v0.6.0 // indirect +require ( + github.com/google/go-cmp v0.6.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/text v0.18.0 // indirect +) diff --git a/go.sum b/go.sum index 5a8d551..01e000a 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,14 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/parser/latex.go b/internal/parser/latex.go index 9824750..5d22b96 100644 --- a/internal/parser/latex.go +++ b/internal/parser/latex.go @@ -1,6 +1,7 @@ package parser import ( + "errors" "fmt" "os" "strings" @@ -8,10 +9,9 @@ import ( type SectionName string - //Read the template file in the assets directory -func read_template()(string,error) { - file, err := os.ReadFile("../../assets/latex/template/template.tex") +func read_template(path string)(string,error) { + file, err := os.ReadFile(path+"/assets/latex/template/template.tex") if err != nil { return "", err } @@ -19,39 +19,59 @@ func read_template()(string,error) { } //Write the template file in the assets directory -func writeTemplate(template string, name string)error{ - err := os.WriteFile("../../assets/latex/output/"+name+".tex", +func writeTemplate(path string, template string, name string)error{ + err := os.WriteFile(path+"/assets/latex/output/"+name, []byte(template), 0644) return err } +//Create a new empty template in the output dir +func Init_output(name string, root_path string)error{ + template,err := read_template(root_path) + if err != nil { + return err + } + err = writeTemplate(root_path, template, name+".tex") + return err +} + //Apply a section to a section type on a latex template -func applyToSection(section Section, section_type string)(string,error){ +func ApplyToSection(section Section, section_type string, output_path string)(string,error){ replacements := []string{section.first, section.second, section.third, section.fourth} - template := "" + b_template, err := os.ReadFile(output_path) + section_type = strings.Title(section_type) + if err != nil { + return "", err + } + template := string(b_template) switch{ case section_type == "Professional": - template = replace_param(prof_template,NB_P_PROF,replacements) + println("+--------"+section_type+"----------------+") + template = strings.Replace(template,"%EXPERIENCE_SECTIONS%", replace_param(prof_template,NB_P_PROF,replacements),1) template = replace_items(template, section.description) case section_type == "Project": - template = replace_param(proj_template,NB_P_PROJ,replacements) + template = strings.Replace(template,"%PROJECTS_SECTIONS%", replace_param(proj_template,NB_P_PROJ,replacements),1) template = replace_items(template, section.description) case section_type == "Education": - template = replace_param(edu_template,NB_P_EDU,replacements) + template = strings.Replace(template,"%EDUCATION_SECTIONS%", replace_param(edu_template,NB_P_EDU,replacements),1) case section_type == "Skill"://TODO https://github.com/Theo-Hafsaoui/Anemon/issues/1 - template = replace_param(sk_template,NB_P_SK,replacements) + template = strings.Replace(template,"%SKILL_SECTIONS%", replace_param(sk_template,NB_P_SK,replacements),1) + default: + return "",errors.New("Don't know type "+section_type) } - return template,nil + path_name := strings.Split(output_path, "/assets/latex/output/") + err = writeTemplate(path_name[0],template,path_name[1]) + return template,err } //Search and replace the number in range of `nb_params` by their replacement func replace_param(template string, nb_params int, replacements []string)string{ for i := 0; i < nb_params; i++ { - position := fmt.Sprintf("%d", i+1) + position := fmt.Sprintf("${%d}", i+1) template = strings.Replace(template, position, replacements[i], 1) } diff --git a/internal/parser/latex_test.go b/internal/parser/latex_test.go index fd4070a..3f0f581 100644 --- a/internal/parser/latex_test.go +++ b/internal/parser/latex_test.go @@ -8,7 +8,9 @@ import ( "github.com/google/go-cmp/cmp" ) -func TestReadLatex(t *testing.T) { + +func TestIO(t *testing.T) { +t.Run("Count returns 1 lines with just one line", func (t *testing.T) { dir := filepath.Join("../../assets", "latex", "template") templateFile := filepath.Join(dir, "template.tex") backupFile := filepath.Join(dir, "save.tex") @@ -22,7 +24,7 @@ func TestReadLatex(t *testing.T) { if err != nil { t.Fatalf("Failed to write file: %v", err) } - content, err := read_template() + content, err := read_template("../../") if err != nil { t.Fatalf("Failed to read file: %v", err) } @@ -39,10 +41,11 @@ func TestReadLatex(t *testing.T) { t.Fatalf("Failed to rename save.tex back to template.tex: %v", err) } } +}) } func TestWriteLatex(t *testing.T) { - err := writeTemplate("Hello, world", "hello") + err := writeTemplate("../../","Hello, world", "hello") if err != nil { t.Fatalf("Failed to write file: %v", err) } @@ -131,7 +134,7 @@ func TestApplySection(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := applyToSection(tt.section, tt.sectionType) + got, err := ApplyToSection(tt.section, tt.sectionType,"../../assets/latex/template/template.tex" ) if err != nil { t.Fatalf("error when applying template: %v", err) } diff --git a/internal/parser/markdown.go b/internal/parser/markdown.go index ea1f29a..eb621a4 100644 --- a/internal/parser/markdown.go +++ b/internal/parser/markdown.go @@ -2,7 +2,8 @@ package parser import ( "errors" - "regexp" + "regexp" + "fmt" "strings" ) @@ -17,6 +18,11 @@ type Section struct { description []string } +func (s Section) String() string { + return fmt.Sprintf("1: %s\n2: %s\n3: %s\n4: %s\nitems: %v", + s.first,s.second,s.third,s.fourth,s.description) +} + /* Parse parses a Markdown-like `paragraph` into a `Section`, extracting headings and description based on the number of leading hashtags. Returns an error if the format is invalid. */ diff --git a/internal/parser/template_sections.go b/internal/parser/template_sections.go index ae2698b..b532342 100644 --- a/internal/parser/template_sections.go +++ b/internal/parser/template_sections.go @@ -4,8 +4,8 @@ const pro_item = "\\resumeItem{%ITEM%}\n" const prof_template = ` \resumeSubheading - {1}{2} - {\href{3}{4}}{ } + ${1}${2} + {\href${3}${4}}{ } \resumeItemListStart %ITEMS% \resumeItemListEnd @@ -14,7 +14,7 @@ const NB_P_PROF = 4 const proj_template = ` \resumeProjectHeading -{\textbf{1} $|$ \emph{2 \href{3}{\faIcon{github}}}}{} +{\textbf${1} $|$ \emph${2 \href${3}{\faIcon{github}}}}{} \resumeItemListStart %ITEMS% \resumeItemListEnd @@ -23,12 +23,12 @@ const NB_P_PROJ = 3 const edu_template = ` \resumeSubheading -{\href{1}{2}}{} -{3}{4} +{\href${1}${2}}{} +${3}${4} ` const NB_P_EDU = 4 const sk_template = ` -\textbf{1}{: 2} \\ +\textbf${1}${: 2} \\ ` const NB_P_SK = 2 diff --git a/internal/walker/cv.go b/internal/walker/cv.go index 332ece5..ffd93a4 100644 --- a/internal/walker/cv.go +++ b/internal/walker/cv.go @@ -6,10 +6,10 @@ import ( "strings" ) -// walkCV traverses the directory tree starting at root and returns a map where -// the keys are the relative paths without the .md extension and the values are -// the contents of the markdown files. -func walkCV(root string) (map[string]string, error) { +// walkCV traverses the directory tree starting at root and returns a map of map where +// the keys are first the language folowed by the second key which is the section +// the values are the contents of the markdown files. +func WalkCV(root string) (map[string]map[string]string, error) { fileMap := make(map[string]string) err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { @@ -28,5 +28,13 @@ func walkCV(root string) (map[string]string, error) { if err != nil { return nil, err } - return fileMap, nil + md_per_language := make(map[string]map[string]string) + for k := range fileMap{ + k_split := strings.Split(k,"/") + if md_per_language [k_split[0]] == nil { + md_per_language[k_split[0]]= make(map[string]string) + } + md_per_language[k_split[0]][k_split[1]]=fileMap[k] + } + return md_per_language, nil } diff --git a/internal/walker/cv_test.go b/internal/walker/cv_test.go index 37e36a1..6c98297 100644 --- a/internal/walker/cv_test.go +++ b/internal/walker/cv_test.go @@ -28,27 +28,41 @@ func TestWalkCV(t *testing.T) { } } - result, err := walkCV(rootDir) + result, err := WalkCV(rootDir) if err != nil { t.Fatalf("walkCV failed: %v", err) } - expected := map[string]string{ - "eng/education": "Education", - "eng/project": "Project", - "fr/education": "Education", - "fr/work": "Work", + expected := map[string]map[string]string{ + "eng": { + "education": "Education", + "project": "Project", + }, + "fr": { + "education": "Education", + "work": "Work", + }, } - for key, expectedValue := range expected { - if result[key] != expectedValue { - t.Errorf("Expected %s to be %q, got %q", key, expectedValue, result[key]) + for lang, expectedFiles := range expected { + if resultFiles, ok := result[lang]; ok { + for file, expectedContent := range expectedFiles { + if resultContent, ok := resultFiles[file]; ok { + if resultContent != expectedContent { + t.Errorf("Expected %s/%s to be %q, got %q", lang, file, expectedContent, resultContent) + } + } else { + t.Errorf("Expected file %s/%s not found in result", lang, file) + } + } + } else { + t.Errorf("Expected language %s not found in result", lang) } } - for key := range result { - if _, found := expected[key]; !found { - t.Errorf("Unexpected key found: %s", key) + for lang := range result { + if _, found := expected[lang]; !found { + t.Errorf("Unexpected language found: %s", lang) } } } diff --git a/main.go b/main.go new file mode 100644 index 0000000..ddfa18b --- /dev/null +++ b/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "anemon/cmd" +) + +func main() { + cmd.Execute() +}