commit a5c24c4e46f272c305030226501d27ea37f56e72 Author: Arnaud Ysmal Date: Mon May 7 00:58:03 2018 +0200 Initial import diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..00b12b5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "resume-schema"] + path = resume-schema + url = https://github.com/jsonresume/resume-schema/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..3d78821 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +jsonresume written in go +------------------------ + +kendall and kendallfr themes are based on https://github.com/LinuxBozo/jsonresume-theme-kendall. + +`$ go build -o jsonresume ./cli` +`$ ./jsonresume -t kendall -r resume-schema/examples/valid/complete.json -o resume.html && cp themes/kendall/*.css .` diff --git a/cli/resume.go b/cli/resume.go new file mode 100644 index 0000000..384b37a --- /dev/null +++ b/cli/resume.go @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018 Arnaud Ysmal. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "jsonresume/model" + "jsonresume/themes" + _ "jsonresume/themes/kendall" + _ "jsonresume/themes/kendallfr" +) + +func main() { + var r *model.Resume + var resume string + var theme string + var err error + var outf string + var f *os.File + + flag.StringVar(&resume, "resume", "resume.json", "JSON of resume") + flag.StringVar(&resume, "r", "resume.json", "JSON of resume") + flag.StringVar(&theme, "theme", "", "Theme of resume") + flag.StringVar(&theme, "t", "", "Theme of resume") + flag.StringVar(&outf, "out", "-", "Output file") + flag.StringVar(&outf, "o", "-", "Output file") + flag.Parse() + + if outf == "-" { + f = os.Stdout + } else { + if f, err = os.OpenFile(outf, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755); err != nil { + log.Fatal(err) + } + } + if r, err = model.Parse(resume); err != nil { + log.Fatal(err) + } + + if t, exists := themes.Themes[theme]; exists { + if err = t.Render(r, f); err != nil { + log.Fatal(err) + } + } else { + fmt.Printf("No such theme\n") + } +} diff --git a/model/json.go b/model/json.go new file mode 100644 index 0000000..1693cc4 --- /dev/null +++ b/model/json.go @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2018 Arnaud Ysmal. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +package model + +import ( + "encoding/json" + "io/ioutil" + "strings" + "time" +) + +const ISO8601DateLayout = "2006-01-02" + +type ResumeDate struct { + time.Time +} + +func (ct *ResumeDate) UnmarshalJSON(b []byte) (err error) { + s := strings.Trim(string(b), "\"") + if s == "null" { + ct.Time = time.Time{} + return + } + ct.Time, err = time.Parse(ISO8601DateLayout, s) + return +} + +// Resume represent Curriculum Vitae +type Resume struct { + Title string `json:"title"` + Basic Basic `json:"basics"` + Work []Work `json:"work"` + Volunteer []Volunteer `json:"volunteer"` + Education []Education `json:"education"` + Awards []Award `json:"awards"` + Publications []Publication `json:"publications"` + Skills []Skill `json:"skills"` + Languages []Language `json:"languages"` + Interests []Interest `json:"interests"` + References []Reference `json:"references"` + Projects []Project `json:"projects"` +} + +// Basic is the basic information for a resume +type Basic struct { + Name string `json:"name"` + Label string `json:"label"` + Image string `json:"image"` + Email string `json:"email"` + Phone string `json:"phone"` + URL string `json:"url"` + Summary string `json:"summary"` + ResumeLocation Location `json:"location"` + Profiles []SocialProfile `json:"profiles"` +} + +// Location is the location details of a resume owner. +type Location struct { + Address string `json:"address"` + PostalCode string `json:"postalCode"` + City string `json:"city"` + CountryCode string `json:"countryCode"` + Region string `json:"region"` +} + +// SocialProfile is the profile of the resume owner. +type SocialProfile struct { + Network string `json:"network"` + UserName string `json:"username"` + URL string `json:"url"` +} + +// Work is the work details of the resume owner. +type Work struct { + Name string `json:"name"` + WorkLocation string `json:"location"` + Description string `json:"description"` + Position string `json:"position"` + URL string `json:"url"` + StartDate ResumeDate `json:"startDate"` + EndDate ResumeDate `json:"endDate"` + Summary string `json:"summary"` + Highlights []string `json:"highlights"` +} + +// Volunteer is the volunteer details of the resume owner. +type Volunteer struct { + Organization string `json:"organization"` + Position string `json:"position"` + URL string `json:"url"` + StartDate ResumeDate `json:"startDate"` + EndDate ResumeDate `json:"endDate"` + Summary string `json:"summary"` + Highlights []string `json:"highlights"` +} + +// Education is the education details of the resume owner. +type Education struct { + Institution string `json:"institution"` + Area string `json:"area"` + StudyType string `json:"studyType"` + StartDate ResumeDate `json:"startDate"` + EndDate ResumeDate `json:"endDate"` + GPA string `json:"gpa"` + Courses []string `json:"courses"` +} + +// Award is the award details of the resume owner. +type Award struct { + Title string `json:"title"` + Date ResumeDate `json:"date"` + Awarder string `json:"awarder"` + Summary string `json:"summary"` +} + +// Publication is the publication details of the resume owner. +type Publication struct { + Name string `json:"name"` + Publisher string `json:"publisher"` + ReleaseDate ResumeDate `json:"releaseDate"` + URL string `json:"url"` + Summary string `json:"summary"` +} + +// Skill is the skill details of the resume owner. +type Skill struct { + Name string `json:"name"` + Level string `json:"level"` + Keywords []string `json:"keywords"` +} + +// Language is the language details of the resume owner. +type Language struct { + Language string `json:"language"` + Fluency string `json:"fluency"` +} + +// Interest is the interest details of the resume owner. +type Interest struct { + Name string `json:"name"` + Keywords []string `json:"keywords"` +} + +// Referee is the details about a person who is refereeing for the resume owner. +type Reference struct { + Name string `json:"name"` + Reference string `json:"reference"` +} + +// Volunteer is the volunteer details of the resume owner. +type Project struct { + Name string `json:"name"` + Description string `json:"description"` + Highlights []string `json:"highlights"` + Keywords []string `json:"keywords"` + StartDate ResumeDate `json:"startDate"` + EndDate ResumeDate `json:"endDate"` + URL string `json:"url"` + Roles []string `json:"roles"` + Entity string `json:"entity"` + Type string `json:"type"` +} + +func Parse(f string) (*Resume, error) { + var content []byte + var err error + var r Resume + + if content, err = ioutil.ReadFile(f); err != nil { + return nil, err + } + if err = json.Unmarshal(content, &r); err != nil { + return nil, err + } + return &r, nil +} diff --git a/resume-schema b/resume-schema new file mode 160000 index 0000000..8558e9e --- /dev/null +++ b/resume-schema @@ -0,0 +1 @@ +Subproject commit 8558e9e6e4badade1c871d5cfd253a740dbf3b34 diff --git a/themes/README.md b/themes/README.md new file mode 100644 index 0000000..8a12399 --- /dev/null +++ b/themes/README.md @@ -0,0 +1 @@ +kendall and kendallfr are based on https://github.com/LinuxBozo/jsonresume-theme-kendall diff --git a/themes/kendall/kendall.go b/themes/kendall/kendall.go new file mode 100644 index 0000000..7b25853 --- /dev/null +++ b/themes/kendall/kendall.go @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018 Arnaud Ysmal. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +package kendall + +import ( + "fmt" + "jsonresume/model" + "jsonresume/themes" + "strings" + "text/template" +) + +var Theme = themes.Theme{ + Name: "kendall", + Functions: template.FuncMap{ + "iconClass": iconClass, + "formatDateWork": formatDateWork, + "formatDateEdu": formatDateEdu, + "formatDatePub": formatDatePub, + }, + Template: "themes/kendall/resume.template", +} + +func init() { + (&Theme).Register() +} +func iconClass(network string) string { + network = strings.ToLower(network) + switch network { + // special cases + case "google-plus": + case "googleplus": + return "fa fa-google-plus" + case "flickr": + case "flicker": + return "fa fa-flickr" + case "dribbble": + case "dribble": + return "fa fa-dribbble" + case "codepen": + return "fa fa-codepen" + case "soundcloud": + return "fa fa-soundcloud" + case "reddit": + return "fa fa-reddit" + case "tumblr": + case "tumbler": + return "fa fa-tumblr" + case "stack-overflow": + case "stackoverflow": + return "fa fa-stack-overflow" + case "blog": + case "rss": + return "fa fa-rss" + case "gitlab": + return "fa fa-gitlab" + case "keybase": + return "fa fa-key" + default: + return "fa fa-" + network + } + + return "fa fa-" + network +} + +func formatDateWork(date model.ResumeDate) string { + if date.IsZero() { + return "Present" + } + return fmt.Sprintf("%s %d", date.Month().String(), date.Year()) +} + +func formatDateEdu(date model.ResumeDate) string { + if date.IsZero() { + return "Present" + } + return fmt.Sprintf("%d", date.Year()) +} + +func formatDatePub(date model.ResumeDate) string { + if date.IsZero() { + return "Present" + } + return fmt.Sprintf("%d %s %d", date.Day(), date.Month().String(), date.Year()) +} diff --git a/themes/kendall/print.css b/themes/kendall/print.css new file mode 100644 index 0000000..a13418f --- /dev/null +++ b/themes/kendall/print.css @@ -0,0 +1,91 @@ +body { + font-size: .95em; + -webkit-print-color-adjust: exact; +} + +a[href]:after { + content: none !important; +} + +#photo{ + display: none; +} + +.box { + margin-bottom: -10px; +} + +blockquote, +#education, +#awards, +.contact-item, +.publication, +.skills, +.interests { + page-break-inside: avoid; +} + +.col-sm-5{ + width: 40%; + padding: 0 15px; +} + +.col-sm-7{ + width: 60%; + padding: 0 15px; +} + +.skills .col-sm-offset-1, +.interests .col-sm-offset-1{ + margin-top: -10px; + margin-bottom: 5px; +} + +#education { + margin: 0; + margin-bottom: -20px; +} +#awards:before, +#education:before { + background: none; +} + +#awards .description, +#education .description, +.job .details { + border: 1px solid #eee; +} +.publication, +.publication .panel-heading, +.publication .name{ + margin: 0; + padding: 0 5px; + border: none; +} +.publication .panel-body { + padding: 0 10px; + margin: 0; +} + +.badge { + margin: 0; +} + +.list-group-item{ + border: none; + margin: 0; + padding: 5px 15px; +} +.list-group-item:after{ + content: ''; + position: absolute; + top: 8px; + right: 0; + left: -1px; + height: 0; + width: 0; + border: solid transparent; + border-right-color: #999; + border-width: 4px; + pointer-events: none; + } diff --git a/themes/kendall/resume.template b/themes/kendall/resume.template new file mode 100644 index 0000000..8ba7b81 --- /dev/null +++ b/themes/kendall/resume.template @@ -0,0 +1,292 @@ + + + + + + + + Resume of {{.Basic.Name}} + + + + + + +
+
+
+
+ + {{if .Basic.Image -}} +
+ avatar +
+ {{end -}} +
+

{{.Basic.Name}}
{{if .Basic.Label}}{{.Basic.Label}}{{end -}}

+
+
+
+
+
+
+ {{if .Basic.Summary -}} + +
+

About

+

{{.Basic.Summary}}

+
+ {{end -}} + {{if .Work -}} + +
+

Work Experience

+ {{range .Work -}} +
+
+
+
{{.Name}}
+ {{if .URL -}} + + {{end -}} +
{{formatDateWork .StartDate}} – {{formatDateWork .EndDate}}
+
+
+
+
+
{{.Position}}
+
+ {{.Summary -}} + {{if .Highlights -}} +
+
    + {{range .Highlights -}} +
  • {{.}}
  • + {{end -}} +
+ {{end -}} +
+
+
+
+ {{end -}} +
+ {{end -}} + {{if .Awards -}} + +
+

Awards

+
    + {{range .Awards -}} +
  • +
    {{formatDatePub .Date}}
    +
    +

    {{.Awarder}}

    +

    {{.Title}}

    +

    {{.Summary}}

    +
    +
  • + {{end -}} +
+
+ {{end -}} + {{if .Volunteer -}} + +
+

Volunteer

+ {{range .Volunteer -}} +
+
+
+
{{.Organization}}
+ {{if .URL -}} + + {{end -}} +
{{formatDateWork .StartDate}} – {{formatDateWork .EndDate}}
+
+
+
+
+
{{.Position}}
+
+ {{.Summary -}} + {{if .Highlights -}} +
+
    + {{range .Highlights -}} +
  • {{.}}
  • + {{end -}} +
+ {{end -}} +
+
+
+
+ {{end -}} +
+ {{end -}} +
+
+ +
+

Contact

+ {{if .Basic.ResumeLocation.City -}} +
+
+ {{if .Basic.ResumeLocation.Address}}
{{.Basic.ResumeLocation.Address}}
{{end -}} +
{{.Basic.ResumeLocation.City}}{{if .Basic.ResumeLocation.Region}}, {{.Basic.ResumeLocation.Region}}{{end -}}{{if .Basic.ResumeLocation.PostalCode}} {{.Basic.ResumeLocation.PostalCode}}{{end -}}{{if .Basic.ResumeLocation.CountryCode}} {{.Basic.ResumeLocation.CountryCode}}{{end -}}
+
+ {{end -}} + {{if .Basic.Phone -}} +
+
+
{{.Basic.Phone}}
+
+ {{end -}} + {{if .Basic.Email -}} + + {{end -}} + {{if .Basic.URL -}} + + {{end -}} + {{range .Basic.Profiles -}} + + {{end -}} +
+ {{if .Education -}} + +
+

Education

+
    + {{range .Education -}} +
  • +
    {{formatDateEdu .StartDate}} {{formatDateEdu .EndDate}}
    +
    +

    {{.Institution}}

    + {{if .StudyType}}

    {{.StudyType}}

    {{end}} +

    {{.Area}}

    + {{if .GPA -}} +

    + GPA: {{.GPA}} +

    + {{end -}} + {{if .Courses -}} +
    Courses
    +
      + {{range .Courses -}} +
    • {{.}}
    • + {{end -}} +
    + {{end -}} +
    +
  • + {{end -}} +
+
+ {{end -}} + {{if .Skills -}} + +
+

Skills

+ {{range .Skills -}} +
+
+ {{.Name}} + {{if .Level}}{{.Level}}{{end -}} +
+
+ {{range .Keywords -}} + {{.}} + {{end -}} +
+
+ {{end -}} +
+ {{end -}} + {{if .Publications -}} + +
+

Publications

+ {{range .Publications -}} +
+
+
{{.Name}}
+
+
+ {{if .Publisher -}} +
{{.Publisher}}
+ {{end -}} +
{{formatDatePub .ReleaseDate}}
+ {{if .URL -}} + + {{end -}} + {{if .Summary -}} +

{{.Summary}}

+ {{end -}} +
+
+ {{end -}} +
+ {{end -}} + {{if .Languages -}} + +
+

Languages

+
    + {{range .Languages -}} +
  • {{.Language}}{{.Fluency}}
  • + {{end -}} +
+
+ {{end -}} + {{if .Interests -}} + +
+

Interests

+ {{range .Interests -}} +
+
+ {{.Name -}} +
+
+ {{range .Keywords -}} + {{.}} + {{end -}} +
+
+ {{end -}} +
+ {{end -}} + {{if .References -}} +
+

References

+ {{range .References -}} +
+
{{.Reference}}
+
{{.Name}}
+

+ {{end -}} +
+ {{end -}} +
+
+
+ + + diff --git a/themes/kendall/style.css b/themes/kendall/style.css new file mode 100644 index 0000000..45c3513 --- /dev/null +++ b/themes/kendall/style.css @@ -0,0 +1,276 @@ +@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,600,700,800); +@charset "utf-8"; +@-webkit-viewport { width: device-width; } +@-moz-viewport { width: device-width; } +@-ms-viewport { width: device-width; } +@-o-viewport { width: device-width; } +@viewport { width: device-width; } + +body{ + font-family: 'Open Sans', Arial, Tahoma; + font-weight: 400; + color: #363636; + background: #334960; +} +blockquote { + font-size: 1em; +} + +.container{ + margin-top: 80px; + margin-bottom: 15px; + background: #fff; +} + +#photo-header{ + margin-top: -75px; +} +#photo{ + width: 160px; + height: 160px; + border-radius: 50%; + overflow: hidden; + padding: 5px; + background: #334960; + display: inline-block; +} +#photo img{ + width: 150px; + height: 150px; + border-radius: 50%; +} +#text-header h1{ + margin: 0; + padding: 0; + font-size: 1.5em; + font-weight: 700; + text-transform: uppercase; + letter-spacing: -1px; +} +#text-header h1::first-line{ + font-size: 1.5em; + font-weight: 800; + line-height: 1.5em; +} +#text-header h1 span{ + color: #334960; + opacity: 0.7; +} +#text-header h1 sup{ + opacity: 0.5; +} +#text-header:after{ + width: 100%; + height: 3px; + border-bottom: 1px solid #ddd; + margin-top: 15px; + content: ''; + display: block; +} + +.box{ + padding-bottom: 10px; + margin-bottom: 25px; +} +.box h2{ + color: #227c74; + font-size: 1.5em; + font-weight: 700; + text-transform: uppercase; +} + +#awards, +#education{ + margin-top: 20px; + margin-bottom: 0; + position: relative; + padding: 1em 0; + list-style: none; +} +#awards:before, +#education:before { + width: 5px; + height: 100%; + position: absolute; + left: 35px; + top: 0; + content: ' '; + display: block; + background: #32475c; + background: -moz-linear-gradient(top, #ffffff 0%, #32475c 7%, #32475c 89%, #ffffff 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(7%,#32475c), color-stop(89%,#32475c), color-stop(100%,#ffffff)); + background: -webkit-linear-gradient(top, #ffffff 0%,#32475c 7%,#32475c 89%,#ffffff 100%); + background: -o-linear-gradient(top, #ffffff 0%,#32475c 7%,#32475c 89%,#ffffff 100%); + background: -ms-linear-gradient(top, #ffffff 0%,#32475c 7%,#32475c 89%,#ffffff 100%); + background: linear-gradient(to bottom, #ffffff 0%,#32475c 7%,#32475c 89%,#ffffff 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ffffff',GradientType=0 ); +} +#awards li, +#education li{ + width: 100%; + z-index: 2; + position: relative; + float: left; +} +#awards .year, +#education .year{ + width: 14%; + background: #fff; + padding: 10px; + font-weight: 700; + display: inline-block; +} +#awards .description, +#education .description{ + width: 83%; + display: inline-block; + background: #eee; + margin-bottom: 10px; + position: relative; + padding: 10px; + border-bottom: 1px solid #ccc; + border-right: 1px solid #ccc; +} +#awards .description:after, +#education .description:after { + content: ''; + position: absolute; + top: 15px; + right: 0; + left: -16px; + height: 0; + width: 0; + border: solid transparent; + border-right-color: #eee; + border-width: 8px; + pointer-events: none; +} +#awards .description h3, +#education .description h3{ + font-size: 1.2em; + margin: 0; + padding: 0; + font-weight: 700; +} +#awards .description p, +#education .description p{ + margin-top: 5px; + padding: 0; +} + +.job{ + margin-bottom: 15px; +} +.job .details { + margin-left: 3%; + width: 95%; + padding: 10px; + margin-bottom: 10px; + background: #eee; + border-bottom: 1px solid #ccc; + border-right: 1px solid #ccc; +} +.job .where{ + font-size: 1.2em; + font-weight: bold; +} +.job .year{ + opacity: 0.7; +} +.job .profession{ + font-size: 1.2em; + font-weight: bold; +} +.job .description{ + line-height: 1.5em; +} +.job .highlights{ + padding: 5px 0; + font-weight: bold; +} +.job .job-details { + padding-left: 5%; + width: 100%; +} +.publication { + margin-bottom: 0; +} +.publication .name{ + font-size: 1em; + font-weight: bold; +} +.publication .year{ + opacity: 0.7; +} +.publication p{ + margin: 0; + padding-top: 10px; +} + +.contact-item{ + width: 100%; + float: left; +} +.contact-item .icon{ + padding: 10px; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + color: #32475c; + background: #eee; +} +.contact-item:last-child .icon{ + border-bottom: none; +} +.contact-item .title{ + width: 80%; + width: calc(100% - 55px); + font-weight: 700; + opacity: 0.9; +} +.contact-item .title.only{ + margin-top: 10px; +} +.contact-item .description{ + width: 80%; + width: calc(100% - 55px); + color: #334960; +} + +.item-interests, +.item-skills{ + height: 30px; + color: #334960; + padding: 5px 10px; + margin-bottom: 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 1.1em; + font-weight: 600; +} +.interest, +.skill{ + color: #fff; + display: inline-block; + margin-right: 5px; + margin-bottom: 5px; + padding: 5px 10px; + background: #32475c; + position: relative; + font-size: .85em; +} +.skill-level { + background-color: #227c74; + border-radius: 4px; + color: #fff; + padding: 1px 8px; + font-size: .75em; + position: absolute; + margin: 1px 10px; +} + +#language-skills .skill{ + margin: 10px 0; + padding-bottom: 10px; + border-bottom: 1px solid #eee; +} diff --git a/themes/kendallfr/kendallfr.go b/themes/kendallfr/kendallfr.go new file mode 100644 index 0000000..114bd5f --- /dev/null +++ b/themes/kendallfr/kendallfr.go @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018 Arnaud Ysmal. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +package kendallfr + +import ( + "fmt" + "jsonresume/model" + "jsonresume/themes" + "strings" + "text/template" +) + +var Theme = themes.Theme{ + Name: "kendallfr", + Functions: template.FuncMap{ + "iconClass": iconClass, + "formatDateWork": formatDateWork, + "formatDateEdu": formatDateEdu, + "formatDatePub": formatDatePub, + }, + Template: "themes/kendallfr/resume.template", +} + +func init() { + (&Theme).Register() +} +func iconClass(network string) string { + network = strings.ToLower(network) + switch network { + // special cases + case "google-plus": + case "googleplus": + return "fa fa-google-plus" + case "flickr": + case "flicker": + return "fa fa-flickr" + case "dribbble": + case "dribble": + return "fa fa-dribbble" + case "codepen": + return "fa fa-codepen" + case "soundcloud": + return "fa fa-soundcloud" + case "reddit": + return "fa fa-reddit" + case "tumblr": + case "tumbler": + return "fa fa-tumblr" + case "stack-overflow": + case "stackoverflow": + return "fa fa-stack-overflow" + case "blog": + case "rss": + return "fa fa-rss" + case "gitlab": + return "fa fa-gitlab" + case "keybase": + return "fa fa-key" + default: + return "fa fa-" + network + } + + return "fa fa-" + network +} + +func getMonth(date model.ResumeDate) string { + switch date.Month() { + case 1: + return "Janvier" + case 2: + return "Février" + case 3: + return "Mars" + case 4: + return "Avril" + case 5: + return "Mai" + case 6: + return "Juin" + case 7: + return "Juillet" + case 8: + return "Août" + case 9: + return "Septembre" + case 10: + return "Octobre" + case 11: + return "Novembre" + case 12: + return "Décembre" + } + return "" +} +func formatDateWork(date model.ResumeDate) string { + if date.IsZero() { + return "Présent" + } + return fmt.Sprintf("%s %d", getMonth(date), date.Year()) +} + +func formatDateEdu(date model.ResumeDate) string { + if date.IsZero() { + return "Présent" + } + return fmt.Sprintf("%d", date.Year()) +} + +func formatDatePub(date model.ResumeDate) string { + if date.IsZero() { + return "Présent" + } + return fmt.Sprintf("%d %s %d", date.Day(), getMonth(date), date.Year()) +} diff --git a/themes/kendallfr/print.css b/themes/kendallfr/print.css new file mode 100644 index 0000000..a13418f --- /dev/null +++ b/themes/kendallfr/print.css @@ -0,0 +1,91 @@ +body { + font-size: .95em; + -webkit-print-color-adjust: exact; +} + +a[href]:after { + content: none !important; +} + +#photo{ + display: none; +} + +.box { + margin-bottom: -10px; +} + +blockquote, +#education, +#awards, +.contact-item, +.publication, +.skills, +.interests { + page-break-inside: avoid; +} + +.col-sm-5{ + width: 40%; + padding: 0 15px; +} + +.col-sm-7{ + width: 60%; + padding: 0 15px; +} + +.skills .col-sm-offset-1, +.interests .col-sm-offset-1{ + margin-top: -10px; + margin-bottom: 5px; +} + +#education { + margin: 0; + margin-bottom: -20px; +} +#awards:before, +#education:before { + background: none; +} + +#awards .description, +#education .description, +.job .details { + border: 1px solid #eee; +} +.publication, +.publication .panel-heading, +.publication .name{ + margin: 0; + padding: 0 5px; + border: none; +} +.publication .panel-body { + padding: 0 10px; + margin: 0; +} + +.badge { + margin: 0; +} + +.list-group-item{ + border: none; + margin: 0; + padding: 5px 15px; +} +.list-group-item:after{ + content: ''; + position: absolute; + top: 8px; + right: 0; + left: -1px; + height: 0; + width: 0; + border: solid transparent; + border-right-color: #999; + border-width: 4px; + pointer-events: none; + } diff --git a/themes/kendallfr/resume.template b/themes/kendallfr/resume.template new file mode 100644 index 0000000..8dad9f3 --- /dev/null +++ b/themes/kendallfr/resume.template @@ -0,0 +1,292 @@ + + + + + + + + CV de {{.Basic.Name}} + + + + + + +
+
+
+
+ + {{if .Basic.Image -}} +
+ avatar +
+ {{end -}} +
+

{{.Basic.Name}}
{{if .Basic.Label}}{{.Basic.Label}}{{end -}}

+
+
+
+
+
+
+ {{if .Basic.Summary -}} + +
+

À propos

+

{{.Basic.Summary}}

+
+ {{end -}} + {{if .Work -}} + +
+

Expérience Professionnelle

+ {{range .Work -}} +
+
+
+
{{.Name}}
+ {{if .URL -}} + + {{end -}} +
{{formatDateWork .StartDate}} – {{formatDateWork .EndDate}}
+
+
+
+
+
{{.Position}}
+
+ {{.Summary -}} + {{if .Highlights -}} +
+
    + {{range .Highlights -}} +
  • {{.}}
  • + {{end -}} +
+ {{end -}} +
+
+
+
+ {{end -}} +
+ {{end -}} + {{if .Awards -}} + +
+

Awards

+
    + {{range .Awards -}} +
  • +
    {{formatDatePub .Date}}
    +
    +

    {{.Awarder}}

    +

    {{.Title}}

    +

    {{.Summary}}

    +
    +
  • + {{end -}} +
+
+ {{end -}} + {{if .Volunteer -}} + +
+

Bénévolat

+ {{range .Volunteer -}} +
+
+
+
{{.Organization}}
+ {{if .URL -}} + + {{end -}} +
{{formatDateWork .StartDate}} – {{formatDateWork .EndDate}}
+
+
+
+
+
{{.Position}}
+
+ {{.Summary -}} + {{if .Highlights -}} +
+
    + {{range .Highlights -}} +
  • {{.}}
  • + {{end -}} +
+ {{end -}} +
+
+
+
+ {{end -}} +
+ {{end -}} +
+
+ +
+

Contact

+ {{if .Basic.ResumeLocation.City -}} +
+
+ {{if .Basic.ResumeLocation.Address}}
{{.Basic.ResumeLocation.Address}}
{{end -}} +
{{.Basic.ResumeLocation.City}}{{if .Basic.ResumeLocation.Region}}, {{.Basic.ResumeLocation.Region}}{{end -}}{{if .Basic.ResumeLocation.PostalCode}} {{.Basic.ResumeLocation.PostalCode}}{{end -}}{{if .Basic.ResumeLocation.CountryCode}} {{.Basic.ResumeLocation.CountryCode}}{{end -}}
+
+ {{end -}} + {{if .Basic.Phone -}} +
+
+
{{.Basic.Phone}}
+
+ {{end -}} + {{if .Basic.Email -}} + + {{end -}} + {{if .Basic.URL -}} + + {{end -}} + {{range .Basic.Profiles -}} + + {{end -}} +
+ {{if .Education -}} + +
+

Formation

+
    + {{range .Education -}} +
  • +
    {{formatDateEdu .StartDate}} {{formatDateEdu .EndDate}}
    +
    +

    {{.Institution}}

    + {{if .StudyType}}

    {{.StudyType}}

    {{end}} +

    {{.Area}}

    + {{if .GPA -}} +

    + GPA: {{.GPA}} +

    + {{end -}} + {{if .Courses -}} +
    Courses
    +
      + {{range .Courses -}} +
    • {{.}}
    • + {{end -}} +
    + {{end -}} +
    +
  • + {{end -}} +
+
+ {{end -}} + {{if .Skills -}} + +
+

Compétences

+ {{range .Skills -}} +
+
+ {{.Name}} + {{if .Level}}{{.Level}}{{end -}} +
+
+ {{range .Keywords -}} + {{.}} + {{end -}} +
+
+ {{end -}} +
+ {{end -}} + {{if .Publications -}} + +
+

Publications

+ {{range .Publications -}} +
+
+
{{.Name}}
+
+
+ {{if .Publisher -}} +
{{.Publisher}}
+ {{end -}} +
{{formatDatePub .ReleaseDate}}
+ {{if .URL -}} + + {{end -}} + {{if .Summary -}} +

{{.Summary}}

+ {{end -}} +
+
+ {{end -}} +
+ {{end -}} + {{if .Languages -}} + +
+

Langues

+
    + {{range .Languages -}} +
  • {{.Language}}{{.Fluency}}
  • + {{end -}} +
+
+ {{end -}} + {{if .Interests -}} + +
+

Intérêts

+ {{range .Interests -}} +
+
+ {{.Name -}} +
+
+ {{range .Keywords -}} + {{.}} + {{end -}} +
+
+ {{end -}} +
+ {{end -}} + {{if .References -}} +
+

Références

+ {{range .References -}} +
+
{{.Reference}}
+
{{.Name}}
+

+ {{end -}} +
+ {{end -}} +
+
+
+ + + diff --git a/themes/kendallfr/style.css b/themes/kendallfr/style.css new file mode 100644 index 0000000..45c3513 --- /dev/null +++ b/themes/kendallfr/style.css @@ -0,0 +1,276 @@ +@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,600,700,800); +@charset "utf-8"; +@-webkit-viewport { width: device-width; } +@-moz-viewport { width: device-width; } +@-ms-viewport { width: device-width; } +@-o-viewport { width: device-width; } +@viewport { width: device-width; } + +body{ + font-family: 'Open Sans', Arial, Tahoma; + font-weight: 400; + color: #363636; + background: #334960; +} +blockquote { + font-size: 1em; +} + +.container{ + margin-top: 80px; + margin-bottom: 15px; + background: #fff; +} + +#photo-header{ + margin-top: -75px; +} +#photo{ + width: 160px; + height: 160px; + border-radius: 50%; + overflow: hidden; + padding: 5px; + background: #334960; + display: inline-block; +} +#photo img{ + width: 150px; + height: 150px; + border-radius: 50%; +} +#text-header h1{ + margin: 0; + padding: 0; + font-size: 1.5em; + font-weight: 700; + text-transform: uppercase; + letter-spacing: -1px; +} +#text-header h1::first-line{ + font-size: 1.5em; + font-weight: 800; + line-height: 1.5em; +} +#text-header h1 span{ + color: #334960; + opacity: 0.7; +} +#text-header h1 sup{ + opacity: 0.5; +} +#text-header:after{ + width: 100%; + height: 3px; + border-bottom: 1px solid #ddd; + margin-top: 15px; + content: ''; + display: block; +} + +.box{ + padding-bottom: 10px; + margin-bottom: 25px; +} +.box h2{ + color: #227c74; + font-size: 1.5em; + font-weight: 700; + text-transform: uppercase; +} + +#awards, +#education{ + margin-top: 20px; + margin-bottom: 0; + position: relative; + padding: 1em 0; + list-style: none; +} +#awards:before, +#education:before { + width: 5px; + height: 100%; + position: absolute; + left: 35px; + top: 0; + content: ' '; + display: block; + background: #32475c; + background: -moz-linear-gradient(top, #ffffff 0%, #32475c 7%, #32475c 89%, #ffffff 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(7%,#32475c), color-stop(89%,#32475c), color-stop(100%,#ffffff)); + background: -webkit-linear-gradient(top, #ffffff 0%,#32475c 7%,#32475c 89%,#ffffff 100%); + background: -o-linear-gradient(top, #ffffff 0%,#32475c 7%,#32475c 89%,#ffffff 100%); + background: -ms-linear-gradient(top, #ffffff 0%,#32475c 7%,#32475c 89%,#ffffff 100%); + background: linear-gradient(to bottom, #ffffff 0%,#32475c 7%,#32475c 89%,#ffffff 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ffffff',GradientType=0 ); +} +#awards li, +#education li{ + width: 100%; + z-index: 2; + position: relative; + float: left; +} +#awards .year, +#education .year{ + width: 14%; + background: #fff; + padding: 10px; + font-weight: 700; + display: inline-block; +} +#awards .description, +#education .description{ + width: 83%; + display: inline-block; + background: #eee; + margin-bottom: 10px; + position: relative; + padding: 10px; + border-bottom: 1px solid #ccc; + border-right: 1px solid #ccc; +} +#awards .description:after, +#education .description:after { + content: ''; + position: absolute; + top: 15px; + right: 0; + left: -16px; + height: 0; + width: 0; + border: solid transparent; + border-right-color: #eee; + border-width: 8px; + pointer-events: none; +} +#awards .description h3, +#education .description h3{ + font-size: 1.2em; + margin: 0; + padding: 0; + font-weight: 700; +} +#awards .description p, +#education .description p{ + margin-top: 5px; + padding: 0; +} + +.job{ + margin-bottom: 15px; +} +.job .details { + margin-left: 3%; + width: 95%; + padding: 10px; + margin-bottom: 10px; + background: #eee; + border-bottom: 1px solid #ccc; + border-right: 1px solid #ccc; +} +.job .where{ + font-size: 1.2em; + font-weight: bold; +} +.job .year{ + opacity: 0.7; +} +.job .profession{ + font-size: 1.2em; + font-weight: bold; +} +.job .description{ + line-height: 1.5em; +} +.job .highlights{ + padding: 5px 0; + font-weight: bold; +} +.job .job-details { + padding-left: 5%; + width: 100%; +} +.publication { + margin-bottom: 0; +} +.publication .name{ + font-size: 1em; + font-weight: bold; +} +.publication .year{ + opacity: 0.7; +} +.publication p{ + margin: 0; + padding-top: 10px; +} + +.contact-item{ + width: 100%; + float: left; +} +.contact-item .icon{ + padding: 10px; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + color: #32475c; + background: #eee; +} +.contact-item:last-child .icon{ + border-bottom: none; +} +.contact-item .title{ + width: 80%; + width: calc(100% - 55px); + font-weight: 700; + opacity: 0.9; +} +.contact-item .title.only{ + margin-top: 10px; +} +.contact-item .description{ + width: 80%; + width: calc(100% - 55px); + color: #334960; +} + +.item-interests, +.item-skills{ + height: 30px; + color: #334960; + padding: 5px 10px; + margin-bottom: 5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 1.1em; + font-weight: 600; +} +.interest, +.skill{ + color: #fff; + display: inline-block; + margin-right: 5px; + margin-bottom: 5px; + padding: 5px 10px; + background: #32475c; + position: relative; + font-size: .85em; +} +.skill-level { + background-color: #227c74; + border-radius: 4px; + color: #fff; + padding: 1px 8px; + font-size: .75em; + position: absolute; + margin: 1px 10px; +} + +#language-skills .skill{ + margin: 10px 0; + padding-bottom: 10px; + border-bottom: 1px solid #eee; +} diff --git a/themes/theme.go b/themes/theme.go new file mode 100644 index 0000000..f3344c5 --- /dev/null +++ b/themes/theme.go @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 Arnaud Ysmal. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +package themes + +import ( + "fmt" + "io" + "jsonresume/model" + "path" + "text/template" +) + +type Theme struct { + Name string + Functions template.FuncMap + Template string +} + +var Themes map[string]*Theme + +func init() { + Themes = make(map[string]*Theme) +} + +func (t *Theme) Render(r *model.Resume, w io.Writer) error { + tmpl := template.Must(template.New(path.Base(t.Template)).Funcs(t.Functions).ParseFiles(t.Template)) + return tmpl.Execute(w, r) +} + +func (t *Theme) Register() error { + if _, exists := Themes[t.Name]; exists { + return fmt.Errorf("Package %s already registered", t.Name) + } + Themes[t.Name] = t + return nil +}