diff --git a/Makefile b/Makefile index b7e015c..d38de05 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,14 @@ build: gofmt -w . go build $(LDFLAGS) -i -o bin/blog cmd/blog/main.go +# `make bindata` to make the bindata module. +# `make bindata-dev` for debug mode module for editing files locally. +.PHONY: bindata bindata-dev +bindata: + go-bindata -pkg root -prefix root/ -o src/root/bundle.go root/... +bindata-dev: + go-bindata -debug -pkg root -prefix root/ -o src/root/bundle.go root/... + # `make run` to run it in debug mode. .PHONY: run run: diff --git a/pages.go b/pages.go index fdafe7b..346459b 100644 --- a/pages.go +++ b/pages.go @@ -3,7 +3,9 @@ package blog import ( "html/template" "io/ioutil" + "mime" "net/http" + "path/filepath" "strings" "github.com/kirsle/blog/src/controllers/posts" @@ -11,6 +13,7 @@ import ( "github.com/kirsle/blog/src/markdown" "github.com/kirsle/blog/src/render" "github.com/kirsle/blog/src/responses" + "github.com/kirsle/blog/src/root" ) // PageHandler is the catch-all route handler, for serving static web pages. @@ -31,7 +34,7 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) { } // Search for a file that matches their URL. - filepath, err := render.ResolvePath(path) + fp, err := render.ResolvePath(path) if err != nil { // See if it resolves as a blog entry. err = postctl.ViewPost(w, r, strings.TrimLeft(path, "/")) @@ -43,17 +46,30 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) { } // Is it a template file? - if strings.HasSuffix(filepath.URI, ".gohtml") { - render.Template(w, r, filepath.URI, nil) + if strings.HasSuffix(fp.URI, ".gohtml") { + render.Template(w, r, fp.URI, nil) return } // Is it a Markdown file? - if strings.HasSuffix(filepath.URI, ".md") || strings.HasSuffix(filepath.URI, ".markdown") { - source, err := ioutil.ReadFile(filepath.Absolute) - if err != nil { - responses.Error(w, r, "Couldn't read Markdown source!") - return + if strings.HasSuffix(fp.URI, ".md") || strings.HasSuffix(fp.URI, ".markdown") { + var source []byte + if len(fp.BindataKey) > 0 { + data, err := root.Asset(fp.BindataKey) + if err != nil { + responses.Error(w, r, "Couldn't read bindata key: "+fp.BindataKey) + return + } + + source = data + } else { + data, err := ioutil.ReadFile(fp.Absolute) + if err != nil { + responses.Error(w, r, "Couldn't read Markdown source!") + return + } + + source = data } // Render it to HTML and find out its title. @@ -64,10 +80,22 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) { render.Template(w, r, ".markdown", map[string]interface{}{ "Title": title, "HTML": template.HTML(html), - "MarkdownPath": filepath.URI, + "MarkdownPath": fp.URI, }) return } - http.ServeFile(w, r, filepath.Absolute) + // It's a regular static file we can serve directly. + { + // Check if we have bindata for it. + if fp.BindataKey != "" { + data, _ := root.Asset(fp.BindataKey) + w.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(fp.URI))) + w.Write(data) + return + } + + // Try the filesystem. + http.ServeFile(w, r, fp.Absolute) + } } diff --git a/src/mail/mail.go b/src/mail/mail.go index 12f0483..50b1c7c 100644 --- a/src/mail/mail.go +++ b/src/mail/mail.go @@ -8,11 +8,12 @@ import ( "net/url" "strings" + "github.com/kirsle/blog/models/comments" + "github.com/kirsle/blog/models/settings" "github.com/kirsle/blog/src/log" "github.com/kirsle/blog/src/markdown" "github.com/kirsle/blog/src/render" - "github.com/kirsle/blog/models/comments" - "github.com/kirsle/blog/models/settings" + "github.com/kirsle/blog/src/root" "github.com/microcosm-cc/bluemonday" gomail "gopkg.in/gomail.v2" ) @@ -51,10 +52,21 @@ func SendEmail(email Email) { // Render the template to HTML. var html bytes.Buffer t := template.New(tmpl.Basename) - t, err = template.ParseFiles(tmpl.Absolute) + + // Load it from bindata or filesystem. + if tmpl.BindataKey != "" { + log.Debug("Parse %s from bindata", tmpl.BindataKey) + asset, _ := root.Asset(tmpl.BindataKey) + t, err = t.Parse(string(asset)) + } else { + log.Debug("Parse %s from file", tmpl.Absolute) + t, err = t.ParseFiles(tmpl.Absolute) + } + if err != nil { log.Error("SendEmail: template parsing error: %s", err.Error()) } + err = t.ExecuteTemplate(&html, tmpl.Basename, email) if err != nil { log.Error("SendEmail: template execution error: %s", err.Error()) diff --git a/src/render/resolve_paths.go b/src/render/resolve_paths.go index 6c16548..5c45b9f 100644 --- a/src/render/resolve_paths.go +++ b/src/render/resolve_paths.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/kirsle/blog/src/log" + "github.com/kirsle/blog/src/root" ) // Blog configuration bindings. @@ -37,6 +38,10 @@ type Filepath struct { Basename string Relative string // Relative path including document root (i.e. "root/about.html") Absolute string // Absolute path on disk (i.e. "/opt/blog/root/about.html") + + // If file was resolved to embedded bindata, this is the bindata key name. + // Zero value means it resolved to a file on filesystem. + BindataKey string } func (f Filepath) String() string { @@ -55,39 +60,78 @@ func ResolvePath(path string) (Filepath, error) { // If you need to debug this function, edit this block. debug := func(tmpl string, args ...interface{}) { - if false { + if true { // edit this to enable log.Debug(tmpl, args...) } } - debug("Resolving filepath for URI: %s", path) - for _, root := range []string{*UserRoot, *DocumentRoot} { - if len(root) == 0 { - continue - } + debug("ResolvePath(%s) called", path) + + if len(*UserRoot) > 0 { + debug("1. Resolving filepath for URI in user root: %s", path) // Resolve the file path. - relPath := filepath.Join(root, path) + relPath := filepath.Join(*UserRoot, path) absPath, err := filepath.Abs(relPath) basename := filepath.Base(relPath) if err != nil { log.Error("%v", err) } - debug("Expected filepath: %s", absPath) + debug(" Expected filepath: %s", absPath) // Found an exact hit? if stat, err := os.Stat(absPath); !os.IsNotExist(err) && !stat.IsDir() { - debug("Exact filepath found: %s", absPath) - return Filepath{path, basename, relPath, absPath}, nil + debug(" + Exact filepath found: %s", absPath) + return Filepath{ + URI: path, + Basename: basename, + Relative: relPath, + Absolute: absPath, + }, nil } // Try some supported suffixes. for _, suffix := range hiddenSuffixes { test := absPath + suffix if stat, err := os.Stat(test); !os.IsNotExist(err) && !stat.IsDir() { - debug("Filepath found via suffix %s: %s", suffix, test) - return Filepath{path + suffix, basename + suffix, relPath + suffix, test}, nil + debug(" + Filepath found via suffix %s: %s", suffix, test) + return Filepath{ + URI: path + suffix, + Basename: basename + suffix, + Relative: relPath + suffix, + Absolute: test, + }, nil + } + } + } + + debug("2. Not found in filesystem, checking bindata for: %s", path) + { + // Exact hit? + if _, err := root.Asset(path); err == nil { + debug(" Found in bindata as: %s", path) + return Filepath{ + URI: path, + Basename: filepath.Base(path), + Relative: path, + Absolute: path, + BindataKey: path, + }, nil + } + + // Try some supported suffixes. + for _, suffix := range hiddenSuffixes { + test := path + suffix + if _, err := root.Asset(test); err == nil { + debug(" Filepath found via suffix %s: %s", suffix, test) + return Filepath{ + URI: test, + Basename: filepath.Base(test), + Relative: test, + Absolute: test, + BindataKey: test, + }, nil } } } diff --git a/src/render/templates.go b/src/render/templates.go index d083247..cd5d62d 100644 --- a/src/render/templates.go +++ b/src/render/templates.go @@ -8,13 +8,14 @@ import ( "time" gorilla "github.com/gorilla/sessions" + "github.com/kirsle/blog/models/settings" + "github.com/kirsle/blog/models/users" "github.com/kirsle/blog/src/log" "github.com/kirsle/blog/src/middleware" "github.com/kirsle/blog/src/middleware/auth" + "github.com/kirsle/blog/src/root" "github.com/kirsle/blog/src/sessions" "github.com/kirsle/blog/src/types" - "github.com/kirsle/blog/models/settings" - "github.com/kirsle/blog/models/users" ) // Vars is an interface to implement by the templates to pass their own custom @@ -113,19 +114,19 @@ func Template(w io.Writer, r *http.Request, path string, data interface{}) error // Get the layout template. if !isPartial { templateName = "layout" - layout, err = ResolvePath(".layout") + layout, err = ResolvePath(".layout.gohtml") if err != nil { log.Error("RenderTemplate(%s): layout template not found", path) return err } } else { - templateName = filepath.Basename + templateName = filepath.Absolute } // The comment entry partial. - commentEntry, err := ResolvePath("comments/entry.partial") + commentEntry, err := ResolvePath("comments/entry.partial.gohtml") if err != nil { - log.Error("RenderTemplate(%s): comments/entry.partial not found") + log.Error("RenderTemplate: comments/entry.partial not found") return err } @@ -135,17 +136,28 @@ func Template(w io.Writer, r *http.Request, path string, data interface{}) error // and allows the filepath template to set the page title. var templates []string if !isPartial { - templates = append(templates, layout.Absolute) + templates = append(templates, layout.Absolute, commentEntry.Absolute, filepath.Absolute) } - t, err = t.ParseFiles(append(templates, commentEntry.Absolute, filepath.Absolute)...) - if err != nil { - log.Error(err.Error()) - return err + + for _, filename := range templates { + + if asset, err2 := root.Asset(filename); err2 == nil { + log.Debug("Parse %s from bindata", filename) + t, err = t.Parse(string(asset)) + } else { + log.Debug("Parse %s from file", filename) + t, err = t.ParseFiles(filename) + } + + if err != nil { + log.Error(err.Error()) + return err + } } err = t.ExecuteTemplate(w, templateName, v) if err != nil { - log.Error("Template parsing error: %s", err) + log.Error("Template parsing error(tmpl name: %s; ): %s", err) return err }