// 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 Description string AuthorName string AuthorEmail 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{} // 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 } // 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 } return t.Execute(wr, p) } // 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) }