From 8d1f383ac53ecd6c823a4d8ac93e16a8fea0af4e Mon Sep 17 00:00:00 2001 From: Arman Mazdaee Date: Tue, 12 Mar 2019 14:54:50 +0330 Subject: [PATCH 1/8] Refactor render package --- render/hugo.go | 62 ++++++++++++++++++++++++++++++++++++++++++++ render/render.go | 67 ++++++------------------------------------------ 2 files changed, 70 insertions(+), 59 deletions(-) create mode 100644 render/hugo.go diff --git a/render/hugo.go b/render/hugo.go new file mode 100644 index 0000000..ea59a21 --- /dev/null +++ b/render/hugo.go @@ -0,0 +1,62 @@ +package render + +import ( + "encoding/json" + "io/ioutil" + "os" + "path" + + "github.com/gohugoio/hugo/commands" +) + +type siteConfig struct { + DisableKinds []string `json:"disableKinds"` + Theme string `json:"theme"` +} + +type frontmatter struct { + Layout string `json:"layout"` +} + +func hugoInitSite(siteDir string) error { + config := siteConfig{} + config.DisableKinds = []string{"taxonomy", "taxonomyTerm", "category", "sitemap", "RSS", "404", "robotsTXT", "home", "section"} + configJSON, err := json.MarshalIndent(config, "", " ") + if err != nil { + return err + } + configPath := path.Join(siteDir, "config.json") + return ioutil.WriteFile(configPath, configJSON, 0600) +} + +func hugoWriteResumeJSON(resumeJSON []byte, resumeName, siteDir string) error { + dataPath := path.Join(siteDir, "data", "resumic", resumeName+".json") + if err := os.MkdirAll(path.Dir(dataPath), 0700); err != nil { + return err + } + if err := ioutil.WriteFile(dataPath, resumeJSON, 0600); err != nil { + return err + } + + content := frontmatter{} + content.Layout = "resumic" + contentJSON, err := json.MarshalIndent(content, "", " ") + if err != nil { + return err + } + contentPath := path.Join(siteDir, "content", "resumic", resumeName+".md") + if err := os.MkdirAll(path.Dir(contentPath), 0700); err != nil { + return err + } + return ioutil.WriteFile(contentPath, contentJSON, 0600) +} + +func hugoBuild(themeDir, siteDir string) error { + resp := commands.Execute([]string{"--quiet", "--themesDir", themeDir, "--source", siteDir}) + return resp.Err +} + +func hugoReadResumeHTML(name, siteDir string) ([]byte, error) { + htmlPath := path.Join(siteDir, "public", "resumic", name, "index.html") + return ioutil.ReadFile(htmlPath) +} diff --git a/render/render.go b/render/render.go index 278bec6..ba5bd35 100644 --- a/render/render.go +++ b/render/render.go @@ -1,80 +1,29 @@ package render import ( - "encoding/json" "io/ioutil" "os" - "path" - - "github.com/gohugoio/hugo/commands" ) -type siteConfig struct { - DisableKinds []string `json:"disableKinds"` - Theme string `json:"theme"` -} - -type frontmatter struct { - Layout string `json:"layout"` -} - -func build(root, themePath string) error { - resp := commands.Execute([]string{"--quiet", "-s", root, "--themesDir", themePath}) - return resp.Err -} - -func RenderHTML(resume []byte, themePath string) ([]byte, error) { - sitePath, err := ioutil.TempDir(os.TempDir(), "resumic") - if err != nil { - return nil, err - } - defer os.RemoveAll(sitePath) - - config := siteConfig{} - config.DisableKinds = []string{"taxonomy", "taxonomyTerm", "category", "sitemap", "RSS", "404", "robotsTXT", "home", "section"} - - configJSON, err := json.MarshalIndent(config, "", " ") - if err != nil { - return nil, err - } - configPath := path.Join(sitePath, "config.json") - err = ioutil.WriteFile(configPath, configJSON, 0600) +func RenderHTML(resumeJSON []byte, themeDir string) ([]byte, error) { + siteDir, err := ioutil.TempDir(os.TempDir(), "resumic") if err != nil { return nil, err } + defer os.RemoveAll(siteDir) - dataPath := path.Join(sitePath, "data", "resumic", "resume.json") - err = os.MkdirAll(path.Dir(dataPath), 0700) - if err != nil { + if err := hugoInitSite(siteDir); err != nil { return nil, err } - err = ioutil.WriteFile(dataPath, resume, 0600) - if err != nil { - return nil, err - } - - content := frontmatter{} - content.Layout = "resumic" - contentJSON, err := json.MarshalIndent(content, "", " ") - if err != nil { - return nil, err - } - contentPath := path.Join(sitePath, "content", "resumic", "resume.md") - err = os.MkdirAll(path.Dir(contentPath), 0700) - if err != nil { - return nil, err - } - err = ioutil.WriteFile(contentPath, contentJSON, 0600) - if err != nil { + resumeName := "resume" + if err := hugoWriteResumeJSON(resumeJSON, resumeName, siteDir); err != nil { return nil, err } - err = build(sitePath, themePath) - if err != nil { + if err := hugoBuild(themeDir, siteDir); err != nil { return nil, err } - htmlPath := path.Join(sitePath, "public", "resumic", "resume", "index.html") - return ioutil.ReadFile(htmlPath) + return hugoReadResumeHTML(resumeName, siteDir) } From 1ac710cccd0c8bb1952ccaa1930d37e9fc90897d Mon Sep 17 00:00:00 2001 From: Arman Mazdaee Date: Tue, 12 Mar 2019 15:02:55 +0330 Subject: [PATCH 2/8] Make sure resume is valid before rendering --- render/render.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/render/render.go b/render/render.go index ba5bd35..049af3f 100644 --- a/render/render.go +++ b/render/render.go @@ -3,9 +3,15 @@ package render import ( "io/ioutil" "os" + + "github.com/resumic/schema/schema" ) func RenderHTML(resumeJSON []byte, themeDir string) ([]byte, error) { + if err := schema.ValidateResume(resumeJSON); err != nil { + return nil, err + } + siteDir, err := ioutil.TempDir(os.TempDir(), "resumic") if err != nil { return nil, err From beee9fbb291262efcffa7facba245ec98bdbc0a1 Mon Sep 17 00:00:00 2001 From: Arman Mazdaee Date: Wed, 13 Mar 2019 21:55:08 +0330 Subject: [PATCH 3/8] Refactor render package --- render/hugo.go | 88 ++++++++++++++++++++++++++++++++++++------------ render/render.go | 13 ++++--- 2 files changed, 76 insertions(+), 25 deletions(-) diff --git a/render/hugo.go b/render/hugo.go index ea59a21..cdb0fa9 100644 --- a/render/hugo.go +++ b/render/hugo.go @@ -3,60 +3,106 @@ package render import ( "encoding/json" "io/ioutil" + "net/http" + "net/url" "os" "path" "github.com/gohugoio/hugo/commands" ) -type siteConfig struct { +type contentFrontmatter struct { + Layout string `json:"layout"` +} + +type hugoConfig struct { DisableKinds []string `json:"disableKinds"` Theme string `json:"theme"` + BaseURL string `json:"baseURL"` + Title string `json:"title"` } -type frontmatter struct { - Layout string `json:"layout"` +func newHugoConfig() hugoConfig { + config := hugoConfig{} + config.DisableKinds = []string{"taxonomy", "taxonomyTerm", "category", "sitemap", "RSS", "404", "robotsTXT", "home", "section"} + config.BaseURL = "http://example.org/" + config.Title = "resumic" + return config } -func hugoInitSite(siteDir string) error { - config := siteConfig{} - config.DisableKinds = []string{"taxonomy", "taxonomyTerm", "category", "sitemap", "RSS", "404", "robotsTXT", "home", "section"} - configJSON, err := json.MarshalIndent(config, "", " ") +type hugoSite struct { + dir string + config hugoConfig +} + +func initHugoSite(siteDir string) (hugoSite, error) { + site := hugoSite{ + dir: siteDir, + } + if err := os.MkdirAll(site.dir, 0700); err != nil { + return site, err + } + + site.config = newHugoConfig() + configJSON, err := json.MarshalIndent(site.config, "", " ") if err != nil { - return err + return site, err + } + configPath := path.Join(site.dir, "config.json") + if err := ioutil.WriteFile(configPath, configJSON, 0600); err != nil { + return site, err } - configPath := path.Join(siteDir, "config.json") - return ioutil.WriteFile(configPath, configJSON, 0600) + + return site, nil } -func hugoWriteResumeJSON(resumeJSON []byte, resumeName, siteDir string) error { - dataPath := path.Join(siteDir, "data", "resumic", resumeName+".json") - if err := os.MkdirAll(path.Dir(dataPath), 0700); err != nil { +func (s hugoSite) writeResumeJSON(resumeJSON []byte, resumeName string) error { + dataDir := path.Join(s.dir, "data", "resumic") + if err := os.MkdirAll(dataDir, 0700); err != nil { return err } + dataPath := path.Join(dataDir, resumeName+".json") if err := ioutil.WriteFile(dataPath, resumeJSON, 0600); err != nil { return err } - content := frontmatter{} + content := contentFrontmatter{} content.Layout = "resumic" contentJSON, err := json.MarshalIndent(content, "", " ") if err != nil { return err } - contentPath := path.Join(siteDir, "content", "resumic", resumeName+".md") - if err := os.MkdirAll(path.Dir(contentPath), 0700); err != nil { + contentDir := path.Join(s.dir, "content", "resumic") + if err := os.MkdirAll(contentDir, 0700); err != nil { return err } + contentPath := path.Join(contentDir, resumeName+".md") return ioutil.WriteFile(contentPath, contentJSON, 0600) } -func hugoBuild(themeDir, siteDir string) error { - resp := commands.Execute([]string{"--quiet", "--themesDir", themeDir, "--source", siteDir}) +func (s hugoSite) getResumeURL(resumeName string) (*url.URL, error) { + baseURL, err := url.Parse(s.config.BaseURL) + if err != nil { + return nil, err + } + return baseURL.Parse("/resumic/" + resumeName) +} + +func (s hugoSite) build(themeDir string) error { + resp := commands.Execute([]string{"--quiet", "--source", s.dir, "--themesDir", themeDir}) return resp.Err } -func hugoReadResumeHTML(name, siteDir string) ([]byte, error) { - htmlPath := path.Join(siteDir, "public", "resumic", name, "index.html") - return ioutil.ReadFile(htmlPath) +func (s hugoSite) readPublic(u *url.URL) ([]byte, error) { + publicDir := path.Join(s.dir, "public") + client := &http.Client{} + client.Transport = http.NewFileTransport(http.Dir(publicDir)) + + response, err := client.Get(u.String()) + if err != nil { + return nil, err + } + defer response.Body.Close() + + return ioutil.ReadAll(response.Body) } diff --git a/render/render.go b/render/render.go index 049af3f..9aaf4dd 100644 --- a/render/render.go +++ b/render/render.go @@ -18,18 +18,23 @@ func RenderHTML(resumeJSON []byte, themeDir string) ([]byte, error) { } defer os.RemoveAll(siteDir) - if err := hugoInitSite(siteDir); err != nil { + site, err := initHugoSite(siteDir) + if err != nil { return nil, err } resumeName := "resume" - if err := hugoWriteResumeJSON(resumeJSON, resumeName, siteDir); err != nil { + if err := site.writeResumeJSON(resumeJSON, resumeName); err != nil { return nil, err } - if err := hugoBuild(themeDir, siteDir); err != nil { + if err := site.build(themeDir); err != nil { return nil, err } - return hugoReadResumeHTML(resumeName, siteDir) + resumeURL, err := site.getResumeURL(resumeName) + if err != nil { + return nil, err + } + return site.readPublic(resumeURL) } From e04baf97493cdc3e0e2940609d1a87f3f6a42e76 Mon Sep 17 00:00:00 2001 From: Arman Mazdaee Date: Sun, 17 Mar 2019 20:14:55 +0330 Subject: [PATCH 4/8] Add embeding support for stylesheets and scripts --- example.html | 14 +- go.mod | 3 +- go.sum | 4 - render/embed.go | 143 ++++++++++++++++++ render/render.go | 2 +- .../test-theme/layouts/resumic/single.html | 5 + theme/defaults/test-theme/static/script.js | 3 + theme/defaults/test-theme/static/style.css | 3 + 8 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 render/embed.go create mode 100644 theme/defaults/test-theme/static/script.js create mode 100644 theme/defaults/test-theme/static/style.css diff --git a/example.html b/example.html index 4f87ed4..fa624fe 100644 --- a/example.html +++ b/example.html @@ -1,6 +1,14 @@ - + + + John Doe - Software Engineer - - \ No newline at end of file + + + + \ No newline at end of file diff --git a/go.mod b/go.mod index 08161b9..4aabfd5 100644 --- a/go.mod +++ b/go.mod @@ -14,9 +14,8 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.1.0 golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 // indirect - golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 // indirect + golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3 golang.org/x/sys v0.0.0-20190204203706-41f3e6584952 // indirect gopkg.in/src-d/go-billy.v4 v4.3.0 // indirect - gopkg.in/src-d/go-git-fixtures.v3 v3.3.0 // indirect gopkg.in/src-d/go-git.v4 v4.9.1 ) diff --git a/go.sum b/go.sum index 24bca7d..4c6e2b0 100644 --- a/go.sum +++ b/go.sum @@ -380,8 +380,6 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56 h1:yhqBHs09SmmUoNOHc9jgK4a60T3XFRtPAkYxVnqgY50= -github.com/xeipuuv/gojsonschema v0.0.0-20181112162635-ac52e6811b56/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg= github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= @@ -470,8 +468,6 @@ gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd gopkg.in/src-d/go-billy.v4 v4.3.0 h1:KtlZ4c1OWbIs4jCv5ZXrTqG8EQocr0g/d4DjNg70aek= gopkg.in/src-d/go-billy.v4 v4.3.0/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk= gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= -gopkg.in/src-d/go-git-fixtures.v3 v3.3.0 h1:AxUOwLW3at53ysFqs0Lg+H+8KSQXl7AEHBvWj8wEsT8= -gopkg.in/src-d/go-git-fixtures.v3 v3.3.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= gopkg.in/src-d/go-git.v4 v4.9.1 h1:0oKHJZY8tM7B71378cfTg2c5jmWyNlXvestTT6WfY+4= gopkg.in/src-d/go-git.v4 v4.9.1/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/render/embed.go b/render/embed.go new file mode 100644 index 0000000..986b672 --- /dev/null +++ b/render/embed.go @@ -0,0 +1,143 @@ +package render + +import ( + "bytes" + "net/url" + + "golang.org/x/net/html/atom" + + "golang.org/x/net/html" +) + +func findAttributeIndex(attrs []html.Attribute, key string) int { + index := -1 + for i := 0; i < len(attrs); i++ { + if attrs[i].Key == key { + index = i + break + } + } + return index +} + +func embedStyleNode(node *html.Node, baseURL *url.URL, site hugoSite) error { + hrefAttrIndex := findAttributeIndex(node.Attr, "href") + if hrefAttrIndex == -1 { + return nil + } + hrefAttr := node.Attr[hrefAttrIndex] + contentURL, err := baseURL.Parse(hrefAttr.Val) + if err != nil { + return err + } + if contentURL.Host != baseURL.Host { + return nil + } + + content, err := site.readPublic(contentURL) + if err != nil { + return err + } + contentNode := &html.Node{} + contentNode.Type = html.TextNode + contentNode.Data = string(content) + + styleNode := &html.Node{} + styleNode.Type = html.ElementNode + styleNode.DataAtom = atom.Style + styleNode.Data = "style" + if index := findAttributeIndex(node.Attr, "media"); index != -1 { + attr := styleNode.Attr[index] + styleNode.Attr = append(styleNode.Attr, attr) + } + if index := findAttributeIndex(node.Attr, "title"); index != -1 { + attr := styleNode.Attr[index] + styleNode.Attr = append(styleNode.Attr, attr) + } + + styleNode.AppendChild(contentNode) + node.Parent.InsertBefore(styleNode, node) + node.Parent.RemoveChild(node) + return nil +} + +func embedScriptNode(node *html.Node, baseURL *url.URL, site hugoSite) error { + srcAttrIndex := findAttributeIndex(node.Attr, "src") + if srcAttrIndex == -1 { + return nil + } + srcAttr := node.Attr[srcAttrIndex] + contentURL, err := baseURL.Parse(srcAttr.Val) + if err != nil { + return err + } + if contentURL.Host != baseURL.Host { + return nil + } + + content, err := site.readPublic(contentURL) + if err != nil { + return err + } + contentNode := &html.Node{} + contentNode.Type = html.TextNode + contentNode.Data = string(content) + + node.Attr = append(node.Attr[:srcAttrIndex], node.Attr[srcAttrIndex+1:]...) + node.AppendChild(contentNode) + return nil +} + +func embedNode(node *html.Node, baseURL *url.URL, site hugoSite) error { + var err error + switch node.DataAtom { + case atom.Link: + relIndex := findAttributeIndex(node.Attr, "rel") + if relIndex == -1 { + break + } + relValue := node.Attr[relIndex].Val + + switch relValue { + case "stylesheet": + err = embedStyleNode(node, baseURL, site) + } + + case atom.Script: + err = embedScriptNode(node, baseURL, site) + } + + if err != nil { + return err + } + + for child := node.FirstChild; child != nil; child = child.NextSibling { + if err := embedNode(child, baseURL, site); err != nil { + return err + } + } + return nil +} + +func embedHTML(pageURL *url.URL, site hugoSite) ([]byte, error) { + rawHTML, err := site.readPublic(pageURL) + if err != nil { + return nil, err + } + + node, err := html.Parse(bytes.NewReader(rawHTML)) + if err != nil { + return nil, err + } + + if err := embedNode(node, pageURL, site); err != nil { + return nil, err + } + + embeddedHTML := &bytes.Buffer{} + if err := html.Render(embeddedHTML, node); err != nil { + return nil, err + } + + return embeddedHTML.Bytes(), nil +} diff --git a/render/render.go b/render/render.go index 9aaf4dd..b646c73 100644 --- a/render/render.go +++ b/render/render.go @@ -36,5 +36,5 @@ func RenderHTML(resumeJSON []byte, themeDir string) ([]byte, error) { if err != nil { return nil, err } - return site.readPublic(resumeURL) + return embedHTML(resumeURL, site) } diff --git a/theme/defaults/test-theme/layouts/resumic/single.html b/theme/defaults/test-theme/layouts/resumic/single.html index dbc871c..16eb8e0 100644 --- a/theme/defaults/test-theme/layouts/resumic/single.html +++ b/theme/defaults/test-theme/layouts/resumic/single.html @@ -1,6 +1,11 @@ + + + {{ $resume := index $.Site.Data.resumic .File.BaseFileName }} {{ $resume.personal.name }} - {{ $resume.core.title }} + + \ No newline at end of file diff --git a/theme/defaults/test-theme/static/script.js b/theme/defaults/test-theme/static/script.js new file mode 100644 index 0000000..95ae9be --- /dev/null +++ b/theme/defaults/test-theme/static/script.js @@ -0,0 +1,3 @@ +document.addEventListener("DOMContentLoaded", function(event) { + console.log("Hi!") + }); \ No newline at end of file diff --git a/theme/defaults/test-theme/static/style.css b/theme/defaults/test-theme/static/style.css new file mode 100644 index 0000000..3b5b6bb --- /dev/null +++ b/theme/defaults/test-theme/static/style.css @@ -0,0 +1,3 @@ +body { + background: blue; +} \ No newline at end of file From d7509b5140d093be80ff372340e7b71dddc54067 Mon Sep 17 00:00:00 2001 From: Arman Mazdaee Date: Sun, 17 Mar 2019 21:29:00 +0330 Subject: [PATCH 5/8] Add embeding for img tag --- example.html | 4 +++ render/embed.go | 33 ++++++++++++++++++ .../test-theme/layouts/resumic/single.html | 4 +++ theme/defaults/test-theme/static/resumic.jpg | Bin 0 -> 2523 bytes 4 files changed, 41 insertions(+) create mode 100644 theme/defaults/test-theme/static/resumic.jpg diff --git a/example.html b/example.html index fa624fe..020cc40 100644 --- a/example.html +++ b/example.html @@ -7,6 +7,10 @@ John Doe - Software Engineer +
+

Powerd By resumic

+ +
diff --git a/render/embed.go b/render/embed.go index 986b672..c88c956 100644 --- a/render/embed.go +++ b/render/embed.go @@ -2,7 +2,10 @@ package render import ( "bytes" + "encoding/base64" + "mime" "net/url" + "path" "golang.org/x/net/html/atom" @@ -88,6 +91,33 @@ func embedScriptNode(node *html.Node, baseURL *url.URL, site hugoSite) error { return nil } +func embedImgNode(node *html.Node, baseURL *url.URL, site hugoSite) error { + srcAttrIndex := findAttributeIndex(node.Attr, "src") + if srcAttrIndex == -1 { + return nil + } + srcAttr := node.Attr[srcAttrIndex] + imageURL, err := baseURL.Parse(srcAttr.Val) + if err != nil { + return err + } + if imageURL.Host != baseURL.Host { + return nil + } + + imageExt := path.Ext(imageURL.Path) + imageMime := mime.TypeByExtension(imageExt) + + imageData, err := site.readPublic(imageURL) + if err != nil { + return err + } + imageBase64 := base64.StdEncoding.EncodeToString(imageData) + + node.Attr[srcAttrIndex].Val = "data:" + imageMime + ";base64," + imageBase64 + return nil +} + func embedNode(node *html.Node, baseURL *url.URL, site hugoSite) error { var err error switch node.DataAtom { @@ -105,6 +135,9 @@ func embedNode(node *html.Node, baseURL *url.URL, site hugoSite) error { case atom.Script: err = embedScriptNode(node, baseURL, site) + + case atom.Img: + err = embedImgNode(node, baseURL, site) } if err != nil { diff --git a/theme/defaults/test-theme/layouts/resumic/single.html b/theme/defaults/test-theme/layouts/resumic/single.html index 16eb8e0..ec88e68 100644 --- a/theme/defaults/test-theme/layouts/resumic/single.html +++ b/theme/defaults/test-theme/layouts/resumic/single.html @@ -6,6 +6,10 @@ {{ $resume := index $.Site.Data.resumic .File.BaseFileName }} {{ $resume.personal.name }} - {{ $resume.core.title }} +
+

Powerd By resumic

+ +
\ No newline at end of file diff --git a/theme/defaults/test-theme/static/resumic.jpg b/theme/defaults/test-theme/static/resumic.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a9a96a02be23615633ba52c7be847b336dde7848 GIT binary patch literal 2523 zcmbVMX;c$i624g=EMjDbxI`8eMMYUeK?w?|xJ*PrQG~cW1w}-Z2(mQWnpZ0o!#m^Wb?)?G6Vtko8@ngl> zfeRq~JBUAir@q90fz5n2GVNvlDLCMq7ipr|$ninr$y>4i1df)t^rM0cAyQjDBWBSQ1*xDnp(%H>8(r7 zED)=k*=&dP51g#mFfh06nkSo*_Cwi!CyeudDf?5{zjU>MX(R&PJd!qG0Hx9bWDsLD zfCcHMjC(O^9z0et(SNBdtUHJz7EXt2`12ojGxSitSG)_1oC}=shG)-9wXU9a)S>L6 zUYM009;UeWsK}}X1Kq6QRg(+Q$c>7JY$g?T_*})97PSz5u{+q{h)D0t@bT(f z*Bqid(*#;Ji<3@VLfFaiKBYZ#yX>>y(V&6_HIgJ&k^fL7Ea~QOEX`rZ=qSa-_!l1sv&_EvW9|mWBW_pY zQq0iE?iXgCuV~l5E$1ms+cChohJkvTB1HuMfeJ8?wrEugy~x2|u3r+<$bem-3-lu1 za1U&`O(x+Amqs0I!oZ+PU??s^34B zu}MR-9T8JSo-KpJhqpY}RH~nnwXs@nBA)1(z#y4rk6|S>@%H?Z=GDPHL7LUHC&}Hf zY;P=0-TZX-y~~!hCtJQmp3rK`J*M?~{B&Onbzsilh_Oz_IOBBa^(xVE{-mvh4h`De z-+t{4^;<8#zgHfCvSB_Fj_&W0=*k0%QJ=_es9pKB-={$;o6gH@4=-(_a+`I~$Sty4 zyk&dcgS*za?B-}{ts8rpY?AmfjAT)D@EtV;0|%b_AWg;S=%iam%qtA&i~Q?a+iQ4f z=c+_AhNFUHx?TO8C^dLR!D-niA^oXLceDR$gT^Ax@_j?Dmd2egJySg?pX%i1BG?B5 z6AV@hgaI8#f|Upw#`gF91_vLoM({r+W z-4Wdw6V}Rh^|E|hw{Jrof?M0hBM1r)42W;BzOH8+r%x_aYGI&pr@)JuOK+_WAUM;f z;oiT9`esQ4OhsDo715}1)BVkdDdR#&Y#lm+E<7|+F_OTX)JAoYNEs_G^~GzU)iboR zOLBJ4+rpqc$bV1r>^xMUSiXOf@oRe!6!QGKLh(LPj2psc_^d@M9@DW+0_u2a6D_pBGPVrLmv`|2t+^XQe-gu0%(vrxe$eq3XD+OzQ2 zBX01np$^r;z-&#;_+0k@#D6%+THJ5c_F+uSxvq>ZI!YO&%8jcrV15wk#s`Hjl_=~2 z*`5<|id)UGdIY!*ucgwXb}|$LtvTo<20YR?U460-x>`3R$yACX^3NBKLxGG0sMspL zj5Vj!46fys2cK6O$?~~hzDo+Lwg?IbBeJs!{y6jTP%F*)n5X08jYfp;EV&5=V7HNp zr4a1FmoMW}q4B#JB;^bYpr&IALk4P5r!-=6Me_Q0s)mWH7vLS_a4Y>xcwz|+l4jU< zE=_VRO|5A5GePN6$JxXY4{!4o9xSF|!W{46w*9o1vb&|32Od-bXT}2-YLleg_+q#c zzhM|+(Ztc_&zqv1m2U%`H2?}JH4X-aOijoaMal4zxoLxg{m6FNkO9(MZ#$;@EIiOd zBOy`!uz4&nHgCXhgs(M;S6xk+8mBYY?{`WBq Date: Mon, 18 Mar 2019 10:23:33 +0330 Subject: [PATCH 6/8] Add CouldNotReadPublicError --- render/hugo.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/render/hugo.go b/render/hugo.go index cdb0fa9..09a1ddb 100644 --- a/render/hugo.go +++ b/render/hugo.go @@ -11,6 +11,15 @@ import ( "github.com/gohugoio/hugo/commands" ) +type CouldNotReadPublicError struct { + url *url.URL + dir string +} + +func (e *CouldNotReadPublicError) Error() string { + return "Could not find " + e.url.String() + " in " + e.dir +} + type contentFrontmatter struct { Layout string `json:"layout"` } @@ -102,6 +111,9 @@ func (s hugoSite) readPublic(u *url.URL) ([]byte, error) { if err != nil { return nil, err } + if response.StatusCode != http.StatusOK { + return nil, &CouldNotReadPublicError{url: u, dir: s.dir} + } defer response.Body.Close() return ioutil.ReadAll(response.Body) From 6116b5258e47e33609841f578a6d737002e9e37d Mon Sep 17 00:00:00 2001 From: Arman Mazdaee Date: Thu, 28 Mar 2019 17:12:28 +0430 Subject: [PATCH 7/8] use data urls for every resource --- example.html | 8 +-- render/embed.go | 164 ++++++++--------------------------------------- render/render.go | 8 ++- 3 files changed, 37 insertions(+), 143 deletions(-) diff --git a/example.html b/example.html index 020cc40..1bf6d36 100644 --- a/example.html +++ b/example.html @@ -1,7 +1,5 @@ - + @@ -11,8 +9,6 @@

Powerd By resumic

- + \ No newline at end of file diff --git a/render/embed.go b/render/embed.go index c88c956..1d4533e 100644 --- a/render/embed.go +++ b/render/embed.go @@ -7,163 +7,55 @@ import ( "net/url" "path" - "golang.org/x/net/html/atom" - "golang.org/x/net/html" ) -func findAttributeIndex(attrs []html.Attribute, key string) int { - index := -1 - for i := 0; i < len(attrs); i++ { - if attrs[i].Key == key { - index = i - break - } - } - return index -} - -func embedStyleNode(node *html.Node, baseURL *url.URL, site hugoSite) error { - hrefAttrIndex := findAttributeIndex(node.Attr, "href") - if hrefAttrIndex == -1 { - return nil - } - hrefAttr := node.Attr[hrefAttrIndex] - contentURL, err := baseURL.Parse(hrefAttr.Val) - if err != nil { - return err - } - if contentURL.Host != baseURL.Host { - return nil - } - - content, err := site.readPublic(contentURL) - if err != nil { - return err - } - contentNode := &html.Node{} - contentNode.Type = html.TextNode - contentNode.Data = string(content) - - styleNode := &html.Node{} - styleNode.Type = html.ElementNode - styleNode.DataAtom = atom.Style - styleNode.Data = "style" - if index := findAttributeIndex(node.Attr, "media"); index != -1 { - attr := styleNode.Attr[index] - styleNode.Attr = append(styleNode.Attr, attr) - } - if index := findAttributeIndex(node.Attr, "title"); index != -1 { - attr := styleNode.Attr[index] - styleNode.Attr = append(styleNode.Attr, attr) - } - - styleNode.AppendChild(contentNode) - node.Parent.InsertBefore(styleNode, node) - node.Parent.RemoveChild(node) - return nil -} - -func embedScriptNode(node *html.Node, baseURL *url.URL, site hugoSite) error { - srcAttrIndex := findAttributeIndex(node.Attr, "src") - if srcAttrIndex == -1 { - return nil - } - srcAttr := node.Attr[srcAttrIndex] - contentURL, err := baseURL.Parse(srcAttr.Val) - if err != nil { - return err - } - if contentURL.Host != baseURL.Host { - return nil - } - - content, err := site.readPublic(contentURL) - if err != nil { - return err - } - contentNode := &html.Node{} - contentNode.Type = html.TextNode - contentNode.Data = string(content) - - node.Attr = append(node.Attr[:srcAttrIndex], node.Attr[srcAttrIndex+1:]...) - node.AppendChild(contentNode) - return nil -} - -func embedImgNode(node *html.Node, baseURL *url.URL, site hugoSite) error { - srcAttrIndex := findAttributeIndex(node.Attr, "src") - if srcAttrIndex == -1 { - return nil - } - srcAttr := node.Attr[srcAttrIndex] - imageURL, err := baseURL.Parse(srcAttr.Val) +func makeDataURL(resourceURL *url.URL, site hugoSite) (string, error) { + resourceContent, err := site.readPublic(resourceURL) if err != nil { - return err - } - if imageURL.Host != baseURL.Host { - return nil + return "", err } - - imageExt := path.Ext(imageURL.Path) - imageMime := mime.TypeByExtension(imageExt) - - imageData, err := site.readPublic(imageURL) - if err != nil { - return err - } - imageBase64 := base64.StdEncoding.EncodeToString(imageData) - - node.Attr[srcAttrIndex].Val = "data:" + imageMime + ";base64," + imageBase64 - return nil + resourceBase64 := base64.StdEncoding.EncodeToString(resourceContent) + resourceExt := path.Ext(resourceURL.Path) + resourceMime := mime.TypeByExtension(resourceExt) + return "data:" + resourceMime + ";base64," + resourceBase64, nil } -func embedNode(node *html.Node, baseURL *url.URL, site hugoSite) error { - var err error - switch node.DataAtom { - case atom.Link: - relIndex := findAttributeIndex(node.Attr, "rel") - if relIndex == -1 { - break +func embedHTMLNode(node *html.Node, baseURL *url.URL, site hugoSite) error { + if node.Type == html.ElementNode { + for index, attr := range node.Attr { + if attr.Key == "href" || attr.Key == "src" { + resourceURL, err := baseURL.Parse(attr.Val) + if err != nil { + return err + } + if baseURL.Host == resourceURL.Host { + dataURL, err := makeDataURL(resourceURL, site) + if err != nil { + return err + } + node.Attr[index].Val = dataURL + } + } } - relValue := node.Attr[relIndex].Val - - switch relValue { - case "stylesheet": - err = embedStyleNode(node, baseURL, site) - } - - case atom.Script: - err = embedScriptNode(node, baseURL, site) - - case atom.Img: - err = embedImgNode(node, baseURL, site) - } - - if err != nil { - return err } for child := node.FirstChild; child != nil; child = child.NextSibling { - if err := embedNode(child, baseURL, site); err != nil { + if err := embedHTMLNode(child, baseURL, site); err != nil { return err } } + return nil } -func embedHTML(pageURL *url.URL, site hugoSite) ([]byte, error) { - rawHTML, err := site.readPublic(pageURL) - if err != nil { - return nil, err - } - - node, err := html.Parse(bytes.NewReader(rawHTML)) +func embedHTML(pageHTML []byte, pageURL *url.URL, site hugoSite) ([]byte, error) { + node, err := html.Parse(bytes.NewReader(pageHTML)) if err != nil { return nil, err } - if err := embedNode(node, pageURL, site); err != nil { + if err := embedHTMLNode(node, pageURL, site); err != nil { return nil, err } diff --git a/render/render.go b/render/render.go index b646c73..6bb71a1 100644 --- a/render/render.go +++ b/render/render.go @@ -36,5 +36,11 @@ func RenderHTML(resumeJSON []byte, themeDir string) ([]byte, error) { if err != nil { return nil, err } - return embedHTML(resumeURL, site) + + resumeHTML, err := site.readPublic(resumeURL) + if err != nil { + return nil, err + } + + return embedHTML(resumeHTML, resumeURL, site) } From e29b67435c6cf3010d937eb81c0590e06516acaf Mon Sep 17 00:00:00 2001 From: Arman Mazdaee Date: Mon, 1 Apr 2019 15:17:19 +0430 Subject: [PATCH 8/8] Use transport to generate all data urls --- example.html | 2 +- render/embed.go | 49 ++++++++++++++++++++---------------- render/http.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ render/hugo.go | 26 ++----------------- render/render.go | 16 ++++++++---- 5 files changed, 107 insertions(+), 51 deletions(-) create mode 100644 render/http.go diff --git a/example.html b/example.html index 1bf6d36..89420e8 100644 --- a/example.html +++ b/example.html @@ -6,7 +6,7 @@ John Doe - Software Engineer
-

Powerd By resumic

+

Powerd By resumic

diff --git a/render/embed.go b/render/embed.go index 1d4533e..7e6c294 100644 --- a/render/embed.go +++ b/render/embed.go @@ -3,45 +3,52 @@ package render import ( "bytes" "encoding/base64" - "mime" - "net/url" - "path" + "io/ioutil" + "net/http" "golang.org/x/net/html" ) -func makeDataURL(resourceURL *url.URL, site hugoSite) (string, error) { - resourceContent, err := site.readPublic(resourceURL) +func makeDataURL(resourceURL string, client *http.Client) (string, error) { + response, err := client.Get(resourceURL) if err != nil { return "", err } - resourceBase64 := base64.StdEncoding.EncodeToString(resourceContent) - resourceExt := path.Ext(resourceURL.Path) - resourceMime := mime.TypeByExtension(resourceExt) - return "data:" + resourceMime + ";base64," + resourceBase64, nil + + contentType := response.Header.Get("Content-type") + if contentType == "" { + contentType = "application/octet-stream" + } + + content, err := ioutil.ReadAll(response.Body) + if err != nil { + return "", err + } + contentBase64 := base64.StdEncoding.EncodeToString(content) + + dataURL := "data:" + contentType + ";base64," + contentBase64 + return dataURL, nil } -func embedHTMLNode(node *html.Node, baseURL *url.URL, site hugoSite) error { +func embedHTMLNode(node *html.Node, baseURL string, client *http.Client) error { if node.Type == html.ElementNode { for index, attr := range node.Attr { if attr.Key == "href" || attr.Key == "src" { - resourceURL, err := baseURL.Parse(attr.Val) + resourceURL, err := absURL(baseURL, attr.Val) if err != nil { - return err + return nil } - if baseURL.Host == resourceURL.Host { - dataURL, err := makeDataURL(resourceURL, site) - if err != nil { - return err - } - node.Attr[index].Val = dataURL + dataURL, err := makeDataURL(resourceURL, client) + if err != nil { + return err } + node.Attr[index].Val = dataURL } } } for child := node.FirstChild; child != nil; child = child.NextSibling { - if err := embedHTMLNode(child, baseURL, site); err != nil { + if err := embedHTMLNode(child, baseURL, client); err != nil { return err } } @@ -49,13 +56,13 @@ func embedHTMLNode(node *html.Node, baseURL *url.URL, site hugoSite) error { return nil } -func embedHTML(pageHTML []byte, pageURL *url.URL, site hugoSite) ([]byte, error) { +func embedHTML(pageHTML []byte, pageURL string, client *http.Client) ([]byte, error) { node, err := html.Parse(bytes.NewReader(pageHTML)) if err != nil { return nil, err } - if err := embedHTMLNode(node, pageURL, site); err != nil { + if err := embedHTMLNode(node, pageURL, client); err != nil { return nil, err } diff --git a/render/http.go b/render/http.go new file mode 100644 index 0000000..04fde27 --- /dev/null +++ b/render/http.go @@ -0,0 +1,65 @@ +package render + +import ( + "net/http" + "net/url" + "path" +) + +func absURL(baseURL, resourceURL string) (string, error) { + base, err := url.Parse(baseURL) + if err != nil { + return "", err + } + resource, err := base.Parse(resourceURL) + if err != nil { + return "", err + } + return resource.String(), nil +} + +type transport struct { + siteURL *url.URL + localTransport http.RoundTripper + siteTransport http.RoundTripper + remoteTransport http.RoundTripper +} + +func newTransport(site hugoSite) (*transport, error) { + t := &transport{} + + siteURL, err := url.Parse(site.config.BaseURL) + if err != nil { + return nil, err + } + t.siteURL = siteURL + + t.localTransport = http.NewFileTransport(http.Dir("/")) + publicDir := path.Join(site.dir, "public") + t.siteTransport = http.NewFileTransport(http.Dir(publicDir)) + t.remoteTransport = &http.Transport{} + + return t, nil +} + +func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { + reqURL := req.URL + if reqURL.Scheme == "file" { + return t.localTransport.RoundTrip(req) + } + if reqURL.Host == "" || reqURL.Host == t.siteURL.Host { + return t.siteTransport.RoundTrip(req) + } + return t.remoteTransport.RoundTrip(req) +} + +func newClient(site hugoSite) (*http.Client, error) { + trans, err := newTransport(site) + if err != nil { + return nil, err + } + client := &http.Client{ + Transport: trans, + } + return client, nil +} diff --git a/render/hugo.go b/render/hugo.go index 09a1ddb..b68ca9b 100644 --- a/render/hugo.go +++ b/render/hugo.go @@ -3,7 +3,6 @@ package render import ( "encoding/json" "io/ioutil" - "net/http" "net/url" "os" "path" @@ -89,32 +88,11 @@ func (s hugoSite) writeResumeJSON(resumeJSON []byte, resumeName string) error { return ioutil.WriteFile(contentPath, contentJSON, 0600) } -func (s hugoSite) getResumeURL(resumeName string) (*url.URL, error) { - baseURL, err := url.Parse(s.config.BaseURL) - if err != nil { - return nil, err - } - return baseURL.Parse("/resumic/" + resumeName) +func (s hugoSite) getResumeURL(resumeName string) (string, error) { + return absURL(s.config.BaseURL, "/resumic/"+resumeName) } func (s hugoSite) build(themeDir string) error { resp := commands.Execute([]string{"--quiet", "--source", s.dir, "--themesDir", themeDir}) return resp.Err } - -func (s hugoSite) readPublic(u *url.URL) ([]byte, error) { - publicDir := path.Join(s.dir, "public") - client := &http.Client{} - client.Transport = http.NewFileTransport(http.Dir(publicDir)) - - response, err := client.Get(u.String()) - if err != nil { - return nil, err - } - if response.StatusCode != http.StatusOK { - return nil, &CouldNotReadPublicError{url: u, dir: s.dir} - } - defer response.Body.Close() - - return ioutil.ReadAll(response.Body) -} diff --git a/render/render.go b/render/render.go index 6bb71a1..b7cdee9 100644 --- a/render/render.go +++ b/render/render.go @@ -17,7 +17,6 @@ func RenderHTML(resumeJSON []byte, themeDir string) ([]byte, error) { return nil, err } defer os.RemoveAll(siteDir) - site, err := initHugoSite(siteDir) if err != nil { return nil, err @@ -27,20 +26,27 @@ func RenderHTML(resumeJSON []byte, themeDir string) ([]byte, error) { if err := site.writeResumeJSON(resumeJSON, resumeName); err != nil { return nil, err } + resumeURL, err := site.getResumeURL(resumeName) + if err != nil { + return nil, err + } if err := site.build(themeDir); err != nil { return nil, err } - resumeURL, err := site.getResumeURL(resumeName) + client, err := newClient(site) if err != nil { return nil, err } - - resumeHTML, err := site.readPublic(resumeURL) + response, err := client.Get(resumeURL) + if err != nil { + return nil, err + } + resumeHTML, err := ioutil.ReadAll(response.Body) if err != nil { return nil, err } - return embedHTML(resumeHTML, resumeURL, site) + return embedHTML(resumeHTML, resumeURL, client) }