diff --git a/example.html b/example.html index 4f87ed4..89420e8 100644 --- a/example.html +++ b/example.html @@ -1,6 +1,14 @@ - + + + John Doe - Software Engineer - - \ No newline at end of file + +
+

Powerd By resumic

+ +
+ + + \ 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..7e6c294 --- /dev/null +++ b/render/embed.go @@ -0,0 +1,75 @@ +package render + +import ( + "bytes" + "encoding/base64" + "io/ioutil" + "net/http" + + "golang.org/x/net/html" +) + +func makeDataURL(resourceURL string, client *http.Client) (string, error) { + response, err := client.Get(resourceURL) + if err != nil { + return "", err + } + + 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 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 := absURL(baseURL, attr.Val) + if err != nil { + return nil + } + 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, client); err != nil { + return err + } + } + + return nil +} + +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, client); 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/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 new file mode 100644 index 0000000..b68ca9b --- /dev/null +++ b/render/hugo.go @@ -0,0 +1,98 @@ +package render + +import ( + "encoding/json" + "io/ioutil" + "net/url" + "os" + "path" + + "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"` +} + +type hugoConfig struct { + DisableKinds []string `json:"disableKinds"` + Theme string `json:"theme"` + BaseURL string `json:"baseURL"` + Title string `json:"title"` +} + +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 +} + +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 site, err + } + configPath := path.Join(site.dir, "config.json") + if err := ioutil.WriteFile(configPath, configJSON, 0600); err != nil { + return site, err + } + + return site, 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 := contentFrontmatter{} + content.Layout = "resumic" + contentJSON, err := json.MarshalIndent(content, "", " ") + if err != nil { + return err + } + 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 (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 +} diff --git a/render/render.go b/render/render.go index 278bec6..b7cdee9 100644 --- a/render/render.go +++ b/render/render.go @@ -1,80 +1,52 @@ package render import ( - "encoding/json" "io/ioutil" "os" - "path" - "github.com/gohugoio/hugo/commands" + "github.com/resumic/schema/schema" ) -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 { +func RenderHTML(resumeJSON []byte, themeDir string) ([]byte, error) { + if err := schema.ValidateResume(resumeJSON); 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, "", " ") + siteDir, err := ioutil.TempDir(os.TempDir(), "resumic") if err != nil { return nil, err } - configPath := path.Join(sitePath, "config.json") - err = ioutil.WriteFile(configPath, configJSON, 0600) + defer os.RemoveAll(siteDir) + site, err := initHugoSite(siteDir) if err != nil { return nil, err } - dataPath := path.Join(sitePath, "data", "resumic", "resume.json") - err = os.MkdirAll(path.Dir(dataPath), 0700) - if err != nil { + resumeName := "resume" + if err := site.writeResumeJSON(resumeJSON, resumeName); err != nil { return nil, err } - err = ioutil.WriteFile(dataPath, resume, 0600) + resumeURL, err := site.getResumeURL(resumeName) if err != nil { return nil, err } - content := frontmatter{} - content.Layout = "resumic" - - contentJSON, err := json.MarshalIndent(content, "", " ") - if err != nil { + if err := site.build(themeDir); err != nil { return nil, err } - contentPath := path.Join(sitePath, "content", "resumic", "resume.md") - err = os.MkdirAll(path.Dir(contentPath), 0700) + + client, err := newClient(site) if err != nil { return nil, err } - err = ioutil.WriteFile(contentPath, contentJSON, 0600) + response, err := client.Get(resumeURL) if err != nil { return nil, err } - - err = build(sitePath, themePath) + resumeHTML, err := ioutil.ReadAll(response.Body) if err != nil { return nil, err } - htmlPath := path.Join(sitePath, "public", "resumic", "resume", "index.html") - return ioutil.ReadFile(htmlPath) + return embedHTML(resumeHTML, resumeURL, client) } diff --git a/theme/defaults/test-theme/layouts/resumic/single.html b/theme/defaults/test-theme/layouts/resumic/single.html index dbc871c..ec88e68 100644 --- a/theme/defaults/test-theme/layouts/resumic/single.html +++ b/theme/defaults/test-theme/layouts/resumic/single.html @@ -1,6 +1,15 @@ + + + {{ $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 0000000..a9a96a0 Binary files /dev/null and b/theme/defaults/test-theme/static/resumic.jpg differ 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