From 5f42a1c78ddcf203b76e86585f3431f62b134034 Mon Sep 17 00:00:00 2001 From: Theo-Hafsaoui Date: Sun, 10 Nov 2024 13:47:21 +0100 Subject: [PATCH] WIP add yaml --- assets/latex/template/template.tex | 11 ++- go.mod | 5 +- go.sum | 2 + internal/adapters/input/cli.go | 3 +- internal/adapters/input/input_test.go | 56 +++++++++++++++ internal/adapters/input/yaml.go | 22 ++++++ internal/adapters/output/latex_test.go | 35 +++++++++ internal/adapters/output/latex_writer.go | 32 +++++++-- internal/core/generate.go | 46 +++++++----- internal/core/generate_test.go | 90 ++++++++++++++++++------ internal/core/ports.go | 20 +++++- params.yml | 16 +++++ 12 files changed, 283 insertions(+), 55 deletions(-) create mode 100644 internal/adapters/input/yaml.go create mode 100644 params.yml diff --git a/assets/latex/template/template.tex b/assets/latex/template/template.tex index 5eab7f9..45cff28 100644 --- a/assets/latex/template/template.tex +++ b/assets/latex/template/template.tex @@ -80,9 +80,7 @@ } \newcommand{\resumeSubItem}[1]{\resumeItem{#1}\vspace{-4pt}} - \renewcommand\labelitemii{$\vcenter{\hbox{\tiny$\bullet$}}$} - \newcommand{\resumeSubHeadingListStart}{\begin{itemize}[leftmargin=0.15in, label={}]} \newcommand{\resumeSubHeadingListEnd}{\end{itemize}} \newcommand{\resumeItemListStart}{\begin{itemize}} @@ -94,11 +92,12 @@ \begin{document} + %VARS% \begin{center} - \textbf{\Huge \scshape \textcolor{nblue}{Anemon Vincent}} \\ \vspace{1pt} - \small +33 6 26 26 50 07 $|$ \href{mailto:anemon@pm.me}{\underline{anemon@pm.me}} \\ - \href{https://linkedin.com/in/anemon/}{\underline{linkedin.com/in/anemon}} $|$ - \href{https://github.com/anemon}{\underline{github.com/anemon}} + \textbf{\Huge \scshape \textcolor{nblue}{\Name \ \Firstname}} \\ \vspace{1pt} + \small \Number \ $|$ \href{mailto:\Mail}{\underline{\Mail}} \\ + \href{https:\Linkedin}{\underline{linkedin.com/in/\Name}} $|$ + \href{https:\Github}{\underline{github.com/\Name}} \end{center} %-----------EDUCATION----------- diff --git a/go.mod b/go.mod index 7510094..0c4bfec 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module anemon go 1.22.0 -require github.com/spf13/cobra v1.8.1 +require ( + github.com/spf13/cobra v1.8.1 + gopkg.in/yaml.v3 v3.0.1 +) require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect diff --git a/go.sum b/go.sum index 912390a..a01295b 100644 --- a/go.sum +++ b/go.sum @@ -6,5 +6,7 @@ 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= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/adapters/input/cli.go b/internal/adapters/input/cli.go index ccdd3b9..cad8d80 100644 --- a/internal/adapters/input/cli.go +++ b/internal/adapters/input/cli.go @@ -8,8 +8,9 @@ import ( //Use the implementation for markdown and latex to generate latex CV from a tree dir of mardown document func GenerateCVFromMarkDownToLatex(root string)error{ var source core.Source = &MarkdownSource{} + var paramsSource core.SourceParams = &YamlSource{} var templateReader core.TemplateReader = &output.LatexReader{} var templateProccesor core.TemplateProcessor = &output.LatexProccesor{} service := &core.CVService{} - return service.GenerateTemplates(root,source,templateReader,templateProccesor) + return service.GenerateTemplates(root,source, paramsSource,templateReader,templateProccesor) } diff --git a/internal/adapters/input/input_test.go b/internal/adapters/input/input_test.go index 8645f13..18fd73b 100644 --- a/internal/adapters/input/input_test.go +++ b/internal/adapters/input/input_test.go @@ -9,6 +9,21 @@ import ( ) var( + yamlContent = ` +info: + name: Doe + firstname: John + number: "12345" + mail: john.doe@example.com + github: johndoe + linkedin: john-doe-linkedin +variante: + optionA: + - "value1" + - "value2" + optionB: + - "value3" +` work_input = ` # Back-End Intern ## February 2024 -- August 2024 @@ -152,3 +167,44 @@ func TestSections(t *testing.T) { }) } + + +func TestGetParamsFrom(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test") + if err != nil { t.Fatalf("Failed to create temp directory: %v", err) } + defer os.RemoveAll(tempDir) + + yamlFilePath := filepath.Join(tempDir, "params.yml") + err = os.WriteFile(yamlFilePath, []byte(yamlContent), 0644) + if err != nil { t.Fatalf("Failed to write YAML file: %v", err) } + + source := &YamlSource{} + params, err := source.GetParamsFrom(tempDir) + if err != nil { + t.Fatalf("GetParamsFrom returned an error: %v", err) + } + expectedParams := core.Params{ + Info: struct { + Name string `yaml:"name"` + FirstName string `yaml:"firstname"` + Number string `yaml:"number"` + Mail string `yaml:"mail"` + GitHub string `yaml:"github"` + LinkedIn string `yaml:"linkedin"` + }{ + Name: "Doe", + FirstName: "John", + Number: "12345", + Mail: "john.doe@example.com", + GitHub: "johndoe", + LinkedIn: "john-doe-linkedin", + }, + Variante: map[string][]string{ + "optionA": {"value1", "value2"}, + "optionB": {"value3"}, + }, + } + if !reflect.DeepEqual(params, expectedParams) { + t.Errorf("Expected %+v, but got %+v", expectedParams, params) + } +} diff --git a/internal/adapters/input/yaml.go b/internal/adapters/input/yaml.go new file mode 100644 index 0000000..a9e480f --- /dev/null +++ b/internal/adapters/input/yaml.go @@ -0,0 +1,22 @@ +package input + +import ( + core "anemon/internal/core" + "os" + "gopkg.in/yaml.v3" +) + +type YamlSource struct{} + +func (*YamlSource) GetParamsFrom(root string) (core.Params, error) { + params := core.Params{} + yamlFile, err := os.ReadFile(root + "/params.yml") + if err != nil { + return params, err + } + err = yaml.Unmarshal(yamlFile, ¶ms) + if err != nil { + return params, err + } + return params, nil +} diff --git a/internal/adapters/output/latex_test.go b/internal/adapters/output/latex_test.go index f501033..245917d 100644 --- a/internal/adapters/output/latex_test.go +++ b/internal/adapters/output/latex_test.go @@ -1,6 +1,7 @@ package output import ( + "anemon/internal/core" "strings" "testing" ) @@ -255,3 +256,37 @@ func TestSanitize(t *testing.T) { t.Run("happy path - should sanitize special ch } }) } + +func TestApplyInfoToTemplate(t *testing.T) { + template := "%VARS%" + params := core.Params{ + Info: struct { + Name string `yaml:"name"` + FirstName string `yaml:"firstname"` + Number string `yaml:"number"` + Mail string `yaml:"mail"` + GitHub string `yaml:"github"` + LinkedIn string `yaml:"linkedin"` + }{ + Name: "John Doe", + FirstName: "John", + Number: "12345", + Mail: "john.doe@example.com", + GitHub: "https://github.com/johndoe", + LinkedIn: "https://linkedin.com/in/johndoe", + }, + Variante: map[string][]string{}, + } + want := `\def\Name{John Doe} +\def\FirstName{John} +\def\Number{12345} +\def\Mail{john.doe@example.com} +\def\GitHub{https://github.com/johndoe} +\def\LinkedIn{https://linkedin.com/in/johndoe}` + got := ApplyInfoToTemplate(template, params) + + if removeWhitespace(got) != removeWhitespace(want) { + t.Errorf("expected:\n%s\ngot:\n%s", want, got) + } +} + diff --git a/internal/adapters/output/latex_writer.go b/internal/adapters/output/latex_writer.go index 682519e..5192412 100644 --- a/internal/adapters/output/latex_writer.go +++ b/internal/adapters/output/latex_writer.go @@ -1,11 +1,13 @@ package output import ( - "fmt" + "anemon/internal/core" "errors" - "strings" - "regexp" + "fmt" "os" + "reflect" + "regexp" + "strings" ) type LatexReader struct{} @@ -19,13 +21,25 @@ func (*LatexProccesor)MakeNewTemplate(path string, template string, name string) return err } -//Read the template file in the assets directory from the root dir -func (*LatexReader)ReadCVTemplate(root string)(string, error){ +//Read the template file in the assets directory from the root dir and apply the params given to it +func (*LatexReader)ReadCVTemplate(root string,params core.Params)(string, error){ file, err := os.ReadFile(root+"/assets/latex/template/template.tex") if err != nil { return "", err } - return string(file), nil + return ApplyInfoToTemplate(string(file),params), nil +} + +//Apply general information(name, mail..) to a template +func ApplyInfoToTemplate(template string, params core.Params) (string){ + var varsBuilder strings.Builder + infoValue := reflect.ValueOf(params.Info) + for i := 0; i < infoValue.NumField(); i++ { + field := infoValue.Type().Field(i) + fieldValue := infoValue.Field(i) + varsBuilder.WriteString("\\def\\"+field.Name+"{"+fieldValue.String()+"}\n") + } + return replaceVars(template,varsBuilder.String()) } //Apply a section to a section type on a latex template @@ -60,6 +74,12 @@ func replaceWithSectionTemplate(template string,SectionTemplate TemplateStruct,h return updated_template } +//Replace the %vars$ with the vars +func replaceVars(template string,vars string)string{ + updated_template := strings.Replace(template,"%VARS%",vars,1) + return updated_template +} + //Search and replace the headers in the template by their replacement func replace_headers(sec_template string, replacements []string)string{ for i := 0; i < len(replacements); i++ { diff --git a/internal/core/generate.go b/internal/core/generate.go index 5c0a856..bc95d69 100644 --- a/internal/core/generate.go +++ b/internal/core/generate.go @@ -1,35 +1,43 @@ package core import ( - "log/slog" - "sort" - "strings" + "log/slog" + "sort" + "strings" ) type CVService struct{} +//TODO apply information in header of CV //generate the template for the cvs defined in the assets directory -func (g *CVService) GenerateTemplates(root string, source Source, templateReader TemplateReader, templateProcessor TemplateProcessor)error{ +func (g *CVService) GenerateTemplates(root string, source Source, paramsSource SourceParams, + templateReader TemplateReader, templateProcessor TemplateProcessor)error{ slog.Info("--Generating CVs--") - cvs,err := source.GetCVsFrom(root) - if err != nil{ return err } - GenTemplate,err := templateReader.ReadCVTemplate(root) + cvs,err := source.GetCVsFrom(root);if err != nil{ return err } + params,err := paramsSource.GetParamsFrom(root);if err != nil{ return err } + GenTemplate,err := templateReader.ReadCVTemplate(root,params) if err != nil{ return err } for _, cv := range cvs { - slog.Info("Generating for:"+cv.Lang) - cvTemplate := GenTemplate - for _, section := range cv.Sections { - for _, paragraph := range section.Paragraphes { - headers := []string{ paragraph.H1, paragraph.H2, - paragraph.H3, paragraph.H4} - items:=paragraph.Items - cvTemplate,err = templateProcessor.ApplySectionToTemplate( - cvTemplate,headers,items,section.Title) + if len(params.Variante)==0{ + params.Variante = map[string][]string{"simple": nil} + } + for vari, keywords:= range params.Variante { + cvName := "CV-"+cv.Lang+"-"+vari+".tex" + slog.Info("Generating for:"+cvName) + cvTemplate := GenTemplate + for _, section := range cv.Sections { + for _, paragraph := range section.Paragraphes { + headers := []string{ paragraph.H1, paragraph.H2, + paragraph.H3, paragraph.H4} + items:=paragraph.Items + sortByScore(items,keywords) + cvTemplate,err = templateProcessor.ApplySectionToTemplate( + cvTemplate,headers,items,section.Title) + if err != nil{ return err } + } + err = templateProcessor.MakeNewTemplate(root,cvTemplate,cvName) if err != nil{ return err } } - cvName := "CV-"+cv.Lang+".tex" - err = templateProcessor.MakeNewTemplate(root,cvTemplate,cvName) - if err != nil{ return err } } } return nil diff --git a/internal/core/generate_test.go b/internal/core/generate_test.go index 9d70272..71820c7 100644 --- a/internal/core/generate_test.go +++ b/internal/core/generate_test.go @@ -5,15 +5,23 @@ import ( "testing" ) +type MockParamsSource struct { + Params Params + Err error +} + +func (s *MockParamsSource) GetParamsFrom(root string) (Params, error) { + if s.Err != nil { return s.Params, s.Err } + return s.Params, nil +} + type MockSource struct { CVs []CV Err error } func (s *MockSource) GetCVsFrom(root string) ([]CV, error) { - if s.Err != nil { - return nil, s.Err - } + if s.Err != nil { return nil, s.Err } return s.CVs, nil } @@ -22,10 +30,8 @@ type MockTemplateReader struct { Err error } -func (r *MockTemplateReader) ReadCVTemplate(path string) (string, error) { - if r.Err != nil { - return "", r.Err - } +func (r *MockTemplateReader) ReadCVTemplate(path string, params Params) (string, error) { + if r.Err != nil { return "", r.Err } return r.Template, nil } @@ -55,6 +61,27 @@ func (p *MockTemplateProcessor) ApplySectionToTemplate(template string, headers func TestGenerateTemplates(t *testing.T) { root := "testRoot" baseTemplate := "base template content" + params := Params{ + Info: struct { + Name string `yaml:"name"` + FirstName string `yaml:"firstname"` + Number string `yaml:"number"` + Mail string `yaml:"mail"` + GitHub string `yaml:"github"` + LinkedIn string `yaml:"linkedin"` + }{ + Name: "Doe", + FirstName: "John", + Number: "12345", + Mail: "john.doe@example.com", + GitHub: "johndoe", + LinkedIn: "john-doe-linkedin", + }, + Variante: map[string][]string{ + "optionA": {"value1", "value2"}, + "optionB": {"value3"}, + }, + } cv := CV{ Lang: "EN", @@ -74,23 +101,46 @@ func TestGenerateTemplates(t *testing.T) { }, } - source := &MockSource{ CVs: []CV{cv}, } - templateReader := &MockTemplateReader{ Template: baseTemplate } - templateProcessor := &MockTemplateProcessor{ GeneratedFiles: make(map[string]string) } - service := CVService{} - err := service.GenerateTemplates(root, source, templateReader, templateProcessor) + t.Run("When giving one language and two variante should generate two cv in the language", func (t *testing.T) { + source := &MockSource{ CVs: []CV{cv}, } + paramsSource := &MockParamsSource{ Params: params, } + templateReader := &MockTemplateReader{ Template: baseTemplate } + templateProcessor := &MockTemplateProcessor{ GeneratedFiles: make(map[string]string) } + service := CVService{} + err := service.GenerateTemplates(root, source, paramsSource, templateReader, templateProcessor) - if err != nil { t.Fatalf("expected no error, got %v", err) } + if err != nil { t.Fatalf("expected no error, got %v", err) } + if len(templateProcessor.GeneratedFiles) != 2 { t.Fatalf("expected 2 generated file, got %d", len(templateProcessor.GeneratedFiles)) } - if len(templateProcessor.GeneratedFiles) != 1 { t.Fatalf("expected 1 generated file, got %d", len(templateProcessor.GeneratedFiles)) } + generatedTemplate, exists := templateProcessor.GeneratedFiles["CV-EN-optionA.tex"] + if !exists { t.Fatalf("expected generated file 'CV-EN-optionA.tex' to exist") } - generatedTemplate, exists := templateProcessor.GeneratedFiles["CV-EN.tex"] - if !exists { t.Fatalf("expected generated file 'CV-EN' to exist") } + expectedContent := baseTemplate + " | Section: Work Experience | Headers: Job Title, Company, Location, Date | Item: Managed projects | Item: Led team" + if !reflect.DeepEqual(generatedTemplate, expectedContent) { + t.Errorf("expected generated template content %v, got %v", expectedContent, generatedTemplate) + } + }) + + t.Run("When giving one language and zero info or variante should generate one cv in the language", func (t *testing.T) { + source := &MockSource{ CVs: []CV{cv}, } + paramsSource := &MockParamsSource{ Params: Params{}, } + templateReader := &MockTemplateReader{ Template: baseTemplate } + templateProcessor := &MockTemplateProcessor{ GeneratedFiles: make(map[string]string) } + service := CVService{} + err := service.GenerateTemplates(root, source, paramsSource, templateReader, templateProcessor) + + if err != nil { t.Fatalf("expected no error, got %v", err) } + if len(templateProcessor.GeneratedFiles) != 1 { t.Fatalf("expected 1 generated file, got %d", len(templateProcessor.GeneratedFiles)) } + + generatedTemplate, exists := templateProcessor.GeneratedFiles["CV-EN-simple.tex"] + if !exists { t.Fatalf("expected generated file 'CV-EN-simple.tex' to exist") } + + expectedContent := baseTemplate + " | Section: Work Experience | Headers: Job Title, Company, Location, Date | Item: Managed projects | Item: Led team" + if !reflect.DeepEqual(generatedTemplate, expectedContent) { + t.Errorf("expected generated template content %v, got %v", expectedContent, generatedTemplate) + } + }) - expectedContent := baseTemplate + " | Section: Work Experience | Headers: Job Title, Company, Location, Date | Item: Managed projects | Item: Led team" - if !reflect.DeepEqual(generatedTemplate, expectedContent) { - t.Errorf("expected generated template content %v, got %v", expectedContent, generatedTemplate) - } } func TestGetScore(t *testing.T) { diff --git a/internal/core/ports.go b/internal/core/ports.go index c077540..7447439 100644 --- a/internal/core/ports.go +++ b/internal/core/ports.go @@ -9,12 +9,16 @@ type ICVservice interface { generateTemplates(root string, source Source, templateReader TemplateReader, templateProcessor TemplateProcessor)error } +type SourceParams interface { + GetParamsFrom(root string) (Params, error) +} + type Source interface { GetCVsFrom(root string) ([]CV, error) } type TemplateReader interface { - ReadCVTemplate(root string)(string, error) + ReadCVTemplate(root string, params Params)(string, error) } type TemplateProcessor interface { @@ -32,7 +36,7 @@ type Section struct { Title string Paragraphes []Paragraphe } - + type Paragraphe struct { H1 string H2 string @@ -41,6 +45,18 @@ type Paragraphe struct { Items []string } +type Params struct { + Info struct { + Name string `yaml:"name"` + FirstName string `yaml:"firstname"` + Number string `yaml:"number"` + Mail string `yaml:"mail"` + GitHub string `yaml:"github"` + LinkedIn string `yaml:"linkedin"` + } `yaml:"info"` + Variante map[string][]string `yaml:"variante"` +} + //Print Method for CV func (cv *CV) Print() { fmt.Println("CV Language: "+ cv.Lang) diff --git a/params.yml b/params.yml new file mode 100644 index 0000000..6bba36d --- /dev/null +++ b/params.yml @@ -0,0 +1,16 @@ +info: + name: Anemon + firstname: Vincent + number: "+33 42 42 42 42 42" + mail: vincent.anemon@example.com + github: vanemon + linkedin: vincent-anemon-linkedin + +variante: + webDev: + - "feature1" + - "feature2" + Hacker: + - "feature3" + - "feature4" +