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

Use drops for site

This commit is contained in:
Oliver Steele 2017-07-03 10:39:55 -04:00
parent 257862fb3d
commit fbd08abe3f
14 changed files with 174 additions and 129 deletions

View File

@ -109,7 +109,7 @@ func pageFromPathOrRoute(s *sites.Site, path string) (pages.Document, error) {
func varsCommand(site *sites.Site) error { func varsCommand(site *sites.Site) error {
printSetting("Variables:", "") printSetting("Variables:", "")
siteData := site.SiteVariables() siteData := site.ToLiquid().(map[string]interface{})
// The YAML representation including collections is impractically large for debugging. // The YAML representation including collections is impractically large for debugging.
// Neuter it. This destroys it as Liquid data, but that's okay in this context. // Neuter it. This destroys it as Liquid data, but that's okay in this context.
// for _, c := range site.Collections { // for _, c := range site.Collections {

View File

@ -4,7 +4,6 @@ import (
"path/filepath" "path/filepath"
"github.com/osteele/gojekyll/config" "github.com/osteele/gojekyll/config"
"github.com/osteele/gojekyll/constants"
"github.com/osteele/gojekyll/pages" "github.com/osteele/gojekyll/pages"
"github.com/osteele/gojekyll/templates" "github.com/osteele/gojekyll/templates"
) )
@ -88,11 +87,8 @@ func (c *Collection) ToLiquid() interface{} {
}) })
} }
// PermalinkPattern returns the permalink pattern for this collection. // PermalinkPattern returns the default permalink pattern for this collection.
func (c *Collection) PermalinkPattern() string { func (c *Collection) PermalinkPattern() string {
defaultPattern := constants.DefaultCollectionPermalinkPattern defaultPattern := c.strategy().defaultPermalinkPattern()
if c.IsPostsCollection() {
defaultPattern = constants.DefaultPostsCollectionPermalinkPattern
}
return templates.VariableMap(c.Metadata).String("permalink", defaultPattern) return templates.VariableMap(c.Metadata).String("permalink", defaultPattern)
} }

View File

@ -1,8 +1,9 @@
package collections package collections
import ( import (
"path/filepath"
"time" "time"
"github.com/osteele/gojekyll/helpers"
) )
// A collectionStrategy encapsulates behavior differences between the _post // A collectionStrategy encapsulates behavior differences between the _post
@ -10,6 +11,7 @@ import (
type collectionStrategy interface { type collectionStrategy interface {
addDate(filename string, fm map[string]interface{}) addDate(filename string, fm map[string]interface{})
collectible(filename string) bool collectible(filename string) bool
defaultPermalinkPattern() string
future(filename string) bool future(filename string) bool
} }
@ -29,25 +31,31 @@ func (s defaultStrategy) future(filename string) bool { return fa
type postsStrategy struct{} type postsStrategy struct{}
func (s postsStrategy) addDate(filename string, fm map[string]interface{}) { func (s postsStrategy) addDate(filename string, fm map[string]interface{}) {
if t, found := DateFromFilename(filename); found { if t, found := helpers.FilenameDate(filename); found {
fm["date"] = t fm["date"] = t
} }
} }
func (s postsStrategy) collectible(filename string) bool { func (s postsStrategy) collectible(filename string) bool {
_, ok := DateFromFilename(filename) _, ok := helpers.FilenameDate(filename)
return ok return ok
} }
func (s postsStrategy) future(filename string) bool { func (s postsStrategy) future(filename string) bool {
t, ok := DateFromFilename(filename) t, ok := helpers.FilenameDate(filename)
return ok && t.After(time.Now()) return ok && t.After(time.Now())
} }
// DateFromFilename returns the date for a filename that uses Jekyll post convention. // DefaultCollectionPermalinkPattern is the default permalink pattern for pages in the posts collection
// It also returns a bool indicating whether a date was found. const DefaultCollectionPermalinkPattern = "/:collection/:path:output_ext"
func DateFromFilename(s string) (time.Time, bool) {
layout := "2006-01-02-" // DefaultPostsCollectionPermalinkPattern is the default collection permalink pattern
t, err := time.Parse(layout, filepath.Base(s + layout)[:len(layout)]) const DefaultPostsCollectionPermalinkPattern = "/:categories/:year/:month/:day/:title.html"
return t, err == nil
func (s defaultStrategy) defaultPermalinkPattern() string {
return DefaultCollectionPermalinkPattern
}
func (s postsStrategy) defaultPermalinkPattern() string {
return DefaultPostsCollectionPermalinkPattern
} }

View File

@ -1,10 +0,0 @@
package constants
// DefaultPermalinkPattern is the default permalink pattern for pages that aren't in a collection
const DefaultPermalinkPattern = "/:path:output_ext"
// DefaultCollectionPermalinkPattern is the default permalink pattern for pages in the posts collection
const DefaultCollectionPermalinkPattern = "/:collection/:path:output_ext"
// DefaultPostsCollectionPermalinkPattern is the default collection permalink pattern
const DefaultPostsCollectionPermalinkPattern = "/:categories/:year/:month/:day/:title.html"

View File

@ -4,8 +4,17 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
) )
// FilenameDate returns the date for a filename that uses Jekyll post convention.
// It also returns a bool indicating whether a date was found.
func FilenameDate(s string) (time.Time, bool) {
layout := "2006-01-02-"
t, err := time.Parse(layout, filepath.Base(s + layout)[:len(layout)])
return t, err == nil
}
// MustRel is like filepath.Rel, but panics if the path cannot be relativized. // MustRel is like filepath.Rel, but panics if the path cannot be relativized.
func MustRel(basepath, targpath string) string { func MustRel(basepath, targpath string) string {
relpath, err := filepath.Rel(basepath, targpath) relpath, err := filepath.Rel(basepath, targpath)

View File

@ -2,10 +2,28 @@ package helpers
import ( import (
"testing" "testing"
"time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func timeMustParse(s string) time.Time {
t, err := time.Parse(time.RFC3339, s)
if err != nil {
panic(err)
}
return t
}
func TestFilenameDate(t *testing.T) {
d, found := FilenameDate("2017-07-02-post.html")
require.True(t, found)
require.Equal(t, timeMustParse("2017-07-02T00:00:00Z"), d)
d, found = FilenameDate("not-post.html")
require.False(t, found)
}
func TestTrimExt(t *testing.T) { func TestTrimExt(t *testing.T) {
require.Equal(t, "/a/b", TrimExt("/a/b.c")) require.Equal(t, "/a/b", TrimExt("/a/b.c"))
require.Equal(t, "/a/b", TrimExt("/a/b")) require.Equal(t, "/a/b", TrimExt("/a/b"))

View File

@ -2,6 +2,7 @@ package pages
import ( import (
"io" "io"
"time"
"github.com/osteele/gojekyll/pipelines" "github.com/osteele/gojekyll/pipelines"
"github.com/osteele/liquid" "github.com/osteele/liquid"
@ -30,11 +31,23 @@ type Document interface {
setPermalink() error setPermalink() error
} }
// 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.
Content(rc RenderingContext) ([]byte, error)
// 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
}
// RenderingContext provides context information for rendering. // RenderingContext provides context information for rendering.
type RenderingContext interface { type RenderingContext interface {
RenderingPipeline() pipelines.PipelineInterface RenderingPipeline() pipelines.PipelineInterface
// SiteVariables is the value of the "site" template variable. // Site is the value of the "site" template variable.
SiteVariables() map[string]interface{} // value of the "site" template variable Site() interface{} // used as a drop in the rendering context
} }
// Container is the document container. // Container is the document container.

View File

@ -13,13 +13,6 @@ import (
"github.com/osteele/liquid/generics" "github.com/osteele/liquid/generics"
) )
// Page is a post or collection page.
type Page interface {
Document
Content(rc RenderingContext) ([]byte, error)
PostDate() time.Time
}
type page struct { type page struct {
file file
raw []byte raw []byte
@ -110,7 +103,7 @@ func (p *page) MarshalYAML() (interface{}, error) {
func (p *page) TemplateContext(rc RenderingContext) map[string]interface{} { func (p *page) TemplateContext(rc RenderingContext) map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"page": p, "page": p,
"site": rc.SiteVariables(), "site": rc.Site(),
} }
} }

View File

@ -7,11 +7,13 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/osteele/gojekyll/constants"
"github.com/osteele/gojekyll/helpers" "github.com/osteele/gojekyll/helpers"
"github.com/osteele/gojekyll/templates" "github.com/osteele/gojekyll/templates"
) )
// DefaultPermalinkPattern is the default permalink pattern for pages that aren't in a collection
const DefaultPermalinkPattern = "/:path:output_ext"
// PermalinkStyles defines built-in styles from https://jekyllrb.com/docs/permalinks/#builtinpermalinkstyles // PermalinkStyles defines built-in styles from https://jekyllrb.com/docs/permalinks/#builtinpermalinkstyles
var PermalinkStyles = map[string]string{ var PermalinkStyles = map[string]string{
"date": "/:categories/:year/:month/:day/:title.html", "date": "/:categories/:year/:month/:day/:title.html",
@ -64,7 +66,7 @@ func (f *file) permalinkVariables() map[string]string {
} }
func (f *file) computePermalink(vars map[string]string) (src string, err error) { func (f *file) computePermalink(vars map[string]string) (src string, err error) {
pattern := templates.VariableMap(f.frontMatter).String("permalink", constants.DefaultPermalinkPattern) pattern := templates.VariableMap(f.frontMatter).String("permalink", DefaultPermalinkPattern)
if p, found := PermalinkStyles[pattern]; found { if p, found := PermalinkStyles[pattern]; found {
pattern = p pattern = p
} }

50
sites/drop.go Normal file
View File

@ -0,0 +1,50 @@
package sites
import (
"time"
"github.com/osteele/gojekyll/templates"
"github.com/osteele/liquid/generics"
)
// ToLiquid returns the site variable for template evaluation.
func (s *Site) ToLiquid() interface{} {
if len(s.drop) == 0 {
s.initializeDrop()
}
return s.drop
}
// MarshalYAML is part of the yaml.Marshaler interface
// The variables subcommand uses this.
func (s *Site) MarshalYAML() (interface{}, error) {
return s.ToLiquid(), nil
}
func (s *Site) initializeDrop() {
vars := templates.MergeVariableMaps(s.config.Variables, map[string]interface{}{
"data": s.data,
// "collections": s.computeCollections(), // generics.MustConvert(s.config.Collections, reflect.TypeOf([]interface{}{})),
// TODO read time from _config, if it's available
"time": time.Now(),
// TODO pages, static_files, html_pages, html_files, documents, tags.TAG
})
collections := []interface{}{}
for _, c := range s.Collections {
vars[c.Name] = c.Pages()
collections = append(collections, c.ToLiquid())
}
generics.SortByProperty(collections, "label", true)
vars["collections"] = collections
s.drop = vars
s.setPostVariables()
}
func (s *Site) setPageContent() error {
for _, c := range s.Collections {
if err := c.SetPageContent(s); err != nil {
return err
}
}
return nil
}

46
sites/posts.go Normal file
View File

@ -0,0 +1,46 @@
package sites
import (
"fmt"
"github.com/osteele/gojekyll/collections"
"github.com/osteele/gojekyll/pages"
)
func (s *Site) findPostCollection() *collections.Collection {
for _, c := range s.Collections {
if c.Name == "posts" {
return c
}
}
panic(fmt.Errorf("no posts!"))
return nil
}
func (s *Site) setPostVariables() {
c := s.findPostCollection()
if c == nil {
return
}
var (
ps = c.Pages()
related = ps
categories = map[string][]pages.Page{}
tags = map[string][]pages.Page{}
)
if len(related) > 10 {
related = related[:10]
}
for _, p := range ps {
for _, k := range p.Categories() {
ps, found := categories[k]
if !found {
ps = []pages.Page{}
}
categories[k] = append(ps, p)
}
}
s.drop["categories"] = categories
s.drop["tags"] = tags
s.drop["related_posts"] = related
}

View File

@ -27,7 +27,7 @@ type Site struct {
pipeline *pipelines.Pipeline pipeline *pipelines.Pipeline
docs []pages.Document // all documents, whether or not they are output docs []pages.Document // all documents, whether or not they are output
preparedToRender bool preparedToRender bool
siteVariables map[string]interface{} drop map[string]interface{}
} }
// SourceDir returns the site source directory. // SourceDir returns the site source directory.
@ -53,7 +53,7 @@ func (s *Site) OutputPages() []pages.Document {
// Pages returns all the pages, output or not. // Pages returns all the pages, output or not.
func (s *Site) Pages() []pages.Document { return s.docs } func (s *Site) Pages() []pages.Document { return s.docs }
// AbsDir is in the page.Container interface. // AbsDir is in the collections.Site interface.
func (s *Site) AbsDir() string { func (s *Site) AbsDir() string {
d, err := filepath.Abs(s.SourceDir()) d, err := filepath.Abs(s.SourceDir())
if err != nil { if err != nil {
@ -67,6 +67,11 @@ func (s *Site) Config() config.Config {
return s.config return s.config
} }
// Site is in the pages.RenderingContext interface.
func (s *Site) Site() interface{} {
return s
}
// PathPrefix is in the page.Container interface. // PathPrefix is in the page.Container interface.
func (s *Site) PathPrefix() string { return "" } func (s *Site) PathPrefix() string { return "" }
@ -82,8 +87,8 @@ func NewSite(flags config.Flags) *Site {
func (s *Site) SetAbsoluteURL(url string) { func (s *Site) SetAbsoluteURL(url string) {
s.config.AbsoluteURL = url s.config.AbsoluteURL = url
s.config.Variables["url"] = url s.config.Variables["url"] = url
if s.siteVariables != nil { if s.drop != nil {
s.siteVariables["url"] = url s.drop["url"] = url
} }
} }

View File

@ -1,82 +0,0 @@
package sites
import (
"time"
"github.com/osteele/gojekyll/pages"
"github.com/osteele/gojekyll/templates"
"github.com/osteele/liquid/generics"
)
// SiteVariables returns the site variable for template evaluation.
func (s *Site) SiteVariables() map[string]interface{} {
if len(s.siteVariables) == 0 {
if err := s.initializeSiteVariables(); err != nil {
panic(err)
}
}
return s.siteVariables
}
func (s *Site) initializeSiteVariables() error {
s.siteVariables = templates.MergeVariableMaps(s.config.Variables, map[string]interface{}{
"data": s.data,
// "collections": s.computeCollections(), // generics.MustConvert(s.config.Collections, reflect.TypeOf([]interface{}{})),
// TODO read time from _config, if it's available
"time": time.Now(),
// TODO pages, static_files, html_pages, html_files, documents, tags.TAG
})
return s.setCollectionVariables(false)
}
// set site[collection.name] for each collection.
func (s *Site) setCollectionVariables(includeContent bool) error {
vars := []interface{}{}
if includeContent {
s.setPageContent()
}
for _, c := range s.Collections {
s.siteVariables[c.Name] = c.Pages()
vars = append(vars, c.ToLiquid())
}
generics.SortByProperty(vars, "label", true)
s.siteVariables["collections"] = vars
return nil
}
func (s *Site) setPageContent() error {
for _, c := range s.Collections {
if err := c.SetPageContent(s); err != nil {
return err
}
}
return nil
}
func (s *Site) setPostVariables(ps []pages.Page) {
var (
related = ps
categories = map[string][]pages.Page{}
tags = map[string][]pages.Page{}
)
if len(related) > 10 {
related = related[:10]
}
for _, p := range ps {
for _, k := range p.Categories() {
ps, found := categories[k]
if !found {
ps = []pages.Page{}
}
categories[k] = append(ps, p)
}
}
s.siteVariables["categories"] = categories
s.siteVariables["tags"] = tags
s.siteVariables["related_posts"] = related
}
func (s *Site) setCollectionContent() error {
return s.setCollectionVariables(true)
}

View File

@ -15,10 +15,7 @@ func (s *Site) prepareRendering() error {
if err := s.initializeRenderingPipeline(); err != nil { if err := s.initializeRenderingPipeline(); err != nil {
return err return err
} }
if err := s.initializeSiteVariables(); err != nil { if err := s.setPageContent(); err != nil {
return err
}
if err := s.setCollectionContent(); err != nil {
return err return err
} }
s.preparedToRender = true s.preparedToRender = true