1
0
mirror of https://github.com/danog/gojekyll.git synced 2024-12-02 12:57:53 +01:00
gojekyll/pages/page.go

259 lines
5.5 KiB
Go
Raw Normal View History

2017-06-22 17:02:32 +02:00
package pages
import (
"bytes"
2017-07-03 15:37:14 +02:00
"fmt"
"io"
"io/ioutil"
2017-08-18 16:55:34 +02:00
"os"
2017-07-25 17:24:37 +02:00
"path"
2017-08-16 21:50:31 +02:00
"strings"
2017-07-14 16:59:32 +02:00
"sync"
2017-07-03 15:37:14 +02:00
"time"
"github.com/osteele/gojekyll/frontmatter"
2017-08-22 16:47:34 +02:00
"github.com/osteele/gojekyll/utils"
2017-08-14 21:23:00 +02:00
"github.com/osteele/gojekyll/version"
2017-07-05 17:35:20 +02:00
"github.com/osteele/liquid/evaluator"
)
2017-07-07 18:24:00 +02:00
// Page is a document with frontmatter.
type Page interface {
Document
// Render asks a page to compute its content.
2017-07-07 18:24:00 +02:00
// This has the side effect of causing the content to subsequently appear in the drop.
Render() error
2017-08-10 15:06:53 +02:00
SetContent(string)
2017-09-03 18:21:55 +02:00
FrontMatter() FrontMatter
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-09-02 18:10:35 +02:00
IsPost() bool
2017-07-13 18:28:02 +02:00
Categories() []string
Tags() []string
2017-07-07 18:24:00 +02:00
}
2017-07-25 17:24:37 +02:00
// PageEmbed can be embedded to give defaults for the Page interface.
type PageEmbed struct {
Path string
}
2017-09-03 18:21:55 +02:00
// FrontMatter is from the frontmatter package.
type FrontMatter = frontmatter.FrontMatter
2017-09-02 19:53:50 +02:00
// URL is in the pages.Page interface.
func (p *PageEmbed) URL() string { return p.Path }
2017-07-25 17:24:37 +02:00
// OutputExt is in the pages.Page interface.
func (p *PageEmbed) OutputExt() string { return path.Ext(p.Path) }
2017-09-02 19:53:50 +02:00
// Source is in the pages.Page interface.
func (p *PageEmbed) Source() string { return "" }
2017-07-25 17:24:37 +02:00
// Published is in the pages.Page interface.
func (p *PageEmbed) Published() bool { return true }
2017-09-02 19:53:50 +02:00
// IsStatic is in the pages.Page interface.
func (p *PageEmbed) IsStatic() bool { return false } // FIXME means different things to different callers
2017-08-10 15:06:53 +02:00
2017-07-25 17:24:37 +02:00
// Reload is in the pages.Page interface.
func (p *PageEmbed) Reload() error { return nil }
2017-08-10 15:06:53 +02:00
// A page is a concrete implementation of the Page interface.
2017-07-02 19:46:05 +02:00
type page struct {
file
firstLine int
raw []byte
2017-08-10 15:06:53 +02:00
sync.RWMutex
content string
contentError error
contentOnce sync.Once
2017-08-10 16:44:04 +02:00
excerpt interface{} // []byte or string, depending on rendering stage
rendered bool
}
2017-09-02 19:53:50 +02:00
// IsStatic is in the File interface.
func (p *page) IsStatic() bool { return false }
2017-07-09 01:57:41 +02:00
func makePage(filename string, f file) (*page, error) {
2017-07-25 17:08:53 +02:00
raw, lineNo, err := readFrontMatter(&f)
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 01:57:41 +02:00
p := page{
file: f,
firstLine: lineNo,
2017-07-25 17:08:53 +02:00
raw: raw,
2017-07-09 01:57:41 +02:00
}
2017-07-25 17:08:53 +02:00
if err := p.setPermalink(); err != nil {
2017-07-09 01:57:41 +02:00
return nil, err
}
return &p, nil
}
2017-07-25 17:08:53 +02:00
func (p *page) Reload() error {
if err := p.file.Reload(); err != nil {
return err
}
// FIXME use original defaults
raw, lineNo, err := readFrontMatter(&p.file)
if err != nil {
return err
}
p.firstLine = lineNo
p.raw = raw
2017-08-10 16:44:04 +02:00
p.reset()
2017-07-25 17:08:53 +02:00
return nil
}
2017-08-10 16:44:04 +02:00
func (p *page) reset() {
p.contentOnce = sync.Once{}
p.rendered = false
}
2017-07-25 17:08:53 +02:00
func readFrontMatter(f *file) (b []byte, lineNo int, err error) {
b, err = ioutil.ReadFile(f.filename)
if err != nil {
return
}
lineNo = 1
fm, err := frontmatter.Read(&b, &lineNo)
2017-07-25 17:08:53 +02:00
if err != nil {
return
}
f.fm = f.fm.Merged(fm)
2017-07-25 17:08:53 +02:00
return
}
2017-09-03 18:21:55 +02:00
func (p *page) FrontMatter() FrontMatter {
return p.fm
2017-07-13 18:28:02 +02:00
}
// Categories is in the Page interface
func (p *page) Categories() []string {
return p.fm.SortedStringArray("categories")
2017-07-13 18:28:02 +02:00
}
2017-09-02 18:10:35 +02:00
// IsPost is in the Page interface
func (p *page) IsPost() bool {
return p.fm["collection"] == "posts"
}
2017-07-13 18:28:02 +02:00
// Tags is in the Page interface
func (p *page) Tags() []string {
return p.fm.SortedStringArray("tags")
2017-07-13 18:28:02 +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-08-18 16:55:34 +02:00
env := os.Getenv("JEKYLL_ENV")
if env == "" {
env = "development"
}
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-08-14 21:23:00 +02:00
"jekyll": map[string]string{
2017-08-18 16:55:34 +02:00
"environment": env,
"version": fmt.Sprintf("%s (gojekyll)", version.Version)},
}
}
2017-06-15 15:07:06 +02:00
2017-07-03 15:37:14 +02:00
// PostDate is part of the Page interface.
// FIXME move this back to Page interface, or re-work this entirely.
func (f *file) PostDate() time.Time {
switch value := f.fm["date"].(type) {
2017-07-03 15:37:14 +02:00
case time.Time:
return value
case string:
t, err := evaluator.ParseDate(value)
2017-07-03 15:37:14 +02:00
if err == nil {
return t
}
}
return f.modTime
2017-07-03 15:37:14 +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 {
2017-08-22 16:47:34 +02:00
if err := p.Render(); err != nil {
return err
}
p.RLock()
defer p.RUnlock()
2017-08-22 16:47:34 +02:00
cn := p.content
lo, ok := p.fm["layout"].(string)
2017-08-22 16:47:34 +02:00
if ok && lo != "" {
rm := p.site.RendererManager()
b, err := rm.ApplyLayout(lo, []byte(cn), p.TemplateContext())
if err != nil {
return err
}
2017-08-10 15:06:53 +02:00
_, err = w.Write(b)
2017-08-22 16:47:34 +02:00
return err
}
2017-08-22 16:47:34 +02:00
_, err := io.WriteString(w, cn)
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.
func (p *page) Render() error {
2017-08-10 15:06:53 +02:00
p.contentOnce.Do(func() {
2017-08-10 16:44:04 +02:00
cn, ex, err := p.computeContent()
2017-08-10 15:06:53 +02:00
p.Lock()
defer p.Unlock()
2017-08-10 16:44:04 +02:00
p.content = cn
2017-08-22 16:47:34 +02:00
p.contentError = utils.WrapPathError(err, p.filename)
2017-08-10 16:44:04 +02:00
p.excerpt = ex
p.rendered = true
2017-08-10 15:06:53 +02:00
})
return p.contentError
2017-08-10 15:06:53 +02:00
}
2017-08-16 21:50:31 +02:00
func (p *page) SetContent(content string) {
p.Lock()
defer p.Unlock()
p.content = content
p.contentError = nil
2017-08-10 16:44:04 +02:00
}
2017-08-16 21:50:31 +02:00
func (p *page) computeContent() (cn string, ex string, err error) {
2017-08-18 17:07:01 +02:00
pl := p.site.RendererManager()
2017-07-09 22:09:03 +02:00
buf := new(bytes.Buffer)
2017-08-10 16:44:04 +02:00
err = pl.Render(buf, p.raw, p.TemplateContext(), p.filename, p.firstLine)
2017-07-09 22:09:03 +02:00
if err != nil {
2017-08-10 16:44:04 +02:00
return
}
cn = buf.String()
ex = cn
2017-08-16 21:50:31 +02:00
pos := strings.Index(ex, p.site.Config().ExcerptSeparator)
if pos >= 0 {
ex = ex[:pos]
2017-08-10 16:44:04 +02:00
}
return
}
2017-08-16 21:50:31 +02:00
func (p *page) Excerpt() interface{} {
if exc, ok := p.fm["excerpt"]; ok {
2017-08-16 21:50:31 +02:00
return exc
}
p.RLock()
defer p.RUnlock()
if p.rendered {
return p.excerpt
}
return p.extractExcerpt()
}
2017-08-10 16:44:04 +02:00
func (p *page) extractExcerpt() []byte {
raw := p.raw
pos := bytes.Index(raw, []byte(p.site.Config().ExcerptSeparator))
if pos >= 0 {
return raw[:pos]
2017-06-22 18:56:08 +02:00
}
2017-08-10 16:44:04 +02:00
return raw
2017-06-22 18:56:08 +02:00
}