Skip to content

Commit

Permalink
WIP refacto to hexa
Browse files Browse the repository at this point in the history
  • Loading branch information
Theo-Hafsaoui committed Oct 20, 2024
1 parent 4014157 commit e265514
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 3 deletions.
1 change: 1 addition & 0 deletions internal/adapters/input/cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package input
156 changes: 156 additions & 0 deletions internal/adapters/input/input_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package input

import (
core "anemon/internal/core"
"os"
"path/filepath"
"reflect"
"testing"
)

var(
work_input = `
# Back-End Intern
## February 2024 -- August 2024
### TechCorp
#### Internship
- Assisted in developing and optimizing a key business process for expanding into new markets, collaborating with various teams to ensure compliance and smooth integration.
- Participated in the migration of core backend logic from a monolithic application to a microservice architecture, improving system performance and scalability.
- Enhanced system monitoring and reliability by implementing tracing mechanisms and performance objectives.
# Back-End Intern
## February 2024 -- August 2024
### TechCorp
#### Internship
- Assisted in developing and optimizing a key business process for expanding into new markets, collaborating with various teams to ensure compliance and smooth integration.
- Participated in the migration of core backend logic from a monolithic application to a microservice architecture, improving system performance and scalability.
- Enhanced system monitoring and reliability by implementing tracing mechanisms and performance objectives.`

skill_input =`
**Langage**
- Langage A, Langage B, Langage C, Langage D, Langage E, Langage F, Langage G/H
**Langage**
- Langage A, Langage B, Langage C, Langage D, Langage E, Langage F, Langage G/H`
invalid_skill_input =`
- Langage A, Langage B, Langage C, Langage D, Langage E, Langage F, Langage G/H
**Langag
- Langage A, Langage B, Langage C, Langage D, Langage E, Langage F, Langage G/H`



skill_paragraphe = core.Paragraphe{H1: "Langage", H2: "Langage A, Langage B, Langage C, Langage D, Langage E, Langage F, Langage G/H"}

work_paragraphe = core.Paragraphe{H1: "Back-End Intern", H2: "February 2024 -- August 2024",
H3: "TechCorp", H4: "Internship",Items: []string{
"Assisted in developing and optimizing a key business process for expanding into new markets, collaborating with various teams to ensure compliance and smooth integration.",
"Participated in the migration of core backend logic from a monolithic application to a microservice architecture, improving system performance and scalability.",
"Enhanced system monitoring and reliability by implementing tracing mechanisms and performance objectives."}}

invalid_input = "ajsdlhsaeld##dafdbhkbhkjsd##"
work_expected_result = []core.Paragraphe{work_paragraphe,work_paragraphe}
skill_expected_result = []core.Paragraphe{skill_paragraphe,skill_paragraphe}

paths = []struct {
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},
}

)

func TestParagraphe(t *testing.T) {

t.Run("Work Paragraphes should return a slice of valid Paragraphe", func (t *testing.T) {
got := getParagrapheFrom(work_input)
want := work_expected_result
if !reflect.DeepEqual(got[0], want[0]){
t.Fatalf("the first Paragraphe should be :\n%s\n got :%s",want,got)
}

if !reflect.DeepEqual(got[1], want[1]){
t.Fatalf("the first Paragraphe should be :\n%s\n got :%s",want,got)
}
})

t.Run("Invalid input should return nothing", func (t *testing.T) {
result := getParagrapheFrom(invalid_input)
if result != nil{
t.Fatalf("Invalid input should return nil got %v",result)
}
})

t.Run("Skill Paragraphe should be return from valid input", func (t *testing.T) {
got := getParagrapheFrom(skill_input)
want := skill_expected_result
if !reflect.DeepEqual(got,want){
t.Fatalf("the first Paragraphe should be :\n%s\n got :%s",want,got)
}
})

t.Run("Invalid skill Paragraphe should return nil", func (t *testing.T) {
got := getParagrapheFrom(invalid_skill_input)
if got != nil{
t.Fatalf("Invalid input should return nil got %v",got)
}
})
}

func TestSections(t *testing.T) {
rootDir := t.TempDir()
for _, p := range paths {
fullPath := filepath.Join(rootDir, p.relativePath)
if err := os.MkdirAll(filepath.Dir(fullPath), os.ModePerm); err != nil {
t.Fatalf("Failed to create directories: %v", err)
}
if err := os.WriteFile(fullPath, []byte(p.content), os.ModePerm); err != nil {
t.Fatalf("Failed to write file: %v", err)
}
}

t.Run("Should return a valid list of cv", func (t *testing.T) {
got,err := GetCVsFrom(rootDir)
got[1].Print()
if err!=nil{
t.Fatalf("Failed to getCV got %s",err)
}

lang_got := got[len(got)-1].Lang
l_want := "fr"
if lang_got != l_want{
t.Fatalf("Should have %s got %s",l_want,lang_got)
}

sec_got := len(got[len(got)-1].Sections)
s_want := 3
if sec_got != s_want{
t.Fatalf("Should have %d got %d",s_want,sec_got)
}

t_sec_got := got[len(got)-1].Sections[len(got[len(got)-1].Sections)-1].Title
t_s_want := "work"
if t_sec_got != t_s_want{
t.Fatalf("Should have %s got %s",t_s_want,t_sec_got)
}

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 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)
}

})
}
147 changes: 147 additions & 0 deletions internal/adapters/input/markdown_tree_reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package input

import (
core "anemon/internal/core"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
)

var hashtagRegex = regexp.MustCompile(`^#+`)

// `GetCVFrom` takes a root directory and extracts Markdown documents to generate a list of CV type.
//
// 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) {
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 {
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
}
current_lang = info.Name()
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)
if err != nil { return err }

new_section := core.Section{Title: strings.TrimRight(info.Name(),".md"),
Paragraphes: getParagrapheFrom(string(content))}

current_sections = append(current_sections, new_section)
}
return nil
})

if err != nil {
return nil, err
}
cvs[len(cvs)-1].Sections=current_sections

return cvs,nil
}


//Take string in the md format and return a slice of core.Paragraphe for each paragraph inside of it
//We define a paragraph by text block where \n\n indicate a separation
func getParagrapheFrom(s_section string)[]core.Paragraphe{
paragraphs := make([]core.Paragraphe,0)
for _,paragraphe := range strings.Split(s_section, "\n\n"){
n_paragraphe,err := parse_paragraphe(paragraphe)
if is_empty(n_paragraphe){
continue
}
if err != nil {
fmt.Println("Failed to parse paragraphe ")
} else{
paragraphs = append(paragraphs, n_paragraphe)
}
}
if len(paragraphs) == 0 { return nil }
return paragraphs
}

//Return if a paragraphs has no header and no items
func is_empty(p core.Paragraphe)bool{
no_header := p.H1 == "" && p.H2 == "" && p.H3 == "" && p.H4 == ""
no_items := len(p.Items)==0
return no_header && no_items
}

/*
Parse parses a Markdown-like `paragraph` into a `Paragraph`,
extracting headings and descriptions based on the number of leading hashtags or stars.
Returns an error if the format is invalid.
*/
func parse_paragraphe(paragraph string) (core.Paragraphe, error) {
var (
n_paragraphe core.Paragraphe
bulletPrefix = "- "
skillAsteriskCount = 4 // Number of asterisks that signify a skill block
)
if len(strings.Split(paragraph, "\n\n")) > 1 {
return n_paragraphe, errors.New("Tried to parse multiple paragraphs into a single section")
}
wasASkill := false
lines := strings.Split(strings.TrimRight(paragraph, "\n"), "\n")
for _, line := range lines {
if len(line) == 0 {
continue
}
nbHashtags := len(hashtagRegex.FindString(line))
if wasASkill {
wasASkill = false
n_paragraphe.H2 = strings.TrimLeft(line, bulletPrefix)
continue
}
if nbHashtags == 0 && strings.HasPrefix(line, "*") && len(strings.Trim(line, "*")) == len(line)-skillAsteriskCount {
n_paragraphe.H1 = strings.Trim(line, "*")
wasASkill = true
continue
}
if err := handleLine(&n_paragraphe, line, nbHashtags); err != nil {
return n_paragraphe, err
}
}
return n_paragraphe, nil
}

// handleLine processes a line based on the number of leading hashtags
func handleLine(n_paragraphe *core.Paragraphe, line string, nbHashtags int) error {
switch {
case nbHashtags > 0 && line[nbHashtags] != ' ':
return fmt.Errorf("Err: cannot parse this md line {%s}, '#' should be followed by space", line)
case nbHashtags == 1:
n_paragraphe.H1 = line[nbHashtags+1:]
case nbHashtags == 2:
n_paragraphe.H2 = line[nbHashtags+1:]
case nbHashtags == 3:
n_paragraphe.H3 = line[nbHashtags+1:]
case nbHashtags == 4:
n_paragraphe.H4 = line[nbHashtags+1:]
case nbHashtags == 0 && len(line) > 1:
if strings.HasPrefix(line, "- ") {
n_paragraphe.Items = append(n_paragraphe.Items, strings.TrimLeft(line, "- "))
}
case nbHashtags > 4:
return fmt.Errorf("Err: cannot parse this md line {%s}", line)
}
return nil
}
52 changes: 52 additions & 0 deletions internal/core/ports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package core

import (
"fmt"
"strings"
)

//CV with Language and Sections
type CV struct {
Lang string
Sections []Section
}

type Section struct {
Title string
Paragraphes []Paragraphe
}

type Paragraphe struct {
H1 string
H2 string
H3 string
H4 string
Items []string
}

//Print Method for CV
func (cv *CV) Print() {
fmt.Println("CV Language: "+ cv.Lang)
fmt.Println(strings.Repeat("=", 40))
fmt.Printf("With %d Sections\n",len(cv.Sections))
for _, section := range cv.Sections {
fmt.Printf("Section: %s\n", section.Title)
fmt.Println(strings.Repeat("-", 40))
for _, p := range section.Paragraphes {
if p.H1 != "" { fmt.Printf("H1: %s\n", p.H1) }else{fmt.Printf("No H1")}
if p.H2 != "" { fmt.Printf(" H2: %s\n", p.H2) }else{fmt.Printf("No H2")}
if p.H3 != "" { fmt.Printf(" H3: %s\n", p.H3) }else{fmt.Printf("No H3")}
if p.H4 != "" { fmt.Printf(" H4: %s\n", p.H4) }else{fmt.Printf("No H4")}

if len(p.Items) > 0 {
fmt.Println(" Items:")
for _, item := range p.Items {
fmt.Printf(" - %s\n", item)
}
}
fmt.Println()
}
fmt.Println()
}
}

5 changes: 2 additions & 3 deletions internal/walker/cv.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import (
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 {
return err
}
println(info.Name())
println(info.IsDir())
if !info.IsDir() && strings.HasSuffix(info.Name(), ".md") {
relativePath := strings.TrimSuffix(strings.TrimPrefix(path, root+string(os.PathSeparator)), ".md")
content, err := os.ReadFile(path)
Expand Down

0 comments on commit e265514

Please sign in to comment.