1
0
mirror of https://github.com/danog/gojekyll.git synced 2024-11-26 23:34:47 +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 {
printSetting("Variables:", "")
siteData := site.SiteVariables()
siteData := site.ToLiquid().(map[string]interface{})
// 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.
// for _, c := range site.Collections {

View File

@ -4,7 +4,6 @@ import (
"path/filepath"
"github.com/osteele/gojekyll/config"
"github.com/osteele/gojekyll/constants"
"github.com/osteele/gojekyll/pages"
"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 {
defaultPattern := constants.DefaultCollectionPermalinkPattern
if c.IsPostsCollection() {
defaultPattern = constants.DefaultPostsCollectionPermalinkPattern
}
defaultPattern := c.strategy().defaultPermalinkPattern()
return templates.VariableMap(c.Metadata).String("permalink", defaultPattern)
}

View File

@ -1,8 +1,9 @@
package collections
import (
"path/filepath"
"time"
"github.com/osteele/gojekyll/helpers"
)
// A collectionStrategy encapsulates behavior differences between the _post
@ -10,6 +11,7 @@ import (
type collectionStrategy interface {
addDate(filename string, fm map[string]interface{})
collectible(filename string) bool
defaultPermalinkPattern() string
future(filename string) bool
}
@ -29,25 +31,31 @@ func (s defaultStrategy) future(filename string) bool { return fa
type postsStrategy struct{}
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
}
}
func (s postsStrategy) collectible(filename string) bool {
_, ok := DateFromFilename(filename)
_, ok := helpers.FilenameDate(filename)
return ok
}
func (s postsStrategy) future(filename string) bool {
t, ok := DateFromFilename(filename)
t, ok := helpers.FilenameDate(filename)
return ok && t.After(time.Now())
}
// DateFromFilename returns the date for a filename that uses Jekyll post convention.
// It also returns a bool indicating whether a date was found.
func DateFromFilename(s string) (time.Time, bool) {
layout := "2006-01-02-"
t, err := time.Parse(layout, filepath.Base(s + layout)[:len(layout)])
return t, err == nil
// 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"
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/filepath"
"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.
func MustRel(basepath, targpath string) string {
relpath, err := filepath.Rel(basepath, targpath)

View File

@ -2,10 +2,28 @@ package helpers
import (
"testing"
"time"
"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) {
require.Equal(t, "/a/b", TrimExt("/a/b.c"))
require.Equal(t, "/a/b", TrimExt("/a/b"))

View File

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

View File

@ -13,13 +13,6 @@ import (
"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 {
file
raw []byte
@ -110,7 +103,7 @@ func (p *page) MarshalYAML() (interface{}, error) {
func (p *page) TemplateContext(rc RenderingContext) map[string]interface{} {
return map[string]interface{}{
"page": p,
"site": rc.SiteVariables(),
"site": rc.Site(),
}
}

View File

@ -7,11 +7,13 @@ import (
"strconv"
"strings"
"github.com/osteele/gojekyll/constants"
"github.com/osteele/gojekyll/helpers"
"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
var PermalinkStyles = map[string]string{
"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) {
pattern := templates.VariableMap(f.frontMatter).String("permalink", constants.DefaultPermalinkPattern)
pattern := templates.VariableMap(f.frontMatter).String("permalink", DefaultPermalinkPattern)
if p, found := PermalinkStyles[pattern]; found {
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
docs []pages.Document // all documents, whether or not they are output
preparedToRender bool
siteVariables map[string]interface{}
drop map[string]interface{}
}
// SourceDir returns the site source directory.
@ -53,7 +53,7 @@ func (s *Site) OutputPages() []pages.Document {
// Pages returns all the pages, output or not.
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 {
d, err := filepath.Abs(s.SourceDir())
if err != nil {
@ -67,6 +67,11 @@ func (s *Site) Config() config.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.
func (s *Site) PathPrefix() string { return "" }
@ -82,8 +87,8 @@ func NewSite(flags config.Flags) *Site {
func (s *Site) SetAbsoluteURL(url string) {
s.config.AbsoluteURL = url
s.config.Variables["url"] = url
if s.siteVariables != nil {
s.siteVariables["url"] = url
if s.drop != nil {
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 {
return err
}
if err := s.initializeSiteVariables(); err != nil {
return err
}
if err := s.setCollectionContent(); err != nil {
if err := s.setPageContent(); err != nil {
return err
}
s.preparedToRender = true