diff --git a/assets/latex/template/template.tex b/assets/latex/template/template.tex index 14b0dde..5eab7f9 100644 --- a/assets/latex/template/template.tex +++ b/assets/latex/template/template.tex @@ -1,12 +1,7 @@ -%------------------------- -% Resume in Latex % Author : Jake Gutierrez % Based off of: https://github.com/sb2nov/resume % License : MIT -%------------------------ - \documentclass[letterpaper,11pt]{article} - \usepackage{latexsym} \usepackage[empty]{fullpage} \usepackage{titlesec} @@ -21,34 +16,13 @@ \usepackage{xcolor} \usepackage[english]{babel} \usepackage{tabularx} -\definecolor{Nblack}{HTML}{3b4252} - \definecolor{NBLACK}{HTML}{2e3440} - \definecolor{Nwhite}{HTML}{eceff4} - \definecolor{nwhite}{HTML}{e5e9f0} - \definecolor{Nblue}{HTML}{5e81ac} - \definecolor{Nurl}{HTML}{8fbcbb} - \definecolor{nblue}{HTML}{005A92} +\definecolor{nblue}{HTML}{005A92} \input{glyphtounicode} - - -%----------FONT OPTIONS---------- -% sans-serif -% \usepackage[sfdefault]{FiraSans} -% \usepackage[sfdefault]{roboto} -% \usepackage[sfdefault]{noto-sans} -% \usepackage[default]{sourcesanspro} - -% serif -% \usepackage{CormorantGaramond} -% \usepackage{charter} - - \pagestyle{fancy} -\fancyhf{} % clear all header and footer fields +\fancyhf{} \fancyfoot{} \renewcommand{\headrulewidth}{0pt} \renewcommand{\footrulewidth}{0pt} - % Adjust margins \addtolength{\oddsidemargin}{-0.5in} \addtolength{\evensidemargin}{-0.5in} @@ -120,43 +94,37 @@ \begin{document} -\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}} -\end{center} - + \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}} + \end{center} + + %-----------EDUCATION----------- + \section{EDUCATION} + \resumeSubHeadingListStart + %EDUCATION_SECTIONS% + \resumeSubHeadingListEnd + + %-----------EXPERIENCE----------- + \section{EXPERIENCE} + \resumeSubHeadingListStart + %EXPERIENCE_SECTIONS% + \resumeSubHeadingListEnd + + %-----------PROJECTS----------- + \section{Projects} + \resumeSubHeadingListStart + %PROJECTS_SECTIONS% + \resumeSubHeadingListEnd + + %-----------PROGRAMMING SKILLS----------- + \section{Technical Skills} + \begin{itemize}[leftmargin=0.15in, label={}] + %SKILL_SECTIONS% + \end{itemize} + + %------------------------------------------- -%-----------EDUCATION----------- -\section{EDUCATION} -\resumeSubHeadingListStart -%EDUCATION_SECTIONS% -\resumeSubHeadingListEnd - - -%-----------EXPERIENCE----------- -\section{EXPERIENCE} -\resumeSubHeadingListStart -%EXPERIENCE_SECTIONS% -\resumeSubHeadingListEnd - - - -%-----------PROJECTS----------- -\section{Projects} -\resumeSubHeadingListStart -%PROJECTS_SECTIONS% -\resumeSubHeadingListEnd - - - -% -%-----------PROGRAMMING SKILLS----------- -\section{Technical Skills} -\begin{itemize}[leftmargin=0.15in, label={}] - %SKILL_SECTIONS% -\end{itemize} - -%------------------------------------------- \end{document} diff --git a/cmd/generate.go b/cmd/generate.go index d444d3c..195264f 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -1,12 +1,8 @@ package cmd import ( - m_lang "anemon/internal/markup_languages" - "anemon/internal/walker" - "errors" + "anemon/internal/adapters/input" "os" - "strings" - "github.com/spf13/cobra" ) @@ -15,64 +11,14 @@ var generateCmd = &cobra.Command{ Short: "Generate a CV", Long: `Generate a CV using the Markdown CV directory in the current work directory`, RunE: func(cmd *cobra.Command, args []string) error{ - dir, err := os.Getwd() - if err != nil{ - return err - } - CV,err := getSectionMapFrom(dir) - if err != nil { - return err - } - err = createLatexCVFrom(dir,CV) - if err != nil { - return err - } + root, err := os.Getwd() + if err != nil{ return err } + err = input.GenerateCVFromMarkDownToLatex(root) + if err != nil { return err } return nil }, } -//Use a CV map created by `getSectionMapFrom` and write for each lang key a latex CV using the given information -func createLatexCVFrom(dir string, CV map[string]map[string]string )(error){ - for lang := range CV{ - err := m_lang.Init_output(lang+"-CV",dir) - if err != nil{ - return err - } - for sec_name := range CV[lang]{ - for _,paragraphe := range strings.Split(CV[lang][sec_name], "\n\n"){ - if len(paragraphe)<=1 { - continue - } - sec, err := m_lang.Parse(paragraphe) - if err != nil { - return err - } - _,err = m_lang.ApplyToSection(sec,sec_name,dir+"/assets/latex/output/"+lang+"-CV.tex") - if err != nil { - return err - } - } - } - } - return nil -} - -//TODO consider struct for this map of map -//Walk throught the CV directory and return a map of lang within each their is a map of section -func getSectionMapFrom(dir string)(map[string]map[string]string,error){ - cv_path := dir+"/cv" - _, err := os.Stat(cv_path) - if err != nil{ - if os.IsNotExist(err) { return nil, errors.New("No `cv` directory found at:"+cv_path) } - return nil,err - } - result, err := walker.WalkCV(cv_path) - if err != nil{ - return nil,err - } - return result,nil -} - func init() { rootCmd.AddCommand(generateCmd) } diff --git a/internal/adapters/input/cli.go b/internal/adapters/input/cli.go index de1ab6f..ccdd3b9 100644 --- a/internal/adapters/input/cli.go +++ b/internal/adapters/input/cli.go @@ -1 +1,15 @@ package input + +import ( + "anemon/internal/adapters/output" + "anemon/internal/core" +) + +//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 templateReader core.TemplateReader = &output.LatexReader{} + var templateProccesor core.TemplateProcessor = &output.LatexProccesor{} + service := &core.CVService{} + return service.GenerateTemplates(root,source,templateReader,templateProccesor) +} diff --git a/internal/adapters/input/input_test.go b/internal/adapters/input/input_test.go index 352d54d..8645f13 100644 --- a/internal/adapters/input/input_test.go +++ b/internal/adapters/input/input_test.go @@ -56,13 +56,13 @@ var( relativePath string content string }{ - {"eng/education.md", work_input}, - {"eng/project.md", work_input}, - {"eng/work.md", work_input}, - {"eng/skill.md", skill_input}, - {"fr/education.md", work_input}, - {"fr/work.md", work_input}, - {"fr/skill.md", skill_input}, + {"cv/eng/education.md", work_input}, + {"cv/eng/project.md", work_input}, + {"cv/eng/work.md", work_input}, + {"cv/eng/skill.md", skill_input}, + {"cv/fr/education.md", work_input}, + {"cv/fr/work.md", work_input}, + {"cv/fr/skill.md", skill_input}, } ) @@ -117,8 +117,8 @@ func TestSections(t *testing.T) { } t.Run("Should return a valid list of cv", func (t *testing.T) { - got,err := GetCVsFrom(rootDir) - got[1].Print() + source := MarkdownSource{} + got,err := source.GetCVsFrom(rootDir) if err!=nil{ t.Fatalf("Failed to getCV got %s",err) } @@ -144,9 +144,7 @@ func TestSections(t *testing.T) { p_t_sec_got := got[len(got)-1].Sections[len(got[len(got)-1].Sections)-1].Paragraphes p_t_s_want := work_expected_result - if len(p_t_sec_got) != len(p_t_s_want){ - t.Fatalf("Should have len %d got %d",len(p_t_s_want),len(p_t_sec_got)) - } + if len(p_t_sec_got) != len(p_t_s_want){ t.Fatalf("Should have len %d got %d",len(p_t_s_want),len(p_t_sec_got)) } if p_t_sec_got[len(p_t_sec_got)-1].H1 != p_t_s_want[len(p_t_s_want)-1].H1{ t.Fatalf("Should have title %s got %s",p_t_s_want[len(p_t_s_want)-1].H1,p_t_sec_got[len(p_t_sec_got)-1].H1) diff --git a/internal/adapters/input/markdown_tree_reader.go b/internal/adapters/input/markdown_tree_reader.go index 38bf266..7fec5f2 100644 --- a/internal/adapters/input/markdown_tree_reader.go +++ b/internal/adapters/input/markdown_tree_reader.go @@ -10,6 +10,8 @@ import ( "strings" ) +type MarkdownSource struct{} + var hashtagRegex = regexp.MustCompile(`^#+`) // `GetCVFrom` takes a root directory and extracts Markdown documents to generate a list of CV type. @@ -17,17 +19,18 @@ var hashtagRegex = regexp.MustCompile(`^#+`) // This function assumes the directory structure is a tree with a depth of 3, where each leaf // is a Markdown (.md) document. Each document may contain multiple paragraphs, but the headers // should not repeat within the same document. -func GetCVsFrom(root string) ([]core.CV, error) { +func (*MarkdownSource) GetCVsFrom(root string) ([]core.CV, error) { + cvsPath := root+"/cv" cvs := make([]core.CV,0) + current_lang := "" current_sections := make([]core.Section,0) has_been_inside_dir := false - err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + err := filepath.Walk(cvsPath, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir(){ if has_been_inside_dir { - println( cvs[len(cvs)-2].Lang ) cvs[len(cvs)-1].Sections=current_sections current_sections = make([]core.Section,0) has_been_inside_dir = false @@ -36,7 +39,6 @@ func GetCVsFrom(root string) ([]core.CV, error) { cvs = append(cvs, core.CV{Lang: current_lang}) } if !info.IsDir() && strings.HasSuffix(info.Name(), ".md") { - println( len(current_sections) ) has_been_inside_dir = true if current_lang == "" { return errors.New("markdown file found before lang directory") } content, err := os.ReadFile(path) diff --git a/internal/adapters/output/latex_test.go b/internal/adapters/output/latex_test.go index 4129bdd..f501033 100644 --- a/internal/adapters/output/latex_test.go +++ b/internal/adapters/output/latex_test.go @@ -26,7 +26,8 @@ func TestApplySectionToTemplate(t *testing.T) { \resumeItemListEnd End`) - got, err := ApplySectionToTemplate(template, headers, items, "Professional") + processor := LatexProccesor{} + got, err := processor.ApplySectionToTemplate(template, headers, items, "Professional") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -50,7 +51,8 @@ End`) \resumeItemListEnd End`) - got, err := ApplySectionToTemplate(template, headers, items, "Project") + processor := LatexProccesor{} + got, err := processor.ApplySectionToTemplate(template, headers, items, "Project") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -64,7 +66,8 @@ End`) headers := []string{"Company Name", "Position", "https://company.com", "Company"} items := []string{"Task 1", "Task 2"} - _, err := ApplySectionToTemplate(template, headers, items, "UnsupportedSection") + processor := LatexProccesor{} + _, err := processor.ApplySectionToTemplate(template, headers, items, "UnsupportedSection") if err == nil || err.Error() != "Don't know type UnsupportedSection" { t.Errorf("expected error 'Don't know type UnsupportedSection', got %v", err) } @@ -85,7 +88,8 @@ End`) \resumeItemListEnd End`) - got, err := ApplySectionToTemplate(template, headers, items, "Project") + processor := LatexProccesor{} + got, err := processor.ApplySectionToTemplate(template, headers, items, "Project") if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/internal/adapters/output/latex_writer.go b/internal/adapters/output/latex_writer.go index 4b2a66b..682519e 100644 --- a/internal/adapters/output/latex_writer.go +++ b/internal/adapters/output/latex_writer.go @@ -8,16 +8,19 @@ import ( "os" ) +type LatexReader struct{} +type LatexProccesor struct{} + //Write the template file in the assets directory -func makeNewTemplate(path string, template string, name string)error{ +func (*LatexProccesor)MakeNewTemplate(path string, template string, name string)error{ err := os.WriteFile(path+"/assets/latex/output/"+name, []byte(template), 0644) return err } //Read the template file in the assets directory from the root dir -func readCVTemplate(root string)(string, error){ +func (*LatexReader)ReadCVTemplate(root string)(string, error){ file, err := os.ReadFile(root+"/assets/latex/template/template.tex") if err != nil { return "", err @@ -25,13 +28,10 @@ func readCVTemplate(root string)(string, error){ return string(file), nil } -//In the core we will read the template -//iterate by section -//Apply to template -//Write - //Apply a section to a section type on a latex template -func ApplySectionToTemplate(template string, headers []string,item []string, section string) (string,error){ +func (*LatexProccesor)ApplySectionToTemplate(template string, headers []string,item []string, section string) (string,error){ + if len(section)<2{ return "",errors.New("Don't know type "+section) } + section = strings.ToUpper(string(section[0]))+section[1:] switch{ case section == "Professional": template = replaceWithSectionTemplate(template,ProfessionalTemplate, @@ -46,6 +46,7 @@ func ApplySectionToTemplate(template string, headers []string,item []string, sec template = replaceWithSectionTemplate(template,SkillTemplate, headers,nil) default: + fmt.Println("Don't know type "+section) return "",errors.New("Don't know type "+section) } return template,nil diff --git a/internal/core/generate.go b/internal/core/generate.go new file mode 100644 index 0000000..1f8d07a --- /dev/null +++ b/internal/core/generate.go @@ -0,0 +1,32 @@ +package core + +type CVService struct{} + +//generate the template for the cvs defined in the assets directory +func (g *CVService) GenerateTemplates(root string, source Source, templateReader TemplateReader, templateProcessor TemplateProcessor)error{ + println("--Generating CVs--") + cvs,err := source.GetCVsFrom(root) + if err != nil{ return err } + GenTemplate,err := templateReader.ReadCVTemplate(root) + if err != nil{ return err } + for _, cv := range cvs { + println("Generating for:"+cv.Lang) + cvTemplate := GenTemplate + for _, section := range cv.Sections { + for _, paragraph := range section.Paragraphes { + println(paragraph.H1) + println(len(cvTemplate)) + headers := []string{ paragraph.H1, paragraph.H2, + paragraph.H3, paragraph.H4} + items:=paragraph.Items + cvTemplate,err = templateProcessor.ApplySectionToTemplate( + cvTemplate,headers,items,section.Title) + 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 new file mode 100644 index 0000000..087dd34 --- /dev/null +++ b/internal/core/generate_test.go @@ -0,0 +1,94 @@ +package core + +import ( + "reflect" + "testing" +) + +type MockSource struct { + CVs []CV + Err error +} + +func (s *MockSource) GetCVsFrom(root string) ([]CV, error) { + if s.Err != nil { + return nil, s.Err + } + return s.CVs, nil +} + +type MockTemplateReader struct { + Template string + Err error +} + +func (r *MockTemplateReader) ReadCVTemplate(path string) (string, error) { + if r.Err != nil { + return "", r.Err + } + return r.Template, nil +} + +type MockTemplateProcessor struct { + AppliedTemplates []string + GeneratedFiles map[string]string + ApplyErr error + MakeErr error +} + +func (p *MockTemplateProcessor) MakeNewTemplate(path string, template string, name string) error { + if p.MakeErr != nil { return p.MakeErr } + p.GeneratedFiles[name] = template + return nil +} + +func (p *MockTemplateProcessor) ApplySectionToTemplate(template string, headers []string, items []string, section string) (string, error) { + if p.ApplyErr != nil { return "", p.ApplyErr } + result := template + " | Section: " + section + " | Headers: " + headers[0] + ", " + headers[1] + ", " + headers[2] + ", " + headers[3] + for _, item := range items { + result += " | Item: " + item + } + p.AppliedTemplates = append(p.AppliedTemplates, result) + return result, nil +} + +func TestGenerateTemplates(t *testing.T) { + root := "testRoot" + baseTemplate := "base template content" + + cv := CV{ + Lang: "EN", + Sections: []Section{ + { + Title: "Work Experience", + Paragraphes: []Paragraphe{ + { + H1: "Job Title", + H2: "Company", + H3: "Location", + H4: "Date", + Items: []string{"Managed projects", "Led team"}, + }, + }, + }, + }, + } + + 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) + + 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"] + 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) + } +} diff --git a/internal/core/ports.go b/internal/core/ports.go index 67cbb7d..c077540 100644 --- a/internal/core/ports.go +++ b/internal/core/ports.go @@ -5,16 +5,20 @@ import ( "strings" ) +type ICVservice interface { + generateTemplates(root string, source Source, templateReader TemplateReader, templateProcessor TemplateProcessor)error +} + type Source interface { GetCVsFrom(root string) ([]CV, error) } type TemplateReader interface { - readCVTemplate(path string)(string, error) + ReadCVTemplate(root string)(string, error) } type TemplateProcessor interface { - makeNewTemplate(path string, template string, name string)error + MakeNewTemplate(path string, template string, name string)error ApplySectionToTemplate(template string, headers []string,item []string, section string) (string,error) }