Intermediate commit

- Following info is now parsed from yaml, specified with --config flag
  default path for config: ./data/resume.yaml
  - Summary
  - Experience
  - Education
  - Skills
  - Languages

Need to update sample YAML with mock data
This commit is contained in:
Jarno Rankinen 2024-02-01 23:05:17 +02:00
parent 67215cf921
commit 8b7978f7b5
7 changed files with 185 additions and 49 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
resume.yaml

27
main.go
View File

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

View File

@ -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 += "<span class=\"tag is-info is-success is-size-6\" style=\"color: black\">☆</span>"
}
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"`
}

View File

@ -33,6 +33,7 @@ body {
.education .item,
.projects .item,
.skills .item,
.languages .item,
.awards .item {
padding: .5em 0; }

View File

@ -34,6 +34,7 @@ body {
.education,
.projects,
.skills,
.languages,
.awards {
.item {
padding: .5em 0;

View File

@ -38,6 +38,7 @@ body {
.education .item,
.projects .item,
.skills .item,
.languages .item,
.awards .item {
padding: .5em 0; }

View File

@ -4,6 +4,22 @@
<head>
{{ template "meta" . }}
<link rel="stylesheet" href="/static/css/{{ .Theme }}-style.css" />
<style>
{{ if .Profile.Skills }}
.skilltag {
display: inline-block;
width: {{ .Profile.GetTagWidth "skills" }};
}
{{ end }}
{{ if .Profile.Languages }}
.languagetag {
display: inline-block;
width: {{ .Profile.GetTagWidth "languages" }};
}
{{ end }}
</style>
</head>
<body id="body-app">
@ -21,7 +37,7 @@
</div>
<div class="column is-2">
<figure class="image image is-128x128">
<img alt="Profile photo" class="is-rounded" src="{{ .Profile.Photo }}" /> <!--<%= content.data.profile.photo %>" />-->
<img alt="Profile photo" class="is-rounded" src="{{ .Profile.Photo }}" />
</figure>
</div>
<div class="column has-text-grey-light has-text-right-in-desktop">
@ -55,55 +71,72 @@
<div class="title is-size-5 has-text-primary has-text-weight-bold">
SUMMARY
</div>
<div class="wrapper"><%- content.data.summary %></div>
<div class="wrapper">{{ .Profile.Summary }}</div>
</div>
{{ with .Profile.Experience }}
<div class="experience">
<div class="title is-size-5 has-text-primary has-text-weight-bold">
EXPERIENCE
</div>
<% content.data.experiences.forEach(function(experience){ %>
<!--<% content.data.experiences.forEach(function(experience){ %>-->
{{ range . }}
<div class="item">
<div class="is-size-5">
<b><%= experience.company %>,</b>
<span class="has-text-weight-semi-bold">
<%= experience.location %> — <i><%= experience.title %></i>
</span>
<!-- <div class="columns is-gapless">-->
<!-- <div class="column">-->
<span><b>{{ .Company }}</b></span>
<!-- </div>-->
<!-- <div class="column">-->
<span class="has-text-weight-semi-bold"> - <i>{{ .Title }}</i></span>
<!-- </div>-->
<!-- </div>-->
</div>
<div class="is-size-7 tag is-primary"><%= experience.period %></div>
<% if (experience.jobdesc.length > 0) { %>
<div>{{ .Location }}</div>
<div class="is-size-6 tag is-primary">{{ .Period }}</div>
{{ with .Description }}
<ul style="list-style: disc; margin-left: 1em">
<% experience.jobdesc.forEach(function(job){ %>
<li><%= job %></li>
<% }); %>
{{ range . }}
<li>{{ . | html }}</li>
{{ end }}
</ul>
<% } %>
{{ end }}
</div>
<% }); %>
{{ end }}
</div>
{{ end }}
{{ with .Profile.Education }}
<div class="education">
<div class="title is-size-5 has-text-primary has-text-weight-bold">
EDUCATION
</div>
<% content.data.educations.forEach(function(education){ %>
{{ range . }}
<div class="item">
<div class="is-size-5">
<b><%= education.name %></b>,
<span class="has-text-weight-semi-bold">
<%= education.city %> — <i><%= education.degree %></i>
</span>
<!-- <div class="columns is-gapless">-->
<!-- <div class="column">-->
<span><b>{{ .Degree }}</b>, </span>
<!-- </div>-->
<!-- <div class="column">-->
<span class="has-text-weight-semi-bold">{{ .Name }}</span>
<!-- </div>-->
<!-- </div>-->
</div>
<div>{{ .Faculty }}
<div class="is-size-6 tag is-primary ml-5">{{ .Period }}</div>
</div>
<div class="is-size-7 tag is-primary"><%= education.period %></div>
<div><%= education.faculty %></div>
</div>
<% }); %>
{{ end }}
</div>
{{ end }}
</div>
{{ if or .Profile.Projects (or .Profile.Skills .Profile.Languages) }}
<div class="column">
{{ with .Profile.Projects }}
<div class="projects">
<div class="title is-size-5 has-text-primary has-text-weight-bold">
PROJECTS
@ -116,34 +149,57 @@
<i><%= proj.company %></i>
</div>
<div class="is-size-7 tag is-primary"><%= proj.period %></div>
<div class="is-size-6 tag is-primary"><%= proj.period %></div>
</div>
<% }); %>
</div>
{{ end }}
{{ with .Profile.Skills }}
<div class="skills">
<div class="title is-size-5 has-text-primary has-text-weight-bold">
SKILLS
</div>
<div class="wrapper">
<% content.data.skills.forEach(function(skill){ %>
{{ range . }}
<div class="item">
<div><%= skill.name %></div>
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="skilltag tag is-size-6">{{ .Name }}</span>
{{ .VisualLevel .Level }}
</div>
</div>
</div>
</div>
<% }); %>
{{ end }}
</div>
</div>
{{ end }}
{{ with .Profile.Languages }}
<div class="languages">
<div class="title is-size-5 has-text-primary has-text-weight-bold">
LANGUAGES
</div>
<div class="wrapper">
<%= content.data.languages.join(', ') %>
{{ range . }}
<div class="item">
<div class="field is-grouped is-grouped-multiline">
<div class="control">
<div class="tags has-addons">
<span class="languagetag tag is-size-6">{{ .Name }}</span>
{{ .VisualLevel .Level }}
</div>
</div>
</div>
</div>
{{ end }}
</div>
</div>
{{ end }}
</div>
{{ end }}
</div>
</div>
</section>