package page import ( "bufio" "bytes" "fmt" "io" "os" "path/filepath" "strings" "text/template" "github.com/russross/blackfriday" "gopkg.in/yaml.v3" ) type Page struct { Path string Title string Head string Description string Tags map[string]interface{} Date *PageTime Published bool Vars map[string]interface{} Markdown []byte } // Can be adjusted to change the base template used in rendering var BaseTemplate = "inc/base.html" // Used to split the .md files into yaml and markdown var DocumentSplit = "|---\n" // 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 + ".md") 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) } var pageIndex map[string]PageList // 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(), ".md") { p2 := &Page{Path: strings.ReplaceAll(path, ".md", "")} 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 }