1
0
mirror of https://github.com/danog/gojekyll.git synced 2024-11-30 08:08:59 +01:00

Simplify Context interface by moving more rendering to its internals

This commit is contained in:
Oliver Steele 2017-06-24 11:05:57 -04:00
parent 2c8f8e2bde
commit 1317603482
8 changed files with 187 additions and 155 deletions

View File

@ -42,12 +42,23 @@ func (c *Collection) Pages() []pages.Page {
// TemplateVariable returns an array of page objects, for use as the template variable
// value of the collection.
func (c *Collection) TemplateVariable() []templates.VariableMap {
func (c *Collection) TemplateVariable(ctx pages.Context, includeContent bool) ([]templates.VariableMap, error) {
d := []templates.VariableMap{}
for _, p := range c.Pages() {
d = append(d, p.PageVariables())
v := p.PageVariables()
dp, ok := p.(*pages.DynamicPage)
if includeContent && ok {
c, err := dp.ComputeContent(ctx)
if err != nil {
return nil, err
}
v = templates.MergeVariableMaps(v, templates.VariableMap{
"content": string(c),
})
}
d = append(d, v)
}
return d
return d, nil
}
// PermalinkPattern returns the permalink pattern for this collection.

View File

@ -3,9 +3,9 @@ package collections
import (
"fmt"
"io"
"path"
"testing"
"github.com/osteele/gojekyll/liquid"
"github.com/osteele/gojekyll/templates"
"github.com/stretchr/testify/require"
)
@ -18,15 +18,19 @@ var tests = []struct{ in, out string }{
type MockContext struct{}
func (c MockContext) FindLayout(_ string, _ *templates.VariableMap) (liquid.Template, error) {
func (c MockContext) OutputExt(filename string) string {
return path.Ext(filename)
}
func (c MockContext) Render(_ io.Writer, _ []byte, _ string, _ templates.VariableMap) ([]byte, error) {
return nil, fmt.Errorf("unimplemented")
}
func (c MockContext) IsMarkdown(_ string) bool { return true }
func (c MockContext) IsSassPath(_ string) bool { return true }
func (c MockContext) SassIncludePaths() []string { return []string{} }
func (c MockContext) ApplyLayout(_ string, _ []byte, _ templates.VariableMap) ([]byte, error) {
return nil, fmt.Errorf("unimplemented")
}
func (c MockContext) SiteVariables() templates.VariableMap { return templates.VariableMap{} }
func (c MockContext) TemplateEngine() liquid.Engine { return nil }
func (c MockContext) WriteSass(io.Writer, []byte) error { return nil }
func TestNewCollection(t *testing.T) {
ctx := MockContext{}

View File

@ -1,14 +1,13 @@
package pages
import (
"bytes"
"io"
"io/ioutil"
"path/filepath"
"github.com/osteele/gojekyll/helpers"
"github.com/osteele/gojekyll/liquid"
"github.com/osteele/gojekyll/templates"
"github.com/russross/blackfriday"
)
// DynamicPage is a static page, that includes frontmatter.
@ -45,17 +44,11 @@ func (p *DynamicPage) PageVariables() templates.VariableMap {
ext = filepath.Ext(relpath)
root = helpers.TrimExt(p.relpath)
base = filepath.Base(root)
content = p.processed
)
if content == nil {
content = &[]byte{}
}
data := templates.VariableMap{
"path": relpath,
"url": p.Permalink(),
"content": content,
"path": relpath,
"url": p.Permalink(),
// TODO output
// not documented, but present in both collection and non-collection pages
@ -105,62 +98,33 @@ func (p *DynamicPage) TemplateContext(ctx Context) templates.VariableMap {
// Write applies Liquid and Markdown, as appropriate.
func (p *DynamicPage) Write(ctx Context, w io.Writer) error {
if ctx.IsSassPath(p.relpath) {
return ctx.WriteSass(w, p.raw)
}
if err := p.ComputeContent(ctx); err != nil {
if p.processed != nil {
_, err := w.Write(*p.processed)
return err
}
_, err := w.Write(*p.processed)
b, err := ctx.Render(w, p.raw, p.filename, p.TemplateContext(ctx))
if err != nil {
return err
}
layout := p.frontMatter.String("layout", "")
if layout != "" {
b, err = ctx.ApplyLayout(layout, b, p.TemplateContext(ctx))
if err != nil {
return err
}
}
_, err = w.Write(b)
return err
}
// ComputeContent computes the page content.
func (p *DynamicPage) ComputeContent(ctx Context) error {
if p.processed != nil {
return nil
}
b, err := ctx.TemplateEngine().ParseAndRender(p.raw, p.TemplateContext(ctx))
if err != nil {
switch err := err.(type) {
case *liquid.RenderError:
if err.Filename == "" {
err.Filename = p.filename
}
return err
default:
return helpers.PathError(err, "Liquid Error", p.filename)
}
}
if ctx.IsMarkdown(p.filename) {
b = blackfriday.MarkdownCommon(b)
}
b, err = p.applyLayout(ctx, p.frontMatter, b)
if err != nil {
return err
}
p.processed = &b
return err
}
func (p *DynamicPage) applyLayout(ctx Context, frontMatter templates.VariableMap, body []byte) ([]byte, error) {
for {
name := frontMatter.String("layout", "")
if name == "" {
return body, nil
}
template, err := ctx.FindLayout(name, &frontMatter)
if err != nil {
return nil, err
}
vars := templates.MergeVariableMaps(p.TemplateContext(ctx), templates.VariableMap{
"content": string(body),
"layout": frontMatter,
})
body, err = template.Render(vars)
if err != nil {
func (p *DynamicPage) ComputeContent(ctx Context) ([]byte, error) {
if p.processed == nil {
w := new(bytes.Buffer)
if err := p.Write(ctx, w); err != nil {
return nil, err
}
*p.processed = w.Bytes()
}
return *p.processed, nil
}

View File

@ -12,7 +12,6 @@ import (
"time"
"github.com/osteele/gojekyll/helpers"
"github.com/osteele/gojekyll/liquid"
"github.com/osteele/gojekyll/templates"
)
@ -42,13 +41,10 @@ type Page interface {
// Context provides context information to a Page.
type Context interface {
FindLayout(relname string, frontMatter *templates.VariableMap) (liquid.Template, error)
IsMarkdown(filename string) bool
IsSassPath(filename string) bool
SassIncludePaths() []string
ApplyLayout(string, []byte, templates.VariableMap) ([]byte, error)
OutputExt(pathname string) string
Render(io.Writer, []byte, string, templates.VariableMap) ([]byte, error)
SiteVariables() templates.VariableMap
TemplateEngine() liquid.Engine
WriteSass(io.Writer, []byte) error
}
// Container is the Page container
@ -94,15 +90,7 @@ func NewPageFromFile(ctx Context, c Container, filename string, relpath string,
frontMatter: defaults,
fileModTime: info.ModTime(),
relpath: relpath,
}
switch {
case ctx.IsMarkdown(relpath):
// fields.isMarkdown = true
fields.outputExt = ".html"
case ctx.IsSassPath(relpath):
fields.outputExt = ".css"
default:
fields.outputExt = filepath.Ext(relpath)
outputExt: ctx.OutputExt(relpath),
}
var p Page
if string(magic) == "---\n" {

View File

@ -16,11 +16,9 @@ var tests = []struct{ in, out string }{
func TestTagInjector(t *testing.T) {
for _, test := range tests {
out := new(bytes.Buffer)
// bytes.NewBufferString(c.in)
w := TagInjector{out, []byte(":insertion:")}
_, err := w.Write([]byte(test.in))
require.NoError(t, err)
// buf.String() // returns a string of what was written to it
require.Equal(t, test.out, out.String())
}
}

126
sites/render.go Normal file
View File

@ -0,0 +1,126 @@
package sites
import (
"io"
"io/ioutil"
"path/filepath"
"github.com/osteele/gojekyll/helpers"
"github.com/osteele/gojekyll/liquid"
"github.com/osteele/gojekyll/templates"
"github.com/russross/blackfriday"
)
// OutputExt returns the output extension.
func (s *Site) OutputExt(pathname string) string {
switch {
case s.IsMarkdown(pathname):
return ".html"
case s.IsSassPath(pathname):
return ".css"
default:
return filepath.Ext(pathname)
}
}
// Render returns nil iff it wrote to the writer
func (s *Site) Render(w io.Writer, b []byte, filename string, e templates.VariableMap) ([]byte, error) {
if s.IsSassPath(filename) {
return nil, s.WriteSass(w, b)
}
b, err := s.renderTemplate(b, e, filename)
if err != nil {
return nil, err
}
if s.IsMarkdown(filename) {
b = blackfriday.MarkdownCommon(b)
}
return b, nil
}
func (s *Site) renderTemplate(b []byte, e templates.VariableMap, filename string) ([]byte, error) {
b, err := s.liquidEngine.ParseAndRender(b, e)
if err != nil {
switch err := err.(type) {
case *liquid.RenderError:
if err.Filename == "" {
err.Filename = filename
}
return nil, err
default:
return nil, helpers.PathError(err, "Liquid Error", filename)
}
}
return b, err
}
// ApplyLayout applies the named layout to bytes.
func (s *Site) ApplyLayout(name string, b []byte, e templates.VariableMap) ([]byte, error) {
for name != "" {
var lfm templates.VariableMap
t, err := s.FindLayout(name, &lfm)
if err != nil {
return nil, err
}
le := templates.MergeVariableMaps(e, templates.VariableMap{
"content": string(b),
"layout": lfm,
})
b, err = t.Render(le)
if err != nil {
return nil, err
}
name = lfm.String("layout", "")
}
return b, nil
}
func (s *Site) makeLocalLiquidEngine() liquid.Engine {
engine := liquid.NewLocalWrapperEngine()
engine.LinkTagHandler(s.RelPathURL)
includeHandler := func(name string, w io.Writer, scope map[string]interface{}) error {
filename := filepath.Join(s.Source, s.config.IncludesDir, name)
template, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
text, err := engine.ParseAndRender(template, scope)
if err != nil {
return err
}
_, err = w.Write(text)
return err
}
engine.IncludeHandler(includeHandler)
return engine
}
func (s *Site) makeLiquidClient() (engine liquid.RemoteEngine, err error) {
engine, err = liquid.NewRPCClientEngine(liquid.DefaultServer)
if err != nil {
return
}
urls := map[string]string{}
for _, p := range s.Paths {
urls[p.SiteRelPath()] = p.Permalink()
}
err = engine.FileURLMap(urls)
if err != nil {
return
}
err = engine.IncludeDirs([]string{filepath.Join(s.Source, s.config.IncludesDir)})
return
}
func (s *Site) makeLiquidEngine() (liquid.Engine, error) {
if s.UseRemoteLiquidEngine {
return s.makeLiquidClient()
}
return s.makeLocalLiquidEngine(), nil
}
// TemplateEngine creates a liquid engine configured to with include paths and link tag resolution
// for this site.
func (s *Site) TemplateEngine() liquid.Engine {
return s.liquidEngine
}

View File

@ -1,7 +1,6 @@
package sites
import (
"io"
"io/ioutil"
"os"
"path/filepath"
@ -127,53 +126,3 @@ func (s *Site) Exclude(path string) bool {
func (s *Site) LayoutsDir() string {
return filepath.Join(s.Source, s.config.LayoutsDir)
}
func (s *Site) makeLocalLiquidEngine() liquid.Engine {
engine := liquid.NewLocalWrapperEngine()
engine.LinkTagHandler(s.RelPathURL)
includeHandler := func(name string, w io.Writer, scope map[string]interface{}) error {
filename := filepath.Join(s.Source, s.config.IncludesDir, name)
template, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
text, err := engine.ParseAndRender(template, scope)
if err != nil {
return err
}
_, err = w.Write(text)
return err
}
engine.IncludeHandler(includeHandler)
return engine
}
func (s *Site) makeLiquidClient() (engine liquid.RemoteEngine, err error) {
engine, err = liquid.NewRPCClientEngine(liquid.DefaultServer)
if err != nil {
return
}
urls := map[string]string{}
for _, p := range s.Paths {
urls[p.SiteRelPath()] = p.Permalink()
}
err = engine.FileURLMap(urls)
if err != nil {
return
}
err = engine.IncludeDirs([]string{filepath.Join(s.Source, s.config.IncludesDir)})
return
}
func (s *Site) makeLiquidEngine() (liquid.Engine, error) {
if s.UseRemoteLiquidEngine {
return s.makeLiquidClient()
}
return s.makeLocalLiquidEngine(), nil
}
// TemplateEngine create a liquid engine configured to with include paths and link tag resolution
// for this site.
func (s *Site) TemplateEngine() liquid.Engine {
return s.liquidEngine
}

View File

@ -7,7 +7,6 @@ import (
"time"
"github.com/osteele/gojekyll/helpers"
"github.com/osteele/gojekyll/pages"
"github.com/osteele/gojekyll/templates"
)
@ -27,31 +26,24 @@ func (s *Site) initSiteVariables() error {
"time": time.Now(),
// TODO pages, posts, related_posts, static_files, html_pages, html_files, collections, data, documents, categories.CATEGORY, tags.TAG
})
s.updateCollectionVariables()
return nil
return s.updateCollectionVariables(false)
}
// SetPageContentTemplateValues sets the site[collection][i].content
// template variables
func (s *Site) SetPageContentTemplateValues() error {
for _, c := range s.Collections {
for _, p := range c.Pages() {
switch p := p.(type) {
case *pages.DynamicPage:
if err := p.ComputeContent(s); err != nil {
return err
}
}
}
}
s.updateCollectionVariables()
return nil
return s.updateCollectionVariables(true)
}
func (s *Site) updateCollectionVariables() {
func (s *Site) updateCollectionVariables(includeContent bool) error {
for _, c := range s.Collections {
s.Variables[c.Name] = c.TemplateVariable()
v, err := c.TemplateVariable(s, includeContent)
if err != nil {
return err
}
s.Variables[c.Name] = v
}
return nil
}
func (s *Site) readDataFiles() (templates.VariableMap, error) {