mirror of
https://github.com/danog/gojekyll.git
synced 2024-12-03 13:07:53 +01:00
Use Liquids.Drop
This commit is contained in:
parent
757b3da7f9
commit
966decdeb4
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/osteele/gojekyll/pages"
|
"github.com/osteele/gojekyll/pages"
|
||||||
"github.com/osteele/gojekyll/server"
|
"github.com/osteele/gojekyll/server"
|
||||||
"github.com/osteele/gojekyll/sites"
|
"github.com/osteele/gojekyll/sites"
|
||||||
|
"github.com/osteele/liquid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// main sets this
|
// main sets this
|
||||||
@ -57,39 +58,6 @@ func serveCommand(site *sites.Site) error {
|
|||||||
return server.Run(*open, printSetting)
|
return server.Run(*open, printSetting)
|
||||||
}
|
}
|
||||||
|
|
||||||
func varsCommand(site *sites.Site) error {
|
|
||||||
printSetting("Variables:", "")
|
|
||||||
siteData := site.SiteVariables()
|
|
||||||
// The YAML representation including collections is impractically large for debugging.
|
|
||||||
// (Actually it's circular, which the yaml package can't handle.)
|
|
||||||
// Neuter it. This destroys it as Liquid data, but that's okay in this context.
|
|
||||||
for _, c := range site.Collections {
|
|
||||||
siteData[c.Name] = fmt.Sprintf("<elided page data for %d items>", len(siteData[c.Name].([]interface{})))
|
|
||||||
}
|
|
||||||
var data map[string]interface{}
|
|
||||||
switch {
|
|
||||||
case *siteVariable:
|
|
||||||
data = siteData
|
|
||||||
case *dataVariable:
|
|
||||||
data = siteData["data"].(map[string]interface{})
|
|
||||||
if *variablePath != "" {
|
|
||||||
data = data[*variablePath].(map[string]interface{})
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
page, err := pageFromPathOrRoute(site, *variablePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data = page.PageVariables()
|
|
||||||
}
|
|
||||||
b, err := yaml.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Println(string(b))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func routesCommand(site *sites.Site) error {
|
func routesCommand(site *sites.Site) error {
|
||||||
printSetting("Routes:", "")
|
printSetting("Routes:", "")
|
||||||
urls := []string{}
|
urls := []string{}
|
||||||
@ -138,3 +106,32 @@ func pageFromPathOrRoute(s *sites.Site, path string) (pages.Document, error) {
|
|||||||
return page, nil
|
return page, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func varsCommand(site *sites.Site) error {
|
||||||
|
printSetting("Variables:", "")
|
||||||
|
siteData := site.SiteVariables()
|
||||||
|
// 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 {
|
||||||
|
// siteData[c.Name] = fmt.Sprintf("<elided page data for %d items>", len(siteData[c.Name].([]pages.Page)))
|
||||||
|
// }
|
||||||
|
var data interface{}
|
||||||
|
switch {
|
||||||
|
case *siteVariable:
|
||||||
|
data = siteData
|
||||||
|
case *dataVariable:
|
||||||
|
data = siteData["data"]
|
||||||
|
default:
|
||||||
|
page, err := pageFromPathOrRoute(site, *variablePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data = page.(liquid.Drop).ToLiquid()
|
||||||
|
}
|
||||||
|
b, err := yaml.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(string(b))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -2,12 +2,12 @@ package collections
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/osteele/gojekyll/config"
|
"github.com/osteele/gojekyll/config"
|
||||||
"github.com/osteele/gojekyll/constants"
|
"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"
|
||||||
"github.com/osteele/liquid/generics"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Collection is a Jekyll collection https://jekyllrb.com/docs/collections/.
|
// Collection is a Jekyll collection https://jekyllrb.com/docs/collections/.
|
||||||
@ -64,30 +64,39 @@ func (c *Collection) Pages() []pages.Page {
|
|||||||
return c.pages
|
return c.pages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pagesByDate struct{ pages []pages.Page }
|
||||||
|
|
||||||
|
// Len is part of sort.Interface.
|
||||||
|
func (p pagesByDate) Len() int {
|
||||||
|
return len(p.pages)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less is part of sort.Interface.
|
||||||
|
func (p pagesByDate) Less(i, j int) bool {
|
||||||
|
a, b := p.pages[i].PostDate(), p.pages[j].PostDate()
|
||||||
|
return a.Before(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap is part of sort.Interface.
|
||||||
|
func (p pagesByDate) Swap(i, j int) {
|
||||||
|
pages := p.pages
|
||||||
|
pages[i], pages[j] = pages[j], pages[i]
|
||||||
|
}
|
||||||
|
|
||||||
// TemplateVariable returns an array of page objects, for use as the template variable
|
// TemplateVariable returns an array of page objects, for use as the template variable
|
||||||
// value of the collection.
|
// value of the collection.
|
||||||
func (c *Collection) TemplateVariable(ctx pages.RenderingContext, includeContent bool) ([]interface{}, error) {
|
func (c *Collection) TemplateVariable(ctx pages.RenderingContext, includeContent bool) ([]pages.Page, error) {
|
||||||
pages := []interface{}{}
|
if includeContent {
|
||||||
for _, p := range c.Pages() {
|
for _, p := range c.Pages() {
|
||||||
v := p.PageVariables()
|
_, err := p.Content(ctx)
|
||||||
if includeContent {
|
|
||||||
c, err := p.Content(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
v = templates.MergeVariableMaps(v, map[string]interface{}{
|
|
||||||
"content": string(c),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
pages = append(pages, v)
|
|
||||||
}
|
}
|
||||||
|
pages := c.Pages()
|
||||||
if c.IsPostsCollection() {
|
if c.IsPostsCollection() {
|
||||||
generics.SortByProperty(pages, "date", true)
|
sort.Sort(pagesByDate{pages})
|
||||||
reversed := make([]interface{}, len(pages))
|
|
||||||
for i, v := range pages {
|
|
||||||
reversed[len(pages)-1-i] = v
|
|
||||||
}
|
|
||||||
pages = reversed
|
|
||||||
}
|
}
|
||||||
return pages, nil
|
return pages, nil
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,6 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var nonAlphanumericSequenceMatcher = regexp.MustCompile(`[^[:alnum:]]+`)
|
|
||||||
|
|
||||||
// Slugify replaces each sequence of non-alphanumerics by a single hyphen
|
|
||||||
func Slugify(s string) string {
|
|
||||||
return nonAlphanumericSequenceMatcher.ReplaceAllString(s, "-")
|
|
||||||
}
|
|
||||||
|
|
||||||
// LeftPad left-pads s with spaces to n wide. It's an alternative to http://left-pad.io.
|
// LeftPad left-pads s with spaces to n wide. It's an alternative to http://left-pad.io.
|
||||||
func LeftPad(s string, n int) string {
|
func LeftPad(s string, n int) string {
|
||||||
if n <= len(s) {
|
if n <= len(s) {
|
||||||
@ -23,6 +16,38 @@ func LeftPad(s string, n int) string {
|
|||||||
return string(ws) + s
|
return string(ws) + s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type replaceStringFuncError error
|
||||||
|
|
||||||
|
// SafeReplaceAllStringFunc is like regexp.ReplaceAllStringFunc but passes an
|
||||||
|
// an error back from the replacement function.
|
||||||
|
func SafeReplaceAllStringFunc(re *regexp.Regexp, src string, repl func(m string) (string, error)) (out string, err error) {
|
||||||
|
// The ReplaceAllStringFunc callback signals errors via panic.
|
||||||
|
// Turn them into return values.
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
if e, ok := r.(replaceStringFuncError); ok {
|
||||||
|
err = e.(error)
|
||||||
|
} else {
|
||||||
|
panic(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return re.ReplaceAllStringFunc(src, func(m string) string {
|
||||||
|
out, err := repl(m)
|
||||||
|
if err != nil {
|
||||||
|
panic(replaceStringFuncError(err))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonAlphanumericSequenceMatcher = regexp.MustCompile(`[^[:alnum:]]+`)
|
||||||
|
|
||||||
|
// Slugify replaces each sequence of non-alphanumerics by a single hyphen
|
||||||
|
func Slugify(s string) string {
|
||||||
|
return nonAlphanumericSequenceMatcher.ReplaceAllString(s, "-")
|
||||||
|
}
|
||||||
|
|
||||||
// StringArrayToMap creates a map for use as a set.
|
// StringArrayToMap creates a map for use as a set.
|
||||||
func StringArrayToMap(strings []string) map[string]bool {
|
func StringArrayToMap(strings []string) map[string]bool {
|
||||||
stringMap := map[string]bool{}
|
stringMap := map[string]bool{}
|
||||||
|
@ -1,11 +1,34 @@
|
|||||||
package helpers
|
package helpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestLeftPad(t *testing.T) {
|
||||||
|
require.Equal(t, "abc", LeftPad("abc", 0))
|
||||||
|
require.Equal(t, "abc", LeftPad("abc", 3))
|
||||||
|
require.Equal(t, " abc", LeftPad("abc", 6))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSafeReplaceAllStringFunc(t *testing.T) {
|
||||||
|
re := regexp.MustCompile(`\w+`)
|
||||||
|
out, err := SafeReplaceAllStringFunc(re, "1 > 0", func(m string) (string, error) {
|
||||||
|
return fmt.Sprint(m == "1"), nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "true > false", out)
|
||||||
|
|
||||||
|
out, err = SafeReplaceAllStringFunc(re, "1 > 0", func(m string) (string, error) {
|
||||||
|
return "", fmt.Errorf("an expected error")
|
||||||
|
})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, "an expected error", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
func TestSlugify(t *testing.T) {
|
func TestSlugify(t *testing.T) {
|
||||||
require.Equal(t, "abc", Slugify("abc"))
|
require.Equal(t, "abc", Slugify("abc"))
|
||||||
require.Equal(t, "ab-c", Slugify("ab.c"))
|
require.Equal(t, "ab-c", Slugify("ab.c"))
|
||||||
@ -13,11 +36,6 @@ func TestSlugify(t *testing.T) {
|
|||||||
require.Equal(t, "ab-c", Slugify("ab()[]c"))
|
require.Equal(t, "ab-c", Slugify("ab()[]c"))
|
||||||
require.Equal(t, "ab123-cde-f-g", Slugify("ab123(cde)[]f.g"))
|
require.Equal(t, "ab123-cde-f-g", Slugify("ab123(cde)[]f.g"))
|
||||||
}
|
}
|
||||||
func TestLeftPad(t *testing.T) {
|
|
||||||
require.Equal(t, "abc", LeftPad("abc", 0))
|
|
||||||
require.Equal(t, "abc", LeftPad("abc", 3))
|
|
||||||
require.Equal(t, " abc", LeftPad("abc", 6))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStringArrayToMap(t *testing.T) {
|
func TestStringArrayToMap(t *testing.T) {
|
||||||
input := []string{"a", "b", "c"}
|
input := []string{"a", "b", "c"}
|
||||||
|
@ -4,10 +4,15 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/osteele/gojekyll/pipelines"
|
"github.com/osteele/gojekyll/pipelines"
|
||||||
|
"github.com/osteele/liquid"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Document is a Jekyll page or file.
|
// Document is a Jekyll page or file.
|
||||||
type Document interface {
|
type Document interface {
|
||||||
|
liquid.Drop
|
||||||
|
yaml.Marshaler
|
||||||
|
|
||||||
// Paths
|
// Paths
|
||||||
SiteRelPath() string // relative to the site source directory
|
SiteRelPath() string // relative to the site source directory
|
||||||
Permalink() string // relative URL path
|
Permalink() string // relative URL path
|
||||||
@ -18,11 +23,11 @@ type Document interface {
|
|||||||
Static() bool
|
Static() bool
|
||||||
Write(RenderingContext, io.Writer) error
|
Write(RenderingContext, io.Writer) error
|
||||||
|
|
||||||
// Variables
|
Categories() []string
|
||||||
PageVariables() map[string]interface{}
|
Tags() []string
|
||||||
|
|
||||||
// Document initialization uses this.
|
// Document initialization
|
||||||
initPermalink() error
|
setPermalink() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderingContext provides context information for rendering.
|
// RenderingContext provides context information for rendering.
|
||||||
|
@ -6,11 +6,13 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/osteele/gojekyll/helpers"
|
"github.com/osteele/gojekyll/helpers"
|
||||||
"github.com/osteele/gojekyll/templates"
|
"github.com/osteele/gojekyll/templates"
|
||||||
|
"github.com/osteele/liquid/generics"
|
||||||
)
|
)
|
||||||
|
|
||||||
// file is embedded in StaticFile and page
|
// file is embedded in StaticFile and page
|
||||||
@ -63,16 +65,15 @@ func NewFile(filename string, c Container, relpath string, defaults map[string]i
|
|||||||
p = &StaticFile{fields}
|
p = &StaticFile{fields}
|
||||||
}
|
}
|
||||||
// Compute this after creating the page, in order to pick up the front matter.
|
// Compute this after creating the page, in order to pick up the front matter.
|
||||||
err = p.initPermalink()
|
if err = p.setPermalink(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Variables returns the attributes of the template page object.
|
// ToLiquid returns the attributes of the template page object.
|
||||||
// See https://jekyllrb.com/docs/variables/#page-variables
|
// See https://jekyllrb.com/docs/variables/#page-variables
|
||||||
func (f *file) PageVariables() map[string]interface{} {
|
func (f *file) ToLiquid() interface{} {
|
||||||
var (
|
var (
|
||||||
relpath = "/" + filepath.ToSlash(f.relpath)
|
relpath = "/" + filepath.ToSlash(f.relpath)
|
||||||
base = path.Base(relpath)
|
base = path.Base(relpath)
|
||||||
@ -88,26 +89,34 @@ func (f *file) PageVariables() map[string]interface{} {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *file) categories() []string {
|
// MarshalYAML is part of the yaml.Marshaler interface
|
||||||
if v, found := f.frontMatter["categories"]; found {
|
// The variables subcommand uses this.
|
||||||
switch v := v.(type) {
|
func (f *file) MarshalYAML() (interface{}, error) {
|
||||||
case string:
|
return f.ToLiquid(), nil
|
||||||
return strings.Fields(v)
|
}
|
||||||
case []interface{}:
|
|
||||||
sl := make([]string, len(v))
|
// Categories is in the File interface
|
||||||
for i, s := range v {
|
func (f *file) Categories() []string {
|
||||||
switch s := s.(type) {
|
return sortedStringValue(f.frontMatter["categories"])
|
||||||
case fmt.Stringer:
|
}
|
||||||
sl[i] = s.String()
|
|
||||||
default:
|
// Categories is in the File interface
|
||||||
sl[i] = fmt.Sprint(s)
|
func (f *file) Tags() []string {
|
||||||
}
|
return sortedStringValue(f.frontMatter["tags"])
|
||||||
}
|
}
|
||||||
return sl
|
|
||||||
default:
|
func sortedStringValue(field interface{}) []string {
|
||||||
fmt.Printf("%T", v)
|
out := []string{}
|
||||||
panic("unimplemented")
|
switch value := field.(type) {
|
||||||
}
|
case string:
|
||||||
}
|
out = strings.Fields(value)
|
||||||
return []string{templates.VariableMap(f.frontMatter).String("category", "")}
|
case []interface{}:
|
||||||
|
if c, e := generics.Convert(value, reflect.TypeOf(out)); e == nil {
|
||||||
|
out = c.([]string)
|
||||||
|
}
|
||||||
|
case []string:
|
||||||
|
out = value
|
||||||
|
}
|
||||||
|
sort.Strings(out)
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,22 @@ package pages
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/osteele/gojekyll/helpers"
|
"github.com/osteele/gojekyll/helpers"
|
||||||
"github.com/osteele/gojekyll/templates"
|
"github.com/osteele/gojekyll/templates"
|
||||||
|
"github.com/osteele/liquid/generics"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Page is a post or collection page.
|
// Page is a post or collection page.
|
||||||
type Page interface {
|
type Page interface {
|
||||||
Document
|
Document
|
||||||
Content(rc RenderingContext) ([]byte, error)
|
Content(rc RenderingContext) ([]byte, error)
|
||||||
|
PostDate() time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type page struct {
|
type page struct {
|
||||||
@ -42,8 +46,8 @@ func newPage(filename string, f file) (*page, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PageVariables returns the attributes of the template page object.
|
// ToLiquid is in the liquid.Drop interface.
|
||||||
func (p *page) PageVariables() map[string]interface{} {
|
func (p *page) ToLiquid() interface{} {
|
||||||
var (
|
var (
|
||||||
relpath = p.relpath
|
relpath = p.relpath
|
||||||
ext = filepath.Ext(relpath)
|
ext = filepath.Ext(relpath)
|
||||||
@ -69,8 +73,8 @@ func (p *page) PageVariables() map[string]interface{} {
|
|||||||
"title": base, // TODO capitalize
|
"title": base, // TODO capitalize
|
||||||
// TODO excerpt category? categories tags
|
// TODO excerpt category? categories tags
|
||||||
// TODO slug
|
// TODO slug
|
||||||
"categories": []string{},
|
"categories": p.Categories(),
|
||||||
"tags": []string{},
|
"tags": p.Tags(),
|
||||||
|
|
||||||
// TODO Only present in collection pages https://jekyllrb.com/docs/collections/#documents
|
// TODO Only present in collection pages https://jekyllrb.com/docs/collections/#documents
|
||||||
"relative_path": p.Path(),
|
"relative_path": p.Path(),
|
||||||
@ -89,17 +93,53 @@ func (p *page) PageVariables() map[string]interface{} {
|
|||||||
data[k] = v
|
data[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if p.content != nil {
|
||||||
|
data["content"] = string(*p.content)
|
||||||
|
// TODO excerpt
|
||||||
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalYAML is part of the yaml.Marshaler interface
|
||||||
|
// The variables subcommand uses this.
|
||||||
|
func (p *page) MarshalYAML() (interface{}, error) {
|
||||||
|
return p.ToLiquid(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// TemplateContext returns the local variables for template evaluation
|
// TemplateContext returns the local variables for template evaluation
|
||||||
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.PageVariables(),
|
"page": p,
|
||||||
"site": rc.SiteVariables(),
|
"site": rc.SiteVariables(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // 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")
|
||||||
|
}
|
||||||
|
|
||||||
// Write applies Liquid and Markdown, as appropriate.
|
// Write applies Liquid and Markdown, as appropriate.
|
||||||
func (p *page) Write(rc RenderingContext, w io.Writer) error {
|
func (p *page) Write(rc RenderingContext, w io.Writer) error {
|
||||||
rp := rc.RenderingPipeline()
|
rp := rc.RenderingPipeline()
|
||||||
|
30
pages/pages_test.go
Normal file
30
pages/pages_test.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package pages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/osteele/gojekyll/config"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type containerMock struct {
|
||||||
|
c config.Config
|
||||||
|
prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c containerMock) OutputExt(p string) string { return filepath.Ext(p) }
|
||||||
|
|
||||||
|
func (c containerMock) PathPrefix() string { return c.prefix }
|
||||||
|
|
||||||
|
func TestPageCategories(t *testing.T) {
|
||||||
|
require.Equal(t, []string{"a", "b"}, sortedStringValue("b a"))
|
||||||
|
require.Equal(t, []string{"a", "b"}, sortedStringValue([]interface{}{"b", "a"}))
|
||||||
|
require.Equal(t, []string{"a", "b"}, sortedStringValue([]string{"b", "a"}))
|
||||||
|
require.Equal(t, []string{}, sortedStringValue(3))
|
||||||
|
|
||||||
|
c := containerMock{config.Default(), ""}
|
||||||
|
fm := map[string]interface{}{"categories": "b a"}
|
||||||
|
f := file{container: c, frontMatter: fm}
|
||||||
|
require.Equal(t, []string{"a", "b"}, f.Categories())
|
||||||
|
}
|
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -38,65 +37,53 @@ var permalinkDateVariables = map[string]string{
|
|||||||
var templateVariableMatcher = regexp.MustCompile(`:\w+\b`)
|
var templateVariableMatcher = regexp.MustCompile(`:\w+\b`)
|
||||||
|
|
||||||
// See https://jekyllrb.com/docs/permalinks/#template-variables
|
// See https://jekyllrb.com/docs/permalinks/#template-variables
|
||||||
func (p *file) permalinkTemplateVariables() map[string]string {
|
func (f *file) permalinkVariables() map[string]string {
|
||||||
var (
|
var (
|
||||||
relpath = strings.TrimPrefix(p.relpath, p.container.PathPrefix())
|
relpath = strings.TrimPrefix(f.relpath, f.container.PathPrefix())
|
||||||
root = helpers.TrimExt(relpath)
|
root = helpers.TrimExt(relpath)
|
||||||
name = filepath.Base(root)
|
name = filepath.Base(root)
|
||||||
categories = p.categories()
|
fm = f.frontMatter
|
||||||
|
bindings = templates.VariableMap(fm)
|
||||||
|
slug = bindings.String("slug", helpers.Slugify(name))
|
||||||
)
|
)
|
||||||
sort.Strings(categories)
|
vars := map[string]string{
|
||||||
bindings := templates.VariableMap(p.frontMatter)
|
"categories": strings.Join(f.Categories(), "/"),
|
||||||
// TODO recognize category; list
|
|
||||||
vs := map[string]string{
|
|
||||||
"categories": strings.Join(categories, "/"),
|
|
||||||
"collection": bindings.String("collection", ""),
|
"collection": bindings.String("collection", ""),
|
||||||
"name": helpers.Slugify(name),
|
"name": helpers.Slugify(name),
|
||||||
"path": "/" + root,
|
"path": "/" + root, // TODO are we removing and then adding this?
|
||||||
"slug": bindings.String("slug", helpers.Slugify(name)),
|
"slug": slug,
|
||||||
"title": bindings.String("slug", helpers.Slugify(name)),
|
"title": slug,
|
||||||
// The following aren't documented, but is evident
|
// The following aren't documented, but are evident
|
||||||
"output_ext": p.OutputExt(),
|
"output_ext": f.OutputExt(),
|
||||||
"y_day": strconv.Itoa(p.fileModTime.YearDay()),
|
"y_day": strconv.Itoa(f.fileModTime.YearDay()),
|
||||||
}
|
}
|
||||||
for name, f := range permalinkDateVariables {
|
for k, v := range permalinkDateVariables {
|
||||||
vs[name] = p.fileModTime.Format(f)
|
vars[k] = f.fileModTime.Format(v)
|
||||||
}
|
}
|
||||||
return vs
|
return vars
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *file) expandPermalink() (s string, err error) {
|
func (f *file) computePermalink(vars map[string]string) (src string, err error) {
|
||||||
pattern := templates.VariableMap(p.frontMatter).String("permalink", constants.DefaultPermalinkPattern)
|
pattern := templates.VariableMap(f.frontMatter).String("permalink", constants.DefaultPermalinkPattern)
|
||||||
|
|
||||||
if p, found := PermalinkStyles[pattern]; found {
|
if p, found := PermalinkStyles[pattern]; found {
|
||||||
pattern = p
|
pattern = p
|
||||||
}
|
}
|
||||||
templateVariables := p.permalinkTemplateVariables()
|
templateVariables := f.permalinkVariables()
|
||||||
// The ReplaceAllStringFunc callback signals errors via panic.
|
s, err := helpers.SafeReplaceAllStringFunc(templateVariableMatcher, pattern, func(m string) (string, error) {
|
||||||
// Turn them into return values.
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
if e, ok := r.(error); ok {
|
|
||||||
err = e
|
|
||||||
} else {
|
|
||||||
panic(r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
s = templateVariableMatcher.ReplaceAllStringFunc(pattern, func(m string) string {
|
|
||||||
varname := m[1:]
|
varname := m[1:]
|
||||||
value, found := templateVariables[varname]
|
value, found := templateVariables[varname]
|
||||||
if !found {
|
if !found {
|
||||||
panic(fmt.Errorf("unknown variable %q in permalink template %q", varname, pattern))
|
return "", fmt.Errorf("unknown variable %q in permalink template %q", varname, pattern)
|
||||||
}
|
}
|
||||||
return value
|
return value, nil
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
return helpers.URLPathClean("/" + s), nil
|
return helpers.URLPathClean("/" + s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// The permalink is computed once instead of on demand, so that subsequent
|
func (f *file) setPermalink() (err error) {
|
||||||
// access needn't check for an error.
|
f.permalink, err = f.computePermalink(f.permalinkVariables())
|
||||||
func (p *file) initPermalink() (err error) {
|
|
||||||
p.permalink, err = p.expandPermalink()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -10,16 +10,6 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type containerMock struct {
|
|
||||||
c config.Config
|
|
||||||
p string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c containerMock) AbsDir() string { return "" }
|
|
||||||
func (c containerMock) Config() config.Config { return c.c }
|
|
||||||
func (c containerMock) OutputExt(p string) string { return filepath.Ext(p) }
|
|
||||||
func (c containerMock) PathPrefix() string { return c.p }
|
|
||||||
|
|
||||||
type pathTest struct{ path, pattern, out string }
|
type pathTest struct{ path, pattern, out string }
|
||||||
|
|
||||||
var tests = []pathTest{
|
var tests = []pathTest{
|
||||||
@ -53,17 +43,17 @@ func TestExpandPermalinkPattern(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
testPermalinkPattern := func(pattern, path string, data map[string]interface{}) (string, error) {
|
testPermalinkPattern := func(pattern, path string, data map[string]interface{}) (string, error) {
|
||||||
vs := templates.MergeVariableMaps(data, map[string]interface{}{"permalink": pattern})
|
fm := templates.MergeVariableMaps(data, map[string]interface{}{"permalink": pattern})
|
||||||
ext := filepath.Ext(path)
|
ext := filepath.Ext(path)
|
||||||
switch ext {
|
switch ext {
|
||||||
case ".md", ".markdown":
|
case ".md", ".markdown":
|
||||||
ext = ".html"
|
ext = ".html"
|
||||||
}
|
}
|
||||||
p := file{container: c, relpath: path, frontMatter: vs, outputExt: ext}
|
p := file{container: c, relpath: path, frontMatter: fm, outputExt: ext}
|
||||||
t0, err := time.Parse(time.RFC3339, "2006-02-03T15:04:05Z")
|
t0, err := time.Parse(time.RFC3339, "2006-02-03T15:04:05Z")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
p.fileModTime = t0
|
p.fileModTime = t0
|
||||||
return p.expandPermalink()
|
return p.computePermalink(p.permalinkVariables())
|
||||||
}
|
}
|
||||||
|
|
||||||
runTests := func(tests []pathTest) {
|
runTests := func(tests []pathTest) {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package sites
|
package sites
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/osteele/gojekyll/pages"
|
||||||
"github.com/osteele/gojekyll/templates"
|
"github.com/osteele/gojekyll/templates"
|
||||||
"github.com/osteele/liquid/generics"
|
"github.com/osteele/liquid/generics"
|
||||||
)
|
)
|
||||||
@ -48,27 +48,22 @@ func (s *Site) setCollectionVariables(includeContent bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Site) setPostVariables(pages []interface{}) {
|
func (s *Site) setPostVariables(ps []pages.Page) {
|
||||||
var (
|
var (
|
||||||
related = pages
|
related = ps
|
||||||
categories = map[string][]interface{}{}
|
categories = map[string][]pages.Page{}
|
||||||
tags = map[string][]interface{}{}
|
tags = map[string][]pages.Page{}
|
||||||
)
|
)
|
||||||
if len(related) > 10 {
|
if len(related) > 10 {
|
||||||
related = related[:10]
|
related = related[:10]
|
||||||
}
|
}
|
||||||
for _, p := range pages {
|
for _, p := range ps {
|
||||||
b := p.(map[string]interface{})
|
for _, k := range p.Categories() {
|
||||||
switch cs := b["categories"].(type) {
|
ps, found := categories[k]
|
||||||
case []interface{}:
|
if !found {
|
||||||
for _, c := range cs {
|
ps = []pages.Page{}
|
||||||
key := fmt.Sprint(c)
|
|
||||||
ps, found := categories[key]
|
|
||||||
if !found {
|
|
||||||
ps = []interface{}{}
|
|
||||||
}
|
|
||||||
categories[key] = append(ps, p)
|
|
||||||
}
|
}
|
||||||
|
categories[k] = append(ps, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.siteVariables["categories"] = categories
|
s.siteVariables["categories"] = categories
|
||||||
|
Loading…
Reference in New Issue
Block a user