2017-06-22 17:02:32 +02:00
|
|
|
package pages
|
2017-06-10 21:38:09 +02:00
|
|
|
|
|
|
|
import (
|
2017-06-29 16:13:25 +02:00
|
|
|
"bytes"
|
2017-07-03 15:37:14 +02:00
|
|
|
"fmt"
|
2017-06-29 16:13:25 +02:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2017-07-14 16:59:32 +02:00
|
|
|
"sync"
|
2017-07-03 15:37:14 +02:00
|
|
|
"time"
|
2017-06-10 21:38:09 +02:00
|
|
|
|
2017-07-09 17:57:20 +02:00
|
|
|
"github.com/osteele/gojekyll/frontmatter"
|
2017-06-22 16:42:57 +02:00
|
|
|
"github.com/osteele/gojekyll/templates"
|
2017-07-05 17:35:20 +02:00
|
|
|
"github.com/osteele/liquid/evaluator"
|
2017-06-10 21:38:09 +02:00
|
|
|
)
|
|
|
|
|
2017-07-07 18:24:00 +02:00
|
|
|
// Page is a document with frontmatter.
|
|
|
|
type Page interface {
|
|
|
|
Document
|
|
|
|
// Content asks a page to compute its content.
|
|
|
|
// This has the side effect of causing the content to subsequently appear in the drop.
|
2017-07-10 19:23:51 +02:00
|
|
|
Content() ([]byte, error)
|
2017-07-09 01:57:41 +02:00
|
|
|
SetContent(content []byte)
|
|
|
|
FrontMatter() map[string]interface{}
|
2017-07-07 18:24:00 +02:00
|
|
|
// PostDate returns the date computed from the filename or frontmatter.
|
|
|
|
// It is an uncaught error to call this on a page that is not a Post.
|
|
|
|
// TODO Should posts have their own interface?
|
|
|
|
PostDate() time.Time
|
2017-07-13 18:28:02 +02:00
|
|
|
|
|
|
|
Categories() []string
|
|
|
|
Tags() []string
|
2017-07-07 18:24:00 +02:00
|
|
|
}
|
|
|
|
|
2017-07-02 19:46:05 +02:00
|
|
|
type page struct {
|
2017-06-29 16:13:25 +02:00
|
|
|
file
|
2017-07-14 16:59:32 +02:00
|
|
|
sync.Mutex
|
2017-07-09 17:57:20 +02:00
|
|
|
firstLine int
|
|
|
|
raw []byte
|
|
|
|
content *[]byte
|
2017-06-10 21:38:09 +02:00
|
|
|
}
|
|
|
|
|
2017-06-29 16:13:25 +02:00
|
|
|
// Static is in the File interface.
|
2017-07-02 19:46:05 +02:00
|
|
|
func (p *page) Static() bool { return false }
|
2017-06-19 04:48:33 +02:00
|
|
|
|
2017-07-09 01:57:41 +02:00
|
|
|
func makePage(filename string, f file) (*page, error) {
|
2017-06-29 16:13:25 +02:00
|
|
|
b, err := ioutil.ReadFile(filename)
|
2017-06-17 06:16:59 +02:00
|
|
|
if err != nil {
|
2017-06-22 16:37:31 +02:00
|
|
|
return nil, err
|
2017-06-17 06:16:59 +02:00
|
|
|
}
|
2017-07-09 17:57:20 +02:00
|
|
|
lineNo := 1
|
|
|
|
frontMatter, err := frontmatter.Read(&b, &lineNo)
|
2017-06-15 15:07:06 +02:00
|
|
|
if err != nil {
|
2017-06-22 16:37:31 +02:00
|
|
|
return nil, err
|
2017-06-15 15:07:06 +02:00
|
|
|
}
|
2017-06-29 16:13:25 +02:00
|
|
|
f.frontMatter = templates.MergeVariableMaps(f.frontMatter, frontMatter)
|
2017-07-09 01:57:41 +02:00
|
|
|
p := page{
|
2017-07-09 17:57:20 +02:00
|
|
|
file: f,
|
|
|
|
firstLine: lineNo,
|
|
|
|
raw: b,
|
2017-07-09 01:57:41 +02:00
|
|
|
}
|
|
|
|
if err = p.setPermalink(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &p, nil
|
2017-06-29 16:13:25 +02:00
|
|
|
}
|
|
|
|
|
2017-07-13 18:28:02 +02:00
|
|
|
func (p *page) FrontMatter() map[string]interface{} {
|
|
|
|
return p.frontMatter
|
|
|
|
}
|
|
|
|
|
|
|
|
// Categories is in the Page interface
|
|
|
|
func (p *page) Categories() []string {
|
|
|
|
return frontmatter.FrontMatter(p.frontMatter).SortedStringArray("categories")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tags is in the Page interface
|
|
|
|
func (p *page) Tags() []string {
|
|
|
|
return frontmatter.FrontMatter(p.frontMatter).SortedStringArray("tags")
|
|
|
|
}
|
|
|
|
|
2017-06-29 16:13:25 +02:00
|
|
|
// TemplateContext returns the local variables for template evaluation
|
2017-07-10 19:23:51 +02:00
|
|
|
func (p *page) TemplateContext() map[string]interface{} {
|
2017-07-01 05:56:29 +02:00
|
|
|
return map[string]interface{}{
|
2017-07-03 15:37:14 +02:00
|
|
|
"page": p,
|
2017-07-10 19:23:51 +02:00
|
|
|
"site": p.site,
|
2017-06-29 16:13:25 +02:00
|
|
|
}
|
|
|
|
}
|
2017-06-15 15:07:06 +02:00
|
|
|
|
2017-07-03 15:37:14 +02:00
|
|
|
// PostDate is part of the Page interface.
|
2017-07-10 19:54:52 +02:00
|
|
|
// FIXME move this back to Page interface, or re-work this entirely.
|
|
|
|
func (f *file) PostDate() time.Time {
|
|
|
|
switch value := f.frontMatter["date"].(type) {
|
2017-07-03 15:37:14 +02:00
|
|
|
case time.Time:
|
|
|
|
return value
|
|
|
|
case string:
|
2017-07-10 19:54:52 +02:00
|
|
|
t, err := evaluator.ParseDate(value)
|
2017-07-03 15:37:14 +02:00
|
|
|
if err == nil {
|
|
|
|
return t
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("expected a date %v", value))
|
|
|
|
}
|
|
|
|
panic("read posts should have set this")
|
|
|
|
}
|
|
|
|
|
2017-06-29 16:13:25 +02:00
|
|
|
// Write applies Liquid and Markdown, as appropriate.
|
2017-07-10 19:23:51 +02:00
|
|
|
func (p *page) Write(w io.Writer) error {
|
|
|
|
content, err := p.Content()
|
2017-06-29 16:13:25 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-07-04 23:13:47 +02:00
|
|
|
layout, ok := p.frontMatter["layout"].(string)
|
|
|
|
if ok && layout != "" {
|
2017-07-10 19:23:51 +02:00
|
|
|
rp := p.site.RenderingPipeline()
|
|
|
|
content, err = rp.ApplyLayout(layout, content, p.TemplateContext())
|
2017-06-29 16:13:25 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2017-07-08 20:38:37 +02:00
|
|
|
_, err = w.Write(content)
|
2017-06-29 16:13:25 +02:00
|
|
|
return err
|
2017-06-15 15:07:06 +02:00
|
|
|
}
|
2017-06-22 18:56:08 +02:00
|
|
|
|
2017-07-01 15:35:54 +02:00
|
|
|
// Content computes the page content.
|
2017-07-10 19:23:51 +02:00
|
|
|
func (p *page) Content() ([]byte, error) {
|
2017-07-14 16:59:32 +02:00
|
|
|
content := p.maybeContent(false)
|
|
|
|
if content != nil {
|
|
|
|
return content, nil
|
2017-07-09 22:09:03 +02:00
|
|
|
}
|
2017-07-10 19:23:51 +02:00
|
|
|
pipe := p.site.RenderingPipeline()
|
2017-07-09 22:09:03 +02:00
|
|
|
buf := new(bytes.Buffer)
|
2017-07-10 19:23:51 +02:00
|
|
|
b, err := pipe.Render(buf, p.raw, p.filename, p.firstLine, p.TemplateContext())
|
2017-07-09 22:09:03 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2017-06-22 18:56:08 +02:00
|
|
|
}
|
2017-07-14 16:59:32 +02:00
|
|
|
p.SetContent(b)
|
2017-07-09 22:09:03 +02:00
|
|
|
return b, nil
|
2017-06-22 18:56:08 +02:00
|
|
|
}
|
2017-07-09 01:57:41 +02:00
|
|
|
|
2017-07-14 16:59:32 +02:00
|
|
|
// retains its argument
|
2017-07-09 01:57:41 +02:00
|
|
|
func (p *page) SetContent(content []byte) {
|
2017-07-14 16:59:32 +02:00
|
|
|
p.Lock()
|
|
|
|
defer p.Unlock()
|
2017-07-09 01:57:41 +02:00
|
|
|
p.content = &content
|
|
|
|
}
|