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. --- page/page.go | 211 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 page/page.go (limited to 'page/page.go') 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) +} -- cgit v1.2.3