diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0568402 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +resume.yaml diff --git a/main.go b/main.go index 8ebcbcd..644ffde 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,12 @@ package main import ( "embed" + "flag" + "fmt" "html/template" "log/slog" "net/http" + "os" "slices" "strings" "time" @@ -13,9 +16,18 @@ import ( //go:embed "static/css/*.css" "templates/*.html" var static embed.FS var templates map[string]*template.Template +var configFile string func main() { - readConfig("") + cfgFile := flag.String("config", "./data/resume.yaml", "Path to the configuration YAML") + flag.Parse() + + _, err := readConfig(*cfgFile) + if err != nil { + slog.Error(fmt.Sprintf("error reading configuration: %s", err.Error())) + os.Exit(1) + } + configFile = *cfgFile staticFileServer := http.FileServer(http.FS(static)) mux := http.NewServeMux() mux.HandleFunc("/", home) @@ -24,7 +36,7 @@ func main() { mux.Handle("/static/", staticFileServer) slog.Info("Starting go-resume server, listening on port 3000") - err := http.ListenAndServe(":3000", mux) + err = http.ListenAndServe("0.0.0.0:3000", mux) slog.Error(err.Error()) } @@ -43,7 +55,7 @@ func home(w http.ResponseWriter, r *http.Request) { slog.Error(err.Error()) http.Error(w, "Server error", http.StatusInternalServerError) } - data, err := readConfig("") + data, err := readConfig(configFile) if err != nil { slog.Error(err.Error()) http.Error(w, "Server error", http.StatusInternalServerError) @@ -54,9 +66,18 @@ func home(w http.ResponseWriter, r *http.Request) { data.Theme = urlSlice[len(urlSlice)-1] } data.Year = time.Now().Format("2006") + tmpl.Funcs(templateFunctions()) err = tmpl.Execute(w, *data) if err != nil { slog.Error(err.Error()) http.Error(w, "Server error", http.StatusInternalServerError) } } + +func templateFunctions() template.FuncMap { + return template.FuncMap{ + "html": func(raw string) template.HTML { + return template.HTML(raw) + }, + } +} diff --git a/resume.go b/resume.go index d2ea680..283e668 100644 --- a/resume.go +++ b/resume.go @@ -1,5 +1,10 @@ package main +import ( + "fmt" + "html/template" +) + type Resume struct { Theme string `yaml:"theme"` Meta *Meta `yaml:"meta"` @@ -8,16 +13,45 @@ type Resume struct { } type Profile struct { - Name string `yaml:"name"` - Title string `yaml:"title"` - City string `yaml:"city"` - Phone string `yaml:"phone"` - Email string `yaml:"email"` - Socials Socials `yaml:"socials"` - Photo string `yaml:"photo"` + Name string `yaml:"name"` + Title string `yaml:"title"` + City string `yaml:"city"` + Phone string `yaml:"phone"` + Email string `yaml:"email"` + Socials Socials `yaml:"socials"` + Photo string `yaml:"photo"` + Summary string `yaml:"summary"` + Experience []Job `yaml:"experience"` + Education []Education `yaml:"education"` + Skills []Skill `yaml:"skills"` + Projects any `yaml:"projects"` + Languages []Language `yaml:"languages"` } -type job struct { +func (p *Profile) GetTagWidth(t string) string { + l := 0 + switch t { + case "skills": + for _, skill := range p.Skills { + if len(skill.Name) > l { + l = len(skill.Name) + } + } + case "languages": + for _, skill := range p.Languages { + if len(skill.Name) > l { + l = len(skill.Name) + } + } + } + return fmt.Sprintf("%dch", l) +} + +func (job *Job) html(raw string) template.HTML { + return template.HTML(raw) +} + +type Job struct { Company string `yaml:"company"` Location string `yaml:"location"` Title string `yaml:"title"` @@ -25,24 +59,40 @@ type job struct { Description []string `yaml:"description"` } -type jobs []job - -type education struct { +type Education struct { Name string `yaml:"name"` - City string `yaml:"city"` Degree string `yaml:"degree"` - Period string `yaml:"period"` Faculty string `yaml:"faculty"` + City string `yaml:"city"` + Period string `yaml:"period"` } -type educations []education - -type skill struct { +type Skill struct { Name string `yaml:"name"` - Level string `yaml:"level"` + Level int `yaml:"level"` } -type skills []skill +func _visualLevel(level int) template.HTML { + tagString := "" + if level > 5 { + level = 5 + } else if level < 1 { + level = 1 + } + for i := 0; i < level; i++ { + tagString += "" + } + return template.HTML(tagString) +} +func (skill *Skill) VisualLevel(level int) template.HTML { + return _visualLevel(level) +} + +func (language *Language) VisualLevel(level int) template.HTML { + return _visualLevel(level) +} + +type skills []Skill type Social struct { Media string `yaml:"media"` @@ -60,3 +110,8 @@ type Meta struct { Robots string `yaml:"robots"` ThemeColor string `yaml:"theme-color"` } + +type Language struct { + Name string `yaml:"name"` + Level int `yaml:"level"` +} diff --git a/static/css/base.css b/static/css/base.css index 17cb677..53b17b9 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -33,6 +33,7 @@ body { .education .item, .projects .item, .skills .item, +.languages .item, .awards .item { padding: .5em 0; } diff --git a/static/css/base.scss b/static/css/base.scss index 7e32004..601d651 100644 --- a/static/css/base.scss +++ b/static/css/base.scss @@ -34,6 +34,7 @@ body { .education, .projects, .skills, +.languages, .awards { .item { padding: .5em 0; diff --git a/static/css/light-style.css b/static/css/light-style.css index ef80c07..8df5254 100644 --- a/static/css/light-style.css +++ b/static/css/light-style.css @@ -38,6 +38,7 @@ body { .education .item, .projects .item, .skills .item, +.languages .item, .awards .item { padding: .5em 0; } diff --git a/templates/index.html b/templates/index.html index 8d7fe43..baf28da 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,6 +4,22 @@ {{ template "meta" . }} + + + @@ -21,7 +37,7 @@
- Profile photo + Profile photo
@@ -55,55 +71,72 @@
SUMMARY
-
<%- content.data.summary %>
+
{{ .Profile.Summary }}
+ {{ with .Profile.Experience }}
EXPERIENCE
- <% content.data.experiences.forEach(function(experience){ %> + + {{ range . }}
- <%= experience.company %>, - - <%= experience.location %> — <%= experience.title %> - + + + {{ .Company }} + + + - {{ .Title }} + +
-
<%= experience.period %>
- <% if (experience.jobdesc.length > 0) { %> +
{{ .Location }}
+
{{ .Period }}
+ {{ with .Description }} - <% } %> + {{ end }}
- <% }); %> + {{ end }}
+ {{ end }} + {{ with .Profile.Education }}
EDUCATION
- <% content.data.educations.forEach(function(education){ %> + {{ range . }}
- <%= education.name %>, - - <%= education.city %> — <%= education.degree %> - + + + {{ .Degree }}, + + + {{ .Name }} + + +
+
{{ .Faculty }} +
{{ .Period }}
-
<%= education.period %>
-
<%= education.faculty %>
- <% }); %> + {{ end }}
+ {{ end }} + {{ if or .Profile.Projects (or .Profile.Skills .Profile.Languages) }}
+ {{ with .Profile.Projects }}
PROJECTS @@ -116,34 +149,57 @@ — <%= proj.company %>
-
<%= proj.period %>
+
<%= proj.period %>
<% }); %>
+ {{ end }} + {{ with .Profile.Skills }}
SKILLS
- <% content.data.skills.forEach(function(skill){ %> + {{ range . }}
-
<%= skill.name %>
+
+
+
+ {{ .Name }} + {{ .VisualLevel .Level }} +
+
+
- <% }); %> + {{ end }}
+ {{ end }} + {{ with .Profile.Languages }}
LANGUAGES
- <%= content.data.languages.join(', ') %> + {{ range . }} +
+
+
+
+ {{ .Name }} + {{ .VisualLevel .Level }} +
+
+
+
+ {{ end }}
- + {{ end }} + {{ end }}