From fe9ec7a0b45c9fd23a615a8b95ade3e9c1ea2d12 Mon Sep 17 00:00:00 2001 From: Mitchell Riedstra Date: Mon, 15 Feb 2021 15:31:37 -0500 Subject: Another re-structure. Deleting code is wonderful. --- cmd/server/main.go | 16 ++-- http/main.go | 71 ------------------ http/render.go | 62 ---------------- local/index.go | 71 ------------------ local/page.go | 199 -------------------------------------------------- local/pagelist.go | 21 ------ local/time.go | 23 ------ page/index.go | 78 ++++++++++++++++++++ page/main.go | 13 ---- page/page.go | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++ page/pagelist.go | 23 ++++++ page/render.go | 77 +++++++++++++++++++ page/time.go | 23 ++++++ 13 files changed, 422 insertions(+), 466 deletions(-) delete mode 100644 http/main.go delete mode 100644 http/render.go delete mode 100644 local/index.go delete mode 100644 local/page.go delete mode 100644 local/pagelist.go delete mode 100644 local/time.go create mode 100644 page/index.go delete mode 100644 page/main.go create mode 100644 page/page.go create mode 100644 page/pagelist.go create mode 100644 page/render.go create mode 100644 page/time.go diff --git a/cmd/server/main.go b/cmd/server/main.go index c900234..9ec96da 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -8,8 +8,7 @@ import ( "os" "time" - siteHttp "riedstra.dev/mitch/go-website/http" - "riedstra.dev/mitch/go-website/local" + "riedstra.dev/mitch/go-website/page" ) var VersionString = "" @@ -24,9 +23,9 @@ func main() { listen := fl.String("l", "0.0.0.0:8001", "Listening address") directory := fl.String("d", ".", "Directory to serve.") version := fl.Bool("v", false, "Print the version then exit") - fl.StringVar(&local.TimeFormat, "T", local.TimeFormat, "Print the version then exit") - fl.StringVar(&siteHttp.ReindexRedirectTo, "r", - siteHttp.ReindexRedirectTo, "Page to redirect to after reindex") + fl.StringVar(&page.TimeFormat, "T", page.TimeFormat, "Print the version then exit") + indexPath := fl.String("i", "/reIndex", + "Path in which, when called will rebuild the index and clear the cache") _ = fl.Parse(os.Args[1:]) if *version { @@ -37,8 +36,13 @@ func main() { log.Fatal(err) } + app := &App{ + ReIndexPath: *indexPath, + StaticDirectory: "static", + } + srv := &http.Server{ - Handler: siteHttp.GetHandler(), + Handler: app, Addr: *listen, WriteTimeout: 15 * time.Second, ReadTimeout: 15 * time.Second, diff --git a/http/main.go b/http/main.go deleted file mode 100644 index c4726d8..0000000 --- a/http/main.go +++ /dev/null @@ -1,71 +0,0 @@ -// The HTTP package handles serving up web pages from the page interface -// This package assumes you'll be changing directory to where the -// Website's static content, templates and includes are located -package http - -import ( - "fmt" - "log" - "net/http" - "os" - "path/filepath" - - "riedstra.dev/mitch/go-website/local" - "riedstra.dev/mitch/go-website/page" - - "github.com/gorilla/mux" -) - -// NewPage is required to specify how we acquire a new page from a given URL -// since an interface is used, you can swap this out with a different -// library to load pages from different sources -var NewPage func(string) page.Page = func(u string) page.Page { - return local.NewPage(u) -} - -// ReindexRedirectTo is the path that we'll redirect to when a call to rebuild -// index is called -var ReindexRedirectTo = "/fullIndex" - -// Logger can be overridden, errors and access logging are combined. -var Logger = log.New(os.Stderr, "", log.LstdFlags) - -// GetHandler Returns a gorilla/mux Router which implements the http.Handler -// interface, allowing you to pull this website into your other Go applications -func GetHandler() *mux.Router { - if NewPage == nil { - fmt.Fprintln(os.Stderr, "Warning, global NewPage method is not defined!") - } - - r := mux.NewRouter() - r.HandleFunc("/rebuildIndex/", RebuildIndexHandler) - r.PathPrefix("/static/").Handler(StaticHandler()) - r.PathPrefix("/").HandlerFunc(PageHandler) - return r -} - -// PageHandler is usually not called directly from external handlers, but the -// option exists if you're building something custom. -func PageHandler(w http.ResponseWriter, r *http.Request) { - u := r.URL.Path - if u == "/" { - u = "/index" - } - u = filepath.Join(".", u) - - RenderForPath(w, r, u) -} - -func RebuildIndexHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - p := NewPage("index") - _ = p.RebuildIndex() - http.Redirect(w, r, ReindexRedirectTo, 302) - } -} - -// StaticHandler simply returns a HTTP handler that looks at the current -// directory and exposes `static` via HTTP `/static` -func StaticHandler() http.Handler { - return http.StripPrefix("/static/", http.FileServer(http.Dir("static"))) -} diff --git a/http/render.go b/http/render.go deleted file mode 100644 index b73ae76..0000000 --- a/http/render.go +++ /dev/null @@ -1,62 +0,0 @@ -package http - -import ( - "net/http" - "path/filepath" - "strings" -) - -func Render(w http.ResponseWriter, r *http.Request, - path string, vars map[string]interface{}, statusCode int) { - - u := r.URL.Path - if u == "/" { - u = "/index" - } - u = filepath.Join(".", u) - - // Sepcifically use the specified path for the page - p := NewPage(path) - - if vars != nil { - p.SetVars(vars) - } - - err := p.Render(w) - if err != nil { - if strings.HasSuffix(err.Error(), "no such file or directory") { - Logger.Printf("%s %s %d %s", r.RemoteAddr, r.Method, 404, u) - p = NewPage("404") - w.WriteHeader(404) - err := p.Render(w) - if err != nil { - Logger.Printf("%s %s path: %s while trying 404: %s", r.RemoteAddr, r.Method, u, err) - http.Error(w, "Internal server error", 500) - return - } - return - } else { - Logger.Printf("%s %s path: %s encountered: %s", r.RemoteAddr, r.Method, u, err) - http.Error(w, "Internal server error", 500) - return - } - } - - Logger.Printf("%s %s %d %s", r.RemoteAddr, r.Method, statusCode, u) - -} - -// RenderWithVars allows you to specify a specific page and whether or not -// you wish to override vars. If left nil they will not be overridden. -// Also see RenderForPath if you don't need to override them -func RenderWithVars(w http.ResponseWriter, r *http.Request, - path string, vars map[string]interface{}) { - - Render(w, r, path, vars, 200) -} - -// RenderForPath takes the path to a page and finish up the rendering -// Allowing you to place logic on what page is rendered by your handlers -func RenderForPath(w http.ResponseWriter, r *http.Request, path string) { - RenderWithVars(w, r, path, nil) -} diff --git a/local/index.go b/local/index.go deleted file mode 100644 index 7b821f6..0000000 --- a/local/index.go +++ /dev/null @@ -1,71 +0,0 @@ -package local - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "time" -) - -var pageIndex map[string]PageList - -// RebuildIndex can be called in order to rebuild the entire website -// index -func (p *Page) RebuildIndex() error { - pageIndex = nil - _, err := p.Index() - return err -} - -// Index returns a map of all pages below the current Page's Path seperated -// into their respective tags If a Page has multiple tags it will be listed -// under each. -func (p *Page) Index() (map[string]PageList, error) { - if pageIndex != nil { - return pageIndex, nil - } - fmt.Fprintln(os.Stderr, "Rebuilding index...") - - out := make(map[string]PageList) - - var pageErr error - - filepath.Walk(filepath.Dir(p.path), - func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - if !info.IsDir() && strings.HasSuffix(info.Name(), Suffix) { - - p2 := NewPage(strings.ReplaceAll(path, Suffix, "")) - err = p2.Read() - - if err != nil { - pageErr = err - fmt.Fprintln(os.Stderr, "Error encountered: ", err) - return err - } - - for tag, _ := range p2.Tags { - if _, ok := out[tag]; !ok { - out[tag] = []*Page{p2} - } else { - out[tag] = append(out[tag], p2) - } - } - - } - - return nil - }) - - pageIndex = out - - return out, nil -} - -func (p *Page) Time() time.Time { - return p.Date.Time -} diff --git a/local/page.go b/local/page.go deleted file mode 100644 index 0ccaf2e..0000000 --- a/local/page.go +++ /dev/null @@ -1,199 +0,0 @@ -// local implements the website backed by a local filesystem. -// -// Reading the base template off the disk, then any markdown files which are -// split into two sections by the DocumentSplit global variable. The first -// section is parsed as yaml to populate the Page struct. The second portion is -// markdown, first executed as part of the text/template then rendered by -// blackfriday. -// -// Usage: -// -// import ( -// "fmt" -// "os" -// site "riedstra.dev/mitch/go-website/local" -// ) -// // Where some/path.md exists -// p := site.NewPage("/some/path") -// // Dump the rendered HTML to stdout -// err := p.Render(os.Stdout) -// if err != nil { -// fmt.Fprintln(os.Stderr, err) -// } -package local - -import ( - "bufio" - "bytes" - "fmt" - "io" - "os" - "text/template" - - "github.com/russross/blackfriday" - "gopkg.in/yaml.v3" -) - -// Page should not be created directly as it will not set the interal path -// properly. Use NewPage instead. -// -// The exported fields can be filled in the yaml at the top of a page and -// utilized within. -type Page struct { - path string - Title string - Head string - Description string - // Tags to apply to the page in question. Useful for Index() - Tags map[string]interface{} - Date *PageTime - Published bool - Vars map[string]interface{} - markdown []byte -} - -// Global is meant to be supplied by external users of this package to populate -// globally accessable information across all of the templates accessiable via -// .Global care must be taken when utilizing this functionality -var Global interface{} - -// BaseTemplate can be adjusted to change the base template used in rendering -var BaseTemplate = "inc/base.html" - -// Suffix is applied to all pages for reading off of the disk -var Suffix = ".md" - -// DocumentSplit is used to split the .md files into yaml and markdown -var DocumentSplit = "|---\n" - -// NewPage returns a page struct with the path populated -func NewPage(pth string) *Page { - return &Page{path: pth} -} - -// NewPage Allow for the creation of a new page from the current page, does -// not inlcude any information about the current page. -func (p Page) NewPage(pth string) *Page { - return NewPage(pth) -} - -// Path gets the current path set on the struct for the page in question -// Useful if you're say iterating across tags to print out a list of -// relevant posts on a blog or so by topic. -func (p Page) Path() string { - return p.path -} - -// Global is specifically for use inside of a page markdown file or -// in a base template. This simply returns the package Global variable -func (p *Page) Global() interface{} { - return Global -} - -// SetVars Will set to `nil` if provided -func (p *Page) SetVars(vars map[string]interface{}) { - p.Vars = vars -} - -// Renders a page -func (p *Page) Render(wr io.Writer) error { - if err := p.Read(); err != nil { - return err - } - - t, err := template.ParseFiles(BaseTemplate) - if err != nil { - return err - } - - // Automatically pull from the yml file if applicable - if p.Head != "" { - t, err = t.Parse(` - {{define "head"}} - {{.RenderHead}} - {{end}} - `) - if err != nil { - return err - } - } - - return t.Execute(wr, p) -} - -func (p *Page) RenderHead() (string, error) { - buf := &bytes.Buffer{} - t, err := template.New("Head").Parse(p.Head) - if err != nil { - return "", err - } - err = t.Execute(buf, p) - if err != nil { - return "", err - } - return string(buf.Bytes()), nil -} - -// Reads in the special markdown file format for the website off of the disk -func (p *Page) Read() error { - yamlBuf := bytes.NewBuffer(nil) - markdownBuf := bytes.NewBuffer(nil) - - fh, err := os.Open(p.path + Suffix) - if err != nil { - return err - } - defer fh.Close() - rdr := bufio.NewReader(fh) - - // Read in the file and split between markdown and yaml buffers - yamlDone := false - for { - - bytes, err := rdr.ReadBytes('\n') - if err == io.EOF { - break - } else if err != nil { - return err - } - - // Is this the line where we stop reading the yaml and start reading markdown? - if DocumentSplit == string(bytes) && !yamlDone { - yamlDone = true - continue - } - - if !yamlDone { - yamlBuf.Write(bytes) - } else { - markdownBuf.Write(bytes) - } - } - - err = yaml.Unmarshal(yamlBuf.Bytes(), p) - if err != nil { - return err - } - - p.markdown = markdownBuf.Bytes() - return nil -} - -func (p *Page) RenderBody() (string, error) { - buf := &bytes.Buffer{} - t, err := template.New("Body").Parse(string(p.markdown)) - if err != nil { - return "", err - } - - err = t.Execute(buf, p) - if err != nil { - return "", err - } - - return string(blackfriday.Run(buf.Bytes())), nil -} - -func (p Page) String() string { - return fmt.Sprintf("Page: %s", p.path) -} diff --git a/local/pagelist.go b/local/pagelist.go deleted file mode 100644 index ea76117..0000000 --- a/local/pagelist.go +++ /dev/null @@ -1,21 +0,0 @@ -package local - -import ( - "sort" -) - -type PageList []*Page - -func (p PageList) SortDate() PageList { - sort.Slice(p, func(i, j int) bool { - return p[i].Time().After(p[j].Time()) - }) - return p -} - -func (p PageList) SortDateReverse() PageList { - sort.Slice(p, func(i, j int) bool { - return p[i].Time().Before(p[j].Time()) - }) - return p -} diff --git a/local/time.go b/local/time.go deleted file mode 100644 index 27efc88..0000000 --- a/local/time.go +++ /dev/null @@ -1,23 +0,0 @@ -package local - -import ( - "gopkg.in/yaml.v3" - "time" -) - -type PageTime struct { - time.Time -} - -// TimeFormat is the format string used when unmarshaling the time -// from the yaml information. -var TimeFormat = "01.02.2006 15:04:05 MST" - -func (pt *PageTime) UnmarshalYAML(n *yaml.Node) error { - t, err := time.Parse(TimeFormat, n.Value) - if err != nil { - return err - } - pt.Time = t - return nil -} diff --git a/page/index.go b/page/index.go new file mode 100644 index 0000000..a453335 --- /dev/null +++ b/page/index.go @@ -0,0 +1,78 @@ +package page + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "time" +) + +var index map[string]PageList +var indexMu sync.RWMutex + +// RebuildIndex can be called in order to rebuild the entire website +// index +func (p *Page) RebuildIndex() error { + indexMu.Lock() + index = nil + indexMu.Unlock() + _, err := p.Index() + return err +} + +// Index returns a map of all pages in the current directory seperated into +// their respective tags If a Page has multiple tags it will be listed under +// each. +// Pages are located by their Suffix, default being ".md" +func (p *Page) Index() (map[string]PageList, error) { + indexMu.RLock() + if index != nil && CacheIndex { + indexMu.RUnlock() + return index, nil + } + indexMu.RUnlock() + Logger.Println("Rebuilding index...") + + out := make(map[string]PageList) + + filepath.Walk(filepath.Dir("."), + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if !info.IsDir() && strings.HasSuffix(info.Name(), Suffix) { + + p2 := NewPage(strings.ReplaceAll(path, Suffix, "")) + err = p2.Read() + + if err != nil { + fmt.Fprintln(os.Stderr, "Error encountered: ", err) + return err + } + + for tag, _ := range p2.Tags { + if _, ok := out[tag]; !ok { + out[tag] = []*Page{p2} + } else { + out[tag] = append(out[tag], p2) + } + } + + } + + return nil + }) + + indexMu.Lock() + index = out + indexMu.Unlock() + + return out, nil +} + +func (p *Page) Time() time.Time { + return p.Date.Time +} diff --git a/page/main.go b/page/main.go deleted file mode 100644 index da7feec..0000000 --- a/page/main.go +++ /dev/null @@ -1,13 +0,0 @@ -// The only purpose of this package is to define the Page interface -// that is used by the `http` package -package page - -import ( - "io" -) - -type Page interface { - Render(io.Writer) error - RebuildIndex() error - SetVars(map[string]interface{}) -} diff --git a/page/page.go b/page/page.go new file mode 100644 index 0000000..82f46f2 --- /dev/null +++ b/page/page.go @@ -0,0 +1,211 @@ +// page implements the website backed by a local filesystem. +// +// Reading the base template off the disk, then any markdown files which are +// split into two sections by the DocumentSplit global variable. The first +// section is parsed as yaml to populate the Page struct. The second portion is +// markdown, first executed as part of the text/template then rendered by +// blackfriday. +// +// Usage: +// +// import ( +// "fmt" +// "os" +// site "riedstra.dev/mitch/go-website/page" +// ) +// // Where some/path.md exists +// p := site.NewPage("/some/path") +// // Dump the rendered HTML to stdout +// err := p.Render(os.Stdout) +// if err != nil { +// fmt.Fprintln(os.Stderr, err) +// } +package page + +import ( + "bufio" + "bytes" + "fmt" + "io" + "log" + "os" + "text/template" + + "github.com/russross/blackfriday" + "gopkg.in/yaml.v3" +) + +// Page should not be created directly as it will not set the interal path +// properly. Use NewPage instead. +// +// The exported fields can be filled in the yaml at the top of a page and +// utilized within. +type Page struct { + path string + Title string + Head string + Description string + // Tags to apply to the page in question. Useful for Index() + Tags map[string]interface{} + Date *PageTime + Published bool + Vars map[string]interface{} + markdown []byte +} + +// Global is meant to be supplied by external users of this package to populate +// globally accessable information across all of the templates accessiable via +// .Global care must be taken when utilizing this functionality +var Global interface{} + +// CachePages determines whether or not the rendered page will be stored in +// memory +var CachePages = true + +// CacheIndex determines whether or not the index will be cached in memory +// or rebuilt on each call +var CacheIndex = true + +// BaseTemplate can be adjusted to change the base template used in rendering +var BaseTemplate = "inc/base.html" + +// Suffix is applied to all pages for reading off of the disk +var Suffix = ".md" + +// DocumentSplit is used to split the .md files into yaml and markdown +var DocumentSplit = "|---\n" + +// Default logger +var Logger = log.New(os.Stderr, "", log.LstdFlags) + +// NewPage returns a page struct with the path populated +func NewPage(pth string) *Page { + return &Page{path: pth} +} + +// NewPage Allow for the creation of a new page from the current page, does +// not inlcude any information about the current page. +func (p Page) NewPage(pth string) *Page { + return NewPage(pth) +} + +// Path gets the current path set on the struct for the page in question +// Useful if you're say iterating across tags to print out a list of +// relevant posts on a blog or so by topic. +func (p Page) Path() string { + return p.path +} + +// Global is specifically for use inside of a page markdown file or +// in a base template. This simply returns the package Global variable +func (p *Page) Global() interface{} { + return Global +} + +// SetVars Will set to `nil` if provided +func (p *Page) SetVars(vars map[string]interface{}) { + p.Vars = vars +} + +// Renders a page +func (p *Page) Render(wr io.Writer) error { + if err := p.Read(); err != nil { + return err + } + + t, err := template.ParseFiles(BaseTemplate) + if err != nil { + return err + } + + // Automatically pull from the yml file if applicable + if p.Head != "" { + t, err = t.Parse(` + {{define "head"}} + {{.RenderHead}} + {{end}} + `) + if err != nil { + return err + } + } + + return t.Execute(wr, p) +} + +func (p *Page) RenderHead() (string, error) { + buf := &bytes.Buffer{} + t, err := template.New("Head").Parse(p.Head) + if err != nil { + return "", err + } + err = t.Execute(buf, p) + if err != nil { + return "", err + } + return string(buf.Bytes()), nil +} + +// Reads in the special markdown file format for the website off of the disk +func (p *Page) Read() error { + yamlBuf := bytes.NewBuffer(nil) + markdownBuf := bytes.NewBuffer(nil) + + fh, err := os.Open(p.path + Suffix) + if err != nil { + return err + } + defer fh.Close() + rdr := bufio.NewReader(fh) + + // Read in the file and split between markdown and yaml buffers + yamlDone := false + for { + + bytes, err := rdr.ReadBytes('\n') + if err == io.EOF { + break + } else if err != nil { + return err + } + + // Is this the line where we stop reading the yaml and start reading markdown? + if DocumentSplit == string(bytes) && !yamlDone { + yamlDone = true + continue + } + + if !yamlDone { + yamlBuf.Write(bytes) + } else { + markdownBuf.Write(bytes) + } + } + + err = yaml.Unmarshal(yamlBuf.Bytes(), p) + if err != nil { + return err + } + + p.markdown = markdownBuf.Bytes() + return nil +} + +func (p *Page) RenderBody() (string, error) { + buf := &bytes.Buffer{} + t, err := template.New("Body").Parse(string(p.markdown)) + if err != nil { + return "", err + } + + err = t.Execute(buf, p) + if err != nil { + return "", err + } + + return string(blackfriday.Run(buf.Bytes())), nil +} + +func (p Page) String() string { + return fmt.Sprintf("Page: %s", p.path) +} diff --git a/page/pagelist.go b/page/pagelist.go new file mode 100644 index 0000000..a5cf844 --- /dev/null +++ b/page/pagelist.go @@ -0,0 +1,23 @@ +package page + +import ( + "sort" +) + +// PageList is a slice of pages, providing a couple of methods to sort +// by the date, or date reversed +type PageList []*Page + +func (p PageList) SortDate() PageList { + sort.Slice(p, func(i, j int) bool { + return p[i].Time().After(p[j].Time()) + }) + return p +} + +func (p PageList) SortDateReverse() PageList { + sort.Slice(p, func(i, j int) bool { + return p[i].Time().Before(p[j].Time()) + }) + return p +} diff --git a/page/render.go b/page/render.go new file mode 100644 index 0000000..4378675 --- /dev/null +++ b/page/render.go @@ -0,0 +1,77 @@ +package page + +import ( + "net/http" + "path/filepath" + "strings" +) + +// Render is a lower level option, allowing you to specify local +// variables and the status code in which to return +func Render(w http.ResponseWriter, r *http.Request, + path string, vars map[string]interface{}, statusCode int) { + + u := r.URL.Path + if u == "/" { + u = "/index" + } + u = filepath.Join(".", u) + + // Sepcifically use the specified path for the page + p := NewPage(path) + + if vars != nil { + p.SetVars(vars) + } + + err := p.Render(w) + if err != nil { + if strings.HasSuffix(err.Error(), "no such file or directory") { + Logger.Printf("%s %s %d %s", + r.RemoteAddr, + r.Method, + http.StatusNotFound, + u) + p = NewPage("404") + w.WriteHeader(http.StatusNotFound) + err := p.Render(w) + if err != nil { + Logger.Printf("%s %s path: %s while trying 404: %s", + r.RemoteAddr, + r.Method, + u, + err) + http.Error(w, "Internal server error", + http.StatusInternalServerError) + return + } + return + } else { + Logger.Printf("%s %s path: %s encountered: %s", + r.RemoteAddr, + r.Method, + u, + err) + http.Error(w, "Internal server error", + http.StatusInternalServerError) + return + } + } + + Logger.Printf("%s %s %d %s", r.RemoteAddr, r.Method, statusCode, u) +} + +// RenderWithVars allows you to specify a specific page and whether or not +// you wish to override vars. If left nil they will not be overridden. +// Also see RenderForPath if you don't need to override them +func RenderWithVars(w http.ResponseWriter, r *http.Request, + path string, vars map[string]interface{}) { + + Render(w, r, path, vars, http.StatusOK) +} + +// RenderForPath takes the path to a page and finish up the rendering +// Allowing you to place logic on what page is rendered by your handlers +func RenderForPath(w http.ResponseWriter, r *http.Request, path string) { + RenderWithVars(w, r, path, nil) +} diff --git a/page/time.go b/page/time.go new file mode 100644 index 0000000..958dc38 --- /dev/null +++ b/page/time.go @@ -0,0 +1,23 @@ +package page + +import ( + "gopkg.in/yaml.v3" + "time" +) + +type PageTime struct { + time.Time +} + +// TimeFormat is the format string used when unmarshaling the time +// from the yaml information. +var TimeFormat = "01.02.2006 15:04:05 MST" + +func (pt *PageTime) UnmarshalYAML(n *yaml.Node) error { + t, err := time.Parse(TimeFormat, n.Value) + if err != nil { + return err + } + pt.Time = t + return nil +} -- cgit v1.2.3