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

Create separate Pipeline struct

This commit is contained in:
Oliver Steele 2017-06-24 13:30:01 -04:00
parent 61f92ecd96
commit 530cc04ab4
13 changed files with 247 additions and 197 deletions

View File

@ -129,6 +129,10 @@ func renderCommand(site *sites.Site) error {
printPathSetting("Render:", filepath.Join(site.Source, p.SiteRelPath()))
printSetting("URL:", p.Permalink())
printSetting("Content:", "")
err = site.InitializeRenderingPipeline()
if err != nil {
return err
}
return p.Write(site, os.Stdout)
}

View File

@ -19,3 +19,10 @@ func TestUnmarshal(t *testing.T) {
require.Equal(t, "x", c.Source)
require.Equal(t, "./_site", c.Destination)
}
func TestIsMarkdown(t *testing.T) {
c := Default()
require.True(t, c.IsMarkdown("name.md"))
require.True(t, c.IsMarkdown("name.markdown"))
require.False(t, c.IsMarkdown("name.html"))
}

36
config/pathnames.go Normal file
View File

@ -0,0 +1,36 @@
package config
import (
"path/filepath"
"strings"
"github.com/osteele/gojekyll/helpers"
)
// IsMarkdown returns a boolean indicating whether the file is a Markdown file, according to the current project.
func (c *Config) IsMarkdown(name string) bool {
ext := filepath.Ext(name)
return c.markdownExtensions()[strings.TrimLeft(ext, ".")]
}
// IsSassPath returns a boolean indicating whether the file is a Sass (".sass" or ".scss") file.
func (c *Config) IsSassPath(name string) bool {
return strings.HasSuffix(name, ".sass") || strings.HasSuffix(name, ".scss")
}
// MarkdownExtensions returns a set of markdown extensions, without the initial dots.
func (c *Config) markdownExtensions() map[string]bool {
extns := strings.SplitN(c.MarkdownExt, `,`, -1)
return helpers.StringArrayToMap(extns)
}
func (c *Config) OutputExt(pathname string) string {
switch {
case c.IsMarkdown(pathname):
return ".html"
case c.IsSassPath(pathname):
return ".css"
default:
return filepath.Ext(pathname)
}
}

View File

@ -43,7 +43,7 @@ func (s *Server) watchFiles() error {
log.Println("error:", err)
continue
}
url, found := site.RelPathURL(relpath)
url, found := site.RelativeFilenameToURL(relpath)
if !found {
// TODO don't warn re config and excluded files
log.Println("error:", filename, "does not match a site URL")

View File

@ -49,12 +49,10 @@ func (s *Site) Clean(options BuildOptions) error {
// It attends to the global options.dry_run.
func (s *Site) Build(options BuildOptions) (int, error) {
count := 0
s.InitializeRenderingPipeline()
if err := s.Clean(options); err != nil {
return count, err
}
if err := s.CopySassFileIncludes(); err != nil {
return count, err
}
if err := s.SetPageContentTemplateValues(); err != nil {
return count, err
}

View File

@ -13,9 +13,9 @@ import (
)
// FindLayout returns a template for the named layout.
func (s *Site) FindLayout(base string, fm *templates.VariableMap) (t liquid.Template, err error) {
func (p *Pipeline) FindLayout(base string, fm *templates.VariableMap) (t liquid.Template, err error) {
exts := []string{"", ".html"}
for _, ext := range strings.SplitN(s.config.MarkdownExt, `,`, -1) {
for _, ext := range strings.SplitN(p.config.MarkdownExt, `,`, -1) {
exts = append(exts, "."+ext)
}
var (
@ -25,7 +25,7 @@ func (s *Site) FindLayout(base string, fm *templates.VariableMap) (t liquid.Temp
)
for _, ext := range exts {
// TODO respect layout config
name = filepath.Join(s.LayoutsDir(), base+ext)
name = filepath.Join(p.LayoutsDir(), base+ext)
content, err = ioutil.ReadFile(name)
if err == nil {
found = true
@ -42,5 +42,10 @@ func (s *Site) FindLayout(base string, fm *templates.VariableMap) (t liquid.Temp
if err != nil {
return
}
return s.TemplateEngine().Parse(content)
return p.liquidEngine.Parse(content)
}
// LayoutsDir returns the path to the layouts directory.
func (p *Pipeline) LayoutsDir() string {
return filepath.Join(p.sourceDir, p.config.LayoutsDir)
}

View File

@ -19,7 +19,6 @@ func (s *Site) Load() (err error) {
if err != nil {
return
}
s.liquidEngine, err = s.makeLiquidEngine()
return
}
@ -32,7 +31,7 @@ func (s *Site) Reload() error {
}
copy.Destination = s.Destination
*s = *copy
s.sassTempDir = ""
s.pipeline = nil
return s.Load()
}

View File

@ -1,20 +0,0 @@
package sites
import (
"path/filepath"
"strings"
"github.com/osteele/gojekyll/helpers"
)
// IsMarkdown returns a boolean indicating whether the file is a Markdown file, according to the current project.
func (s *Site) IsMarkdown(name string) bool {
ext := filepath.Ext(name)
return s.MarkdownExtensions()[strings.TrimLeft(ext, ".")]
}
// MarkdownExtensions returns a set of markdown extension, without the final dots.
func (s *Site) MarkdownExtensions() map[string]bool {
extns := strings.SplitN(s.config.MarkdownExt, `,`, -1)
return helpers.StringArrayToMap(extns)
}

154
sites/pipeline.go Normal file
View File

@ -0,0 +1,154 @@
package sites
import (
"io"
"io/ioutil"
"path/filepath"
"github.com/osteele/gojekyll/config"
"github.com/osteele/gojekyll/helpers"
"github.com/osteele/gojekyll/liquid"
"github.com/osteele/gojekyll/pages"
"github.com/osteele/gojekyll/templates"
"github.com/russross/blackfriday"
)
// Pipeline applies a rendering transformation to a file.
type Pipeline struct {
config config.Config
liquidEngine liquid.Engine
pageSupplier PageSupplier
sassTempDir string
sourceDir string
}
// PipelineOptions configures a pipeline.
type PipelineOptions struct {
UseRemoteLiquidEngine bool
}
// PageSupplier tells a pipeline how to resolve link tags.
type PageSupplier interface {
Pages() []pages.Page
RelativeFilenameToURL(string) (string, bool)
}
// NewPipeline makes a rendering pipeline.
func NewPipeline(sourceDir string, c config.Config, pageSupplier PageSupplier, o PipelineOptions) (*Pipeline, error) {
p := Pipeline{config: c, pageSupplier: pageSupplier, sourceDir: sourceDir}
engine, err := p.makeLiquidEngine(o)
if err != nil {
return nil, err
}
p.liquidEngine = engine
if err := p.CopySassFileIncludes(); err != nil {
return nil, err
}
return &p, nil
}
// OutputExt returns the output extension.
func (p *Pipeline) OutputExt(pathname string) string {
return p.config.OutputExt(pathname)
}
// OutputExt returns the output extension.
func (s *Site) OutputExt(pathname string) string {
return s.config.OutputExt(pathname)
}
// Render returns nil iff it wrote to the writer
func (p *Pipeline) Render(w io.Writer, b []byte, filename string, e templates.VariableMap) ([]byte, error) {
if p.config.IsSassPath(filename) {
return nil, p.WriteSass(w, b)
}
b, err := p.renderTemplate(b, e, filename)
if err != nil {
return nil, err
}
if p.config.IsMarkdown(filename) {
b = blackfriday.MarkdownCommon(b)
}
return b, nil
}
func (p *Pipeline) renderTemplate(b []byte, e templates.VariableMap, filename string) ([]byte, error) {
b, err := p.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 (p *Pipeline) ApplyLayout(name string, b []byte, e templates.VariableMap) ([]byte, error) {
for name != "" {
var lfm templates.VariableMap
t, err := p.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 (p *Pipeline) makeLocalLiquidEngine() liquid.Engine {
engine := liquid.NewLocalWrapperEngine()
engine.LinkTagHandler(p.pageSupplier.RelativeFilenameToURL)
includeHandler := func(name string, w io.Writer, scope map[string]interface{}) error {
filename := filepath.Join(p.sourceDir, p.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 (p *Pipeline) makeLiquidClient() (engine liquid.RemoteEngine, err error) {
engine, err = liquid.NewRPCClientEngine(liquid.DefaultServer)
if err != nil {
return
}
urls := map[string]string{}
for _, page := range p.pageSupplier.Pages() {
urls[page.SiteRelPath()] = page.Permalink()
}
err = engine.FileURLMap(urls)
if err != nil {
return
}
err = engine.IncludeDirs([]string{filepath.Join(p.sourceDir, p.config.IncludesDir)})
return
}
func (p *Pipeline) makeLiquidEngine(o PipelineOptions) (liquid.Engine, error) {
if o.UseRemoteLiquidEngine {
return p.makeLiquidClient()
}
return p.makeLocalLiquidEngine(), nil
}

View File

@ -1,137 +0,0 @@
package sites
import (
"io"
"io/ioutil"
"path/filepath"
"github.com/osteele/gojekyll/helpers"
"github.com/osteele/gojekyll/liquid"
"github.com/osteele/gojekyll/pages"
"github.com/osteele/gojekyll/templates"
"github.com/russross/blackfriday"
)
// RenderingContext returns the page rendering context for a container.
func (s *Site) RenderingContext() pages.RenderingContext {
return s
}
// RenderingPipeline returns the page's rendering context.
func (s *Site) RenderingPipeline() pages.RenderingPipeline {
return s
}
// 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

@ -14,26 +14,21 @@ import (
libsass "github.com/wellington/go-libsass"
)
// IsSassPath returns a boolean indicating whether the file is a Sass (".sass" or ".scss") file.
func (s *Site) IsSassPath(name string) bool {
return strings.HasSuffix(name, ".sass") || strings.HasSuffix(name, ".scss")
}
// CopySassFileIncludes copies sass partials into a temporary directory,
// removing initial underscores.
// TODO delete the temp directory when done
func (s *Site) CopySassFileIncludes() error {
func (p *Pipeline) CopySassFileIncludes() error {
// TODO use libsass.ImportsOption instead?
if s.sassTempDir == "" {
if p.sassTempDir == "" {
dir, err := ioutil.TempDir(os.TempDir(), "_sass")
if err != nil {
return err
}
s.sassTempDir = dir
p.sassTempDir = dir
}
src := filepath.Join(s.Source, "_sass")
dst := s.sassTempDir
src := filepath.Join(p.sourceDir, "_sass")
dst := p.sassTempDir
err := filepath.Walk(src, func(from string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return err
@ -52,17 +47,17 @@ func (s *Site) CopySassFileIncludes() error {
}
// SassIncludePaths returns an array of sass include directories.
func (s *Site) SassIncludePaths() []string {
return []string{s.sassTempDir}
func (p *Pipeline) SassIncludePaths() []string {
return []string{p.sassTempDir}
}
// WriteSass converts a SASS file and writes it to w.
func (s *Site) WriteSass(w io.Writer, b []byte) error {
func (p *Pipeline) WriteSass(w io.Writer, b []byte) error {
comp, err := libsass.New(w, bytes.NewBuffer(b))
if err != nil {
return err
}
err = comp.Option(libsass.IncludePaths(s.SassIncludePaths()))
err = comp.Option(libsass.IncludePaths(p.SassIncludePaths()))
if err != nil {
log.Fatal(err)
}

View File

@ -1,6 +1,7 @@
package sites
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
@ -9,7 +10,6 @@ import (
"github.com/osteele/gojekyll/collections"
"github.com/osteele/gojekyll/config"
"github.com/osteele/gojekyll/helpers"
"github.com/osteele/gojekyll/liquid"
"github.com/osteele/gojekyll/pages"
"github.com/osteele/gojekyll/templates"
)
@ -25,10 +25,11 @@ type Site struct {
Variables templates.VariableMap
Paths map[string]pages.Page // URL path -> Page, only for output pages
config config.Config
liquidEngine liquid.Engine
pages []pages.Page // all pages, output or not
sassTempDir string
config config.Config
pipeline *Pipeline
// liquidEngine liquid.Engine
// sassTempDir string
pages []pages.Page // all pages, output or not
}
// Pages returns a slice of pages.
@ -80,8 +81,8 @@ func (s *Site) RelPathPage(relpath string) (pages.Page, bool) {
return nil, false
}
// RelPathURL returns a page's relative URL, give a file path relative to the site source directory.
func (s *Site) RelPathURL(relpath string) (string, bool) {
// RelativePathToURL returns a page's relative URL, give a file path relative to the site source directory.
func (s *Site) RelativeFilenameToURL(relpath string) (string, bool) {
var url string
p, found := s.RelPathPage(relpath)
if found {
@ -90,6 +91,20 @@ func (s *Site) RelPathURL(relpath string) (string, bool) {
return url, found
}
// RenderingPipeline returns the rendering pipeline.
func (s *Site) RenderingPipeline() pages.RenderingPipeline {
if s.pipeline == nil {
panic(fmt.Errorf("uninitialized rendering pipeline"))
}
return s.pipeline
}
func (s *Site) InitializeRenderingPipeline() (err error) {
o := PipelineOptions{UseRemoteLiquidEngine: s.UseRemoteLiquidEngine}
s.pipeline, err = NewPipeline(s.Source, s.config, s, o)
return
}
// URLPage returns the page that will be served at URL
func (s *Site) URLPage(urlpath string) (p pages.Page, found bool) {
p, found = s.Paths[urlpath]
@ -121,8 +136,3 @@ func (s *Site) Exclude(path string) bool {
return false
}
}
// LayoutsDir returns the path to the layouts directory.
func (s *Site) LayoutsDir() string {
return filepath.Join(s.Source, s.config.LayoutsDir)
}

View File

@ -7,8 +7,7 @@ import (
)
func TestIsMarkdown(t *testing.T) {
site := NewSite()
require.True(t, site.IsMarkdown("name.md"))
require.True(t, site.IsMarkdown("name.markdown"))
require.False(t, site.IsMarkdown("name.html"))
s := NewSite()
require.Equal(t, "", s.PathPrefix())
require.False(t, s.KeepFile("random"))
}