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-06-10 21:38:09 +02:00
|
|
|
"path/filepath"
|
2017-07-03 15:37:14 +02:00
|
|
|
"time"
|
2017-06-10 21:38:09 +02:00
|
|
|
|
2017-06-17 02:11:52 +02:00
|
|
|
"github.com/osteele/gojekyll/helpers"
|
2017-06-22 16:42:57 +02:00
|
|
|
"github.com/osteele/gojekyll/templates"
|
2017-07-03 15:37:14 +02:00
|
|
|
"github.com/osteele/liquid/generics"
|
2017-06-10 21:38:09 +02:00
|
|
|
)
|
|
|
|
|
2017-07-02 19:54:43 +02:00
|
|
|
// Page is a post or collection page.
|
2017-07-02 19:46:05 +02:00
|
|
|
type Page interface {
|
|
|
|
Document
|
|
|
|
Content(rc RenderingContext) ([]byte, error)
|
2017-07-03 15:37:14 +02:00
|
|
|
PostDate() time.Time
|
2017-07-02 19:46:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type page struct {
|
2017-06-29 16:13:25 +02:00
|
|
|
file
|
2017-07-01 15:35:54 +02:00
|
|
|
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-02 19:46:05 +02:00
|
|
|
func newPage(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-06-29 16:13:25 +02:00
|
|
|
|
|
|
|
frontMatter, err := templates.ReadFrontMatter(&b)
|
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-02 19:46:05 +02:00
|
|
|
return &page{
|
2017-06-29 16:13:25 +02:00
|
|
|
file: f,
|
|
|
|
raw: b,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2017-07-03 15:37:14 +02:00
|
|
|
// ToLiquid is in the liquid.Drop interface.
|
|
|
|
func (p *page) ToLiquid() interface{} {
|
2017-06-29 16:13:25 +02:00
|
|
|
var (
|
|
|
|
relpath = p.relpath
|
|
|
|
ext = filepath.Ext(relpath)
|
|
|
|
root = helpers.TrimExt(p.relpath)
|
|
|
|
base = filepath.Base(root)
|
|
|
|
)
|
|
|
|
|
2017-07-01 05:56:29 +02:00
|
|
|
data := map[string]interface{}{
|
2017-06-29 16:13:25 +02:00
|
|
|
"path": relpath,
|
|
|
|
"url": p.Permalink(),
|
|
|
|
// TODO output
|
2017-06-15 15:07:06 +02:00
|
|
|
|
2017-06-29 16:13:25 +02:00
|
|
|
// not documented, but present in both collection and non-collection pages
|
|
|
|
"permalink": p.Permalink(),
|
|
|
|
|
|
|
|
// TODO only in non-collection pages:
|
|
|
|
// TODO dir
|
|
|
|
// TODO name
|
|
|
|
// TODO next previous
|
|
|
|
|
|
|
|
// TODO Documented as present in all pages, but de facto only defined for collection pages
|
|
|
|
"id": base,
|
|
|
|
"title": base, // TODO capitalize
|
|
|
|
// TODO excerpt category? categories tags
|
|
|
|
// TODO slug
|
2017-07-03 15:37:14 +02:00
|
|
|
"categories": p.Categories(),
|
|
|
|
"tags": p.Tags(),
|
2017-06-29 16:13:25 +02:00
|
|
|
|
|
|
|
// TODO Only present in collection pages https://jekyllrb.com/docs/collections/#documents
|
|
|
|
"relative_path": p.Path(),
|
|
|
|
// TODO collection(name)
|
|
|
|
|
|
|
|
// TODO undocumented; only present in collection pages:
|
|
|
|
"ext": ext,
|
2017-06-22 15:36:39 +02:00
|
|
|
}
|
2017-06-29 16:13:25 +02:00
|
|
|
for k, v := range p.frontMatter {
|
|
|
|
switch k {
|
|
|
|
// doc implies these aren't present, but they appear to be present in a collection page:
|
|
|
|
// case "layout", "published":
|
|
|
|
case "permalink":
|
|
|
|
// omit this, in order to use the value above
|
|
|
|
default:
|
|
|
|
data[k] = v
|
2017-06-16 22:44:09 +02:00
|
|
|
}
|
2017-06-15 15:31:04 +02:00
|
|
|
}
|
2017-07-03 15:37:14 +02:00
|
|
|
if p.content != nil {
|
|
|
|
data["content"] = string(*p.content)
|
|
|
|
// TODO excerpt
|
|
|
|
}
|
2017-06-29 16:13:25 +02:00
|
|
|
return data
|
2017-06-15 15:07:06 +02:00
|
|
|
}
|
|
|
|
|
2017-07-03 15:37:14 +02:00
|
|
|
// MarshalYAML is part of the yaml.Marshaler interface
|
|
|
|
// The variables subcommand uses this.
|
|
|
|
func (p *page) MarshalYAML() (interface{}, error) {
|
|
|
|
return p.ToLiquid(), nil
|
|
|
|
}
|
|
|
|
|
2017-06-29 16:13:25 +02:00
|
|
|
// TemplateContext returns the local variables for template evaluation
|
2017-07-02 19:46:05 +02:00
|
|
|
func (p *page) TemplateContext(rc RenderingContext) 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-06-29 16:13:25 +02:00
|
|
|
"site": rc.SiteVariables(),
|
|
|
|
}
|
|
|
|
}
|
2017-06-15 15:07:06 +02:00
|
|
|
|
2017-07-03 15:37:14 +02:00
|
|
|
// // Categories is part of the Page interface.
|
|
|
|
// func (p *page) Categories() []string {
|
|
|
|
// return []string{}
|
|
|
|
// }
|
|
|
|
|
|
|
|
// Tags is part of the Page interface.
|
|
|
|
func (p *page) Tags() []string {
|
|
|
|
return []string{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// PostDate is part of the Page interface.
|
|
|
|
func (p *page) PostDate() time.Time {
|
|
|
|
switch value := p.frontMatter["date"].(type) {
|
|
|
|
case time.Time:
|
|
|
|
return value
|
|
|
|
case string:
|
|
|
|
t, err := generics.ParseTime(value)
|
|
|
|
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-02 19:46:05 +02:00
|
|
|
func (p *page) Write(rc RenderingContext, w io.Writer) error {
|
2017-06-29 16:13:25 +02:00
|
|
|
rp := rc.RenderingPipeline()
|
|
|
|
b, err := rp.Render(w, p.raw, p.filename, p.TemplateContext(rc))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-07-01 05:56:29 +02:00
|
|
|
layout := templates.VariableMap(p.frontMatter).String("layout", "")
|
2017-06-29 16:13:25 +02:00
|
|
|
if layout != "" {
|
|
|
|
b, err = rp.ApplyLayout(layout, b, p.TemplateContext(rc))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_, err = w.Write(b)
|
|
|
|
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-02 19:46:05 +02:00
|
|
|
func (p *page) Content(rc RenderingContext) ([]byte, error) {
|
2017-07-01 15:35:54 +02:00
|
|
|
if p.content == nil {
|
|
|
|
// TODO DRY w/ Page.Write
|
|
|
|
rp := rc.RenderingPipeline()
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
b, err := rp.Render(buf, p.raw, p.filename, p.TemplateContext(rc))
|
|
|
|
if err != nil {
|
2017-06-29 16:13:25 +02:00
|
|
|
return nil, err
|
2017-06-22 18:56:08 +02:00
|
|
|
}
|
2017-07-01 15:35:54 +02:00
|
|
|
p.content = &b
|
2017-06-22 18:56:08 +02:00
|
|
|
}
|
2017-07-01 15:35:54 +02:00
|
|
|
return *p.content, nil
|
2017-06-22 18:56:08 +02:00
|
|
|
}
|