mirror of
https://github.com/danog/gojekyll.git
synced 2024-11-30 06:59:04 +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/server"
|
||||
"github.com/osteele/gojekyll/sites"
|
||||
"github.com/osteele/liquid"
|
||||
)
|
||||
|
||||
// main sets this
|
||||
@ -57,39 +58,6 @@ func serveCommand(site *sites.Site) error {
|
||||
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 {
|
||||
printSetting("Routes:", "")
|
||||
urls := []string{}
|
||||
@ -138,3 +106,32 @@ func pageFromPathOrRoute(s *sites.Site, path string) (pages.Document, error) {
|
||||
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 (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/osteele/gojekyll/config"
|
||||
"github.com/osteele/gojekyll/constants"
|
||||
"github.com/osteele/gojekyll/pages"
|
||||
"github.com/osteele/gojekyll/templates"
|
||||
"github.com/osteele/liquid/generics"
|
||||
)
|
||||
|
||||
// Collection is a Jekyll collection https://jekyllrb.com/docs/collections/.
|
||||
@ -64,30 +64,39 @@ func (c *Collection) Pages() []pages.Page {
|
||||
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
|
||||
// value of the collection.
|
||||
func (c *Collection) TemplateVariable(ctx pages.RenderingContext, includeContent bool) ([]interface{}, error) {
|
||||
pages := []interface{}{}
|
||||
for _, p := range c.Pages() {
|
||||
v := p.PageVariables()
|
||||
if includeContent {
|
||||
c, err := p.Content(ctx)
|
||||
func (c *Collection) TemplateVariable(ctx pages.RenderingContext, includeContent bool) ([]pages.Page, error) {
|
||||
if includeContent {
|
||||
for _, p := range c.Pages() {
|
||||
_, err := p.Content(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v = templates.MergeVariableMaps(v, map[string]interface{}{
|
||||
"content": string(c),
|
||||
})
|
||||
}
|
||||
pages = append(pages, v)
|
||||
}
|
||||
pages := c.Pages()
|
||||
if c.IsPostsCollection() {
|
||||
generics.SortByProperty(pages, "date", true)
|
||||
reversed := make([]interface{}, len(pages))
|
||||
for i, v := range pages {
|
||||
reversed[len(pages)-1-i] = v
|
||||
}
|
||||
pages = reversed
|
||||
sort.Sort(pagesByDate{pages})
|
||||
}
|
||||
return pages, nil
|
||||
}
|
||||
|
@ -4,13 +4,6 @@ import (
|
||||
"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.
|
||||
func LeftPad(s string, n int) string {
|
||||
if n <= len(s) {
|
||||
@ -23,6 +16,38 @@ func LeftPad(s string, n int) string {
|
||||
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.
|
||||
func StringArrayToMap(strings []string) map[string]bool {
|
||||
stringMap := map[string]bool{}
|
||||
|
@ -1,11 +1,34 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"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) {
|
||||
require.Equal(t, "abc", Slugify("abc"))
|
||||
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, "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) {
|
||||
input := []string{"a", "b", "c"}
|
||||
|
@ -4,10 +4,15 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/osteele/gojekyll/pipelines"
|
||||
"github.com/osteele/liquid"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Document is a Jekyll page or file.
|
||||
type Document interface {
|
||||
liquid.Drop
|
||||
yaml.Marshaler
|
||||
|
||||
// Paths
|
||||
SiteRelPath() string // relative to the site source directory
|
||||
Permalink() string // relative URL path
|
||||
@ -18,11 +23,11 @@ type Document interface {
|
||||
Static() bool
|
||||
Write(RenderingContext, io.Writer) error
|
||||
|
||||
// Variables
|
||||
PageVariables() map[string]interface{}
|
||||
Categories() []string
|
||||
Tags() []string
|
||||
|
||||
// Document initialization uses this.
|
||||
initPermalink() error
|
||||
// Document initialization
|
||||
setPermalink() error
|
||||
}
|
||||
|
||||
// RenderingContext provides context information for rendering.
|
||||
|
@ -6,11 +6,13 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/osteele/gojekyll/helpers"
|
||||
"github.com/osteele/gojekyll/templates"
|
||||
"github.com/osteele/liquid/generics"
|
||||
)
|
||||
|
||||
// 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}
|
||||
}
|
||||
// Compute this after creating the page, in order to pick up the front matter.
|
||||
err = p.initPermalink()
|
||||
if err != nil {
|
||||
if err = p.setPermalink(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
func (f *file) PageVariables() map[string]interface{} {
|
||||
func (f *file) ToLiquid() interface{} {
|
||||
var (
|
||||
relpath = "/" + filepath.ToSlash(f.relpath)
|
||||
base = path.Base(relpath)
|
||||
@ -88,26 +89,34 @@ func (f *file) PageVariables() map[string]interface{} {
|
||||
})
|
||||
}
|
||||
|
||||
func (f *file) categories() []string {
|
||||
if v, found := f.frontMatter["categories"]; found {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
return strings.Fields(v)
|
||||
case []interface{}:
|
||||
sl := make([]string, len(v))
|
||||
for i, s := range v {
|
||||
switch s := s.(type) {
|
||||
case fmt.Stringer:
|
||||
sl[i] = s.String()
|
||||
default:
|
||||
sl[i] = fmt.Sprint(s)
|
||||
}
|
||||
}
|
||||
return sl
|
||||
default:
|
||||
fmt.Printf("%T", v)
|
||||
panic("unimplemented")
|
||||
}
|
||||
}
|
||||
return []string{templates.VariableMap(f.frontMatter).String("category", "")}
|
||||
// MarshalYAML is part of the yaml.Marshaler interface
|
||||
// The variables subcommand uses this.
|
||||
func (f *file) MarshalYAML() (interface{}, error) {
|
||||
return f.ToLiquid(), nil
|
||||
}
|
||||
|
||||
// Categories is in the File interface
|
||||
func (f *file) Categories() []string {
|
||||
return sortedStringValue(f.frontMatter["categories"])
|
||||
}
|
||||
|
||||
// Categories is in the File interface
|
||||
func (f *file) Tags() []string {
|
||||
return sortedStringValue(f.frontMatter["tags"])
|
||||
}
|
||||
|
||||
func sortedStringValue(field interface{}) []string {
|
||||
out := []string{}
|
||||
switch value := field.(type) {
|
||||
case string:
|
||||
out = strings.Fields(value)
|
||||
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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/osteele/gojekyll/helpers"
|
||||
"github.com/osteele/gojekyll/templates"
|
||||
"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 {
|
||||
@ -42,8 +46,8 @@ func newPage(filename string, f file) (*page, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PageVariables returns the attributes of the template page object.
|
||||
func (p *page) PageVariables() map[string]interface{} {
|
||||
// ToLiquid is in the liquid.Drop interface.
|
||||
func (p *page) ToLiquid() interface{} {
|
||||
var (
|
||||
relpath = p.relpath
|
||||
ext = filepath.Ext(relpath)
|
||||
@ -69,8 +73,8 @@ func (p *page) PageVariables() map[string]interface{} {
|
||||
"title": base, // TODO capitalize
|
||||
// TODO excerpt category? categories tags
|
||||
// TODO slug
|
||||
"categories": []string{},
|
||||
"tags": []string{},
|
||||
"categories": p.Categories(),
|
||||
"tags": p.Tags(),
|
||||
|
||||
// TODO Only present in collection pages https://jekyllrb.com/docs/collections/#documents
|
||||
"relative_path": p.Path(),
|
||||
@ -89,17 +93,53 @@ func (p *page) PageVariables() map[string]interface{} {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
if p.content != nil {
|
||||
data["content"] = string(*p.content)
|
||||
// TODO excerpt
|
||||
}
|
||||
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
|
||||
func (p *page) TemplateContext(rc RenderingContext) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"page": p.PageVariables(),
|
||||
"page": p,
|
||||
"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.
|
||||
func (p *page) Write(rc RenderingContext, w io.Writer) error {
|
||||
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"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -38,65 +37,53 @@ var permalinkDateVariables = map[string]string{
|
||||
var templateVariableMatcher = regexp.MustCompile(`:\w+\b`)
|
||||
|
||||
// See https://jekyllrb.com/docs/permalinks/#template-variables
|
||||
func (p *file) permalinkTemplateVariables() map[string]string {
|
||||
func (f *file) permalinkVariables() map[string]string {
|
||||
var (
|
||||
relpath = strings.TrimPrefix(p.relpath, p.container.PathPrefix())
|
||||
root = helpers.TrimExt(relpath)
|
||||
name = filepath.Base(root)
|
||||
categories = p.categories()
|
||||
relpath = strings.TrimPrefix(f.relpath, f.container.PathPrefix())
|
||||
root = helpers.TrimExt(relpath)
|
||||
name = filepath.Base(root)
|
||||
fm = f.frontMatter
|
||||
bindings = templates.VariableMap(fm)
|
||||
slug = bindings.String("slug", helpers.Slugify(name))
|
||||
)
|
||||
sort.Strings(categories)
|
||||
bindings := templates.VariableMap(p.frontMatter)
|
||||
// TODO recognize category; list
|
||||
vs := map[string]string{
|
||||
"categories": strings.Join(categories, "/"),
|
||||
vars := map[string]string{
|
||||
"categories": strings.Join(f.Categories(), "/"),
|
||||
"collection": bindings.String("collection", ""),
|
||||
"name": helpers.Slugify(name),
|
||||
"path": "/" + root,
|
||||
"slug": bindings.String("slug", helpers.Slugify(name)),
|
||||
"title": bindings.String("slug", helpers.Slugify(name)),
|
||||
// The following aren't documented, but is evident
|
||||
"output_ext": p.OutputExt(),
|
||||
"y_day": strconv.Itoa(p.fileModTime.YearDay()),
|
||||
"path": "/" + root, // TODO are we removing and then adding this?
|
||||
"slug": slug,
|
||||
"title": slug,
|
||||
// The following aren't documented, but are evident
|
||||
"output_ext": f.OutputExt(),
|
||||
"y_day": strconv.Itoa(f.fileModTime.YearDay()),
|
||||
}
|
||||
for name, f := range permalinkDateVariables {
|
||||
vs[name] = p.fileModTime.Format(f)
|
||||
for k, v := range permalinkDateVariables {
|
||||
vars[k] = f.fileModTime.Format(v)
|
||||
}
|
||||
return vs
|
||||
return vars
|
||||
}
|
||||
|
||||
func (p *file) expandPermalink() (s string, err error) {
|
||||
pattern := templates.VariableMap(p.frontMatter).String("permalink", constants.DefaultPermalinkPattern)
|
||||
|
||||
func (f *file) computePermalink(vars map[string]string) (src string, err error) {
|
||||
pattern := templates.VariableMap(f.frontMatter).String("permalink", constants.DefaultPermalinkPattern)
|
||||
if p, found := PermalinkStyles[pattern]; found {
|
||||
pattern = p
|
||||
}
|
||||
templateVariables := p.permalinkTemplateVariables()
|
||||
// The ReplaceAllStringFunc callback signals errors via panic.
|
||||
// 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 {
|
||||
templateVariables := f.permalinkVariables()
|
||||
s, err := helpers.SafeReplaceAllStringFunc(templateVariableMatcher, pattern, func(m string) (string, error) {
|
||||
varname := m[1:]
|
||||
value, found := templateVariables[varname]
|
||||
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
|
||||
}
|
||||
|
||||
// The permalink is computed once instead of on demand, so that subsequent
|
||||
// access needn't check for an error.
|
||||
func (p *file) initPermalink() (err error) {
|
||||
p.permalink, err = p.expandPermalink()
|
||||
func (f *file) setPermalink() (err error) {
|
||||
f.permalink, err = f.computePermalink(f.permalinkVariables())
|
||||
return
|
||||
}
|
||||
|
@ -10,16 +10,6 @@ import (
|
||||
"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 }
|
||||
|
||||
var tests = []pathTest{
|
||||
@ -53,17 +43,17 @@ func TestExpandPermalinkPattern(t *testing.T) {
|
||||
)
|
||||
|
||||
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)
|
||||
switch ext {
|
||||
case ".md", ".markdown":
|
||||
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")
|
||||
require.NoError(t, err)
|
||||
p.fileModTime = t0
|
||||
return p.expandPermalink()
|
||||
return p.computePermalink(p.permalinkVariables())
|
||||
}
|
||||
|
||||
runTests := func(tests []pathTest) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
package sites
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/osteele/gojekyll/pages"
|
||||
"github.com/osteele/gojekyll/templates"
|
||||
"github.com/osteele/liquid/generics"
|
||||
)
|
||||
@ -48,27 +48,22 @@ func (s *Site) setCollectionVariables(includeContent bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Site) setPostVariables(pages []interface{}) {
|
||||
func (s *Site) setPostVariables(ps []pages.Page) {
|
||||
var (
|
||||
related = pages
|
||||
categories = map[string][]interface{}{}
|
||||
tags = map[string][]interface{}{}
|
||||
related = ps
|
||||
categories = map[string][]pages.Page{}
|
||||
tags = map[string][]pages.Page{}
|
||||
)
|
||||
if len(related) > 10 {
|
||||
related = related[:10]
|
||||
}
|
||||
for _, p := range pages {
|
||||
b := p.(map[string]interface{})
|
||||
switch cs := b["categories"].(type) {
|
||||
case []interface{}:
|
||||
for _, c := range cs {
|
||||
key := fmt.Sprint(c)
|
||||
ps, found := categories[key]
|
||||
if !found {
|
||||
ps = []interface{}{}
|
||||
}
|
||||
categories[key] = append(ps, p)
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user