resource: Add front matter metadata to Resource

This commit expands the Resource interface with 3 new methods:

* Name
* Title
* Params

All of these can be set in the Page front matter. `Name` will get its default value from the base filename, and is the value used in the ByPrefix and GetByPrefix lookup methods.

Fixes #4244
This commit is contained in:
Bjørn Erik Pedersen 2018-01-15 20:40:39 +01:00
parent f8a119b606
commit 20c9b6ec81
No known key found for this signature in database
GPG key ID: 330E6E2BD4859D8F
27 changed files with 627 additions and 187 deletions

View file

@ -225,7 +225,7 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
gp1 := sites.GetContentPage(filepath.FromSlash("content/sect/doc1.en.md"))
require.NotNil(t, gp1)
require.Equal(t, "doc1", gp1.Title)
require.Equal(t, "doc1", gp1.title)
gp2 := sites.GetContentPage(filepath.FromSlash("content/dummysect/notfound.md"))
require.Nil(t, gp2)
@ -317,9 +317,9 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
require.Len(t, homeEn.Translations(), 3)
require.Equal(t, "fr", homeEn.Translations()[0].Lang())
require.Equal(t, "nn", homeEn.Translations()[1].Lang())
require.Equal(t, "På nynorsk", homeEn.Translations()[1].Title)
require.Equal(t, "På nynorsk", homeEn.Translations()[1].title)
require.Equal(t, "nb", homeEn.Translations()[2].Lang())
require.Equal(t, "På bokmål", homeEn.Translations()[2].Title, configSuffix)
require.Equal(t, "På bokmål", homeEn.Translations()[2].title, configSuffix)
require.Equal(t, "Bokmål", homeEn.Translations()[2].Language().LanguageName, configSuffix)
sectFr := frSite.getPage(KindSection, "sect")
@ -328,7 +328,7 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
require.Equal(t, "fr", sectFr.Lang())
require.Len(t, sectFr.Translations(), 1)
require.Equal(t, "en", sectFr.Translations()[0].Lang())
require.Equal(t, "Sects", sectFr.Translations()[0].Title)
require.Equal(t, "Sects", sectFr.Translations()[0].title)
nnSite := sites.Sites[2]
require.Equal(t, "nn", nnSite.Language.Lang)
@ -495,9 +495,9 @@ func TestMultiSitesRebuild(t *testing.T) {
require.Len(t, enSite.RegularPages, 6)
require.Len(t, enSite.AllPages, 34)
require.Len(t, frSite.RegularPages, 5)
require.Equal(t, "new_fr_1", frSite.RegularPages[3].Title)
require.Equal(t, "new_en_2", enSite.RegularPages[0].Title)
require.Equal(t, "new_en_1", enSite.RegularPages[1].Title)
require.Equal(t, "new_fr_1", frSite.RegularPages[3].title)
require.Equal(t, "new_en_2", enSite.RegularPages[0].title)
require.Equal(t, "new_en_1", enSite.RegularPages[1].title)
rendered := readDestination(t, fs, "public/en/new1/index.html")
require.True(t, strings.Contains(rendered, "new_en_1"), rendered)
@ -531,7 +531,7 @@ func TestMultiSitesRebuild(t *testing.T) {
},
func(t *testing.T) {
require.Len(t, enSite.RegularPages, 6, "Rename")
require.Equal(t, "new_en_1", enSite.RegularPages[1].Title)
require.Equal(t, "new_en_1", enSite.RegularPages[1].title)
rendered := readDestination(t, fs, "public/en/new1renamed/index.html")
require.True(t, strings.Contains(rendered, "new_en_1"), rendered)
}},
@ -683,7 +683,7 @@ title = "Svenska"
// Veriy Swedish site
require.Len(t, svSite.RegularPages, 1)
svPage := svSite.RegularPages[0]
require.Equal(t, "Swedish Contentfile", svPage.Title)
require.Equal(t, "Swedish Contentfile", svPage.title)
require.Equal(t, "sv", svPage.Lang())
require.Len(t, svPage.Translations(), 2)
require.Len(t, svPage.AllTranslations(), 3)

View file

@ -104,7 +104,7 @@ func doTestNodeAsPage(t *testing.T, ugly, preserveTaxonomyNames bool) {
require.True(t, home.Path() != "")
section2 := nodes[5]
require.Equal(t, "Section2", section2.Title)
require.Equal(t, "Section2", section2.title)
pages := sites.findAllPagesByKind(KindPage)
require.Len(t, pages, 4)
@ -252,9 +252,9 @@ func doTestNodesWithNoContentFile(t *testing.T, ugly bool) {
for _, p := range pages {
var want string
if ugly {
want = "/" + p.s.PathSpec.URLize(p.Title) + ".html"
want = "/" + p.s.PathSpec.URLize(p.title) + ".html"
} else {
want = "/" + p.s.PathSpec.URLize(p.Title) + "/"
want = "/" + p.s.PathSpec.URLize(p.title) + "/"
}
if p.URL() != want {
t.Errorf("Taxonomy term URL mismatch: want %q, got %q", want, p.URL())

View file

@ -111,6 +111,10 @@ type Page struct {
// provided by the Resource object.
Resources resource.Resources
// This is the raw front matter metadata that is going to be assigned to
// the Resources above.
resourcesMetadata []map[string]interface{}
// translations will contain references to this page in other language
// if available.
translations Pages
@ -120,7 +124,7 @@ type Page struct {
translationKey string
// Params contains configuration defined in the params section of page frontmatter.
Params map[string]interface{}
params map[string]interface{}
// Content sections
Content template.HTML
@ -214,7 +218,7 @@ type Page struct {
Site *SiteInfo `json:"-"`
Title string
title string
Description string
Keywords []string
Data map[string]interface{}
@ -468,7 +472,7 @@ func (p *Page) Param(key interface{}) (interface{}, error) {
func (p *Page) traverseDirect(key string) (interface{}, error) {
keyStr := strings.ToLower(key)
if val, ok := p.Params[keyStr]; ok {
if val, ok := p.params[keyStr]; ok {
return val, nil
}
@ -476,7 +480,7 @@ func (p *Page) traverseDirect(key string) (interface{}, error) {
}
func (p *Page) traverseNested(keySegments []string) (interface{}, error) {
result := traverse(keySegments, p.Params)
result := traverse(keySegments, p.params)
if result != nil {
return result, nil
}
@ -519,7 +523,7 @@ func (p *Page) Author() Author {
}
func (p *Page) Authors() AuthorList {
authorKeys, ok := p.Params["authors"]
authorKeys, ok := p.params["authors"]
if !ok {
return AuthorList{}
}
@ -757,7 +761,7 @@ func (s *Site) newPageFromFile(fi *fileInfo) *Page {
contentType: "",
Source: Source{File: fi},
Keywords: []string{}, Sitemap: Sitemap{Priority: -1},
Params: make(map[string]interface{}),
params: make(map[string]interface{}),
translations: make(Pages, 0),
sections: sectionsFromDir(fi.Dir()),
Site: &s.Info,
@ -927,7 +931,7 @@ func (p *Page) LinkTitle() string {
if len(p.linkTitle) > 0 {
return p.linkTitle
}
return p.Title
return p.title
}
func (p *Page) shouldBuild() bool {
@ -988,6 +992,22 @@ func (p *Page) RelPermalink() string {
return p.relPermalink
}
// See resource.Resource
func (p *Page) Name() string {
if p.File != nil {
return p.File.BaseFileName()
}
return p.title
}
func (p *Page) Title() string {
return p.title
}
func (p *Page) Params() map[string]interface{} {
return p.params
}
func (p *Page) subResourceTargetPathFactory(base string) string {
return path.Join(p.relTargetPathBase, base)
}
@ -1094,39 +1114,39 @@ func (p *Page) update(f interface{}) error {
loki := strings.ToLower(k)
switch loki {
case "title":
p.Title = cast.ToString(v)
p.Params[loki] = p.Title
p.title = cast.ToString(v)
p.params[loki] = p.title
case "linktitle":
p.linkTitle = cast.ToString(v)
p.Params[loki] = p.linkTitle
p.params[loki] = p.linkTitle
case "description":
p.Description = cast.ToString(v)
p.Params[loki] = p.Description
p.params[loki] = p.Description
case "slug":
p.Slug = cast.ToString(v)
p.Params[loki] = p.Slug
p.params[loki] = p.Slug
case "url":
if url := cast.ToString(v); strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
return fmt.Errorf("Only relative URLs are supported, %v provided", url)
}
p.URLPath.URL = cast.ToString(v)
p.URLPath.frontMatterURL = p.URLPath.URL
p.Params[loki] = p.URLPath.URL
p.params[loki] = p.URLPath.URL
case "type":
p.contentType = cast.ToString(v)
p.Params[loki] = p.contentType
p.params[loki] = p.contentType
case "extension", "ext":
p.extension = cast.ToString(v)
p.Params[loki] = p.extension
p.params[loki] = p.extension
case "keywords":
p.Keywords = cast.ToStringSlice(v)
p.Params[loki] = p.Keywords
p.params[loki] = p.Keywords
case "date":
p.Date, err = cast.ToTimeE(v)
if err != nil {
p.s.Log.ERROR.Printf("Failed to parse date '%v' in page %s", v, p.File.Path())
}
p.Params[loki] = p.Date
p.params[loki] = p.Date
case "lastmod":
p.Lastmod, err = cast.ToTimeE(v)
if err != nil {
@ -1135,10 +1155,10 @@ func (p *Page) update(f interface{}) error {
case "modified":
vv, err := cast.ToTimeE(v)
if err == nil {
p.Params[loki] = vv
p.params[loki] = vv
modified = vv
} else {
p.Params[loki] = cast.ToString(v)
p.params[loki] = cast.ToString(v)
}
case "outputs":
o := cast.ToStringSlice(v)
@ -1150,17 +1170,16 @@ func (p *Page) update(f interface{}) error {
p.s.Log.ERROR.Printf("Failed to resolve output formats: %s", err)
} else {
p.outputFormats = outFormats
p.Params[loki] = outFormats
p.params[loki] = outFormats
}
}
//p.Params[loki] = p.Keywords
case "publishdate", "pubdate":
p.PublishDate, err = cast.ToTimeE(v)
if err != nil {
p.s.Log.ERROR.Printf("Failed to parse publishdate '%v' in page %s", v, p.File.Path())
}
p.Params[loki] = p.PublishDate
p.params[loki] = p.PublishDate
case "expirydate", "unpublishdate":
p.ExpiryDate, err = cast.ToTimeE(v)
if err != nil {
@ -1178,20 +1197,20 @@ func (p *Page) update(f interface{}) error {
vv, err := cast.ToTimeE(v)
if err == nil {
p.PublishDate = vv
p.Params[loki] = p.PublishDate
p.params[loki] = p.PublishDate
} else {
p.Params[loki] = cast.ToString(v)
p.params[loki] = cast.ToString(v)
}
}
case "layout":
p.Layout = cast.ToString(v)
p.Params[loki] = p.Layout
p.params[loki] = p.Layout
case "markup":
p.Markup = cast.ToString(v)
p.Params[loki] = p.Markup
p.params[loki] = p.Markup
case "weight":
p.Weight = cast.ToInt(v)
p.Params[loki] = p.Weight
p.params[loki] = p.Weight
case "aliases":
p.Aliases = cast.ToStringSlice(v)
for _, alias := range p.Aliases {
@ -1199,56 +1218,89 @@ func (p *Page) update(f interface{}) error {
return fmt.Errorf("Only relative aliases are supported, %v provided", alias)
}
}
p.Params[loki] = p.Aliases
p.params[loki] = p.Aliases
case "status":
p.Status = cast.ToString(v)
p.Params[loki] = p.Status
p.params[loki] = p.Status
case "sitemap":
p.Sitemap = parseSitemap(cast.ToStringMap(v))
p.Params[loki] = p.Sitemap
p.params[loki] = p.Sitemap
case "iscjklanguage":
isCJKLanguage = new(bool)
*isCJKLanguage = cast.ToBool(v)
case "translationkey":
p.translationKey = cast.ToString(v)
p.Params[loki] = p.translationKey
p.params[loki] = p.translationKey
case "resources":
var resources []map[string]interface{}
handled := true
switch vv := v.(type) {
case []map[interface{}]interface{}:
for _, vvv := range vv {
resources = append(resources, cast.ToStringMap(vvv))
}
case []map[string]interface{}:
for _, vvv := range vv {
resources = append(resources, vvv)
}
case []interface{}:
for _, vvv := range vv {
switch vvvv := vvv.(type) {
case map[interface{}]interface{}:
resources = append(resources, cast.ToStringMap(vvvv))
case map[string]interface{}:
resources = append(resources, vvvv)
}
}
default:
handled = false
}
if handled {
p.params[loki] = resources
p.resourcesMetadata = resources
break
}
fallthrough
default:
// If not one of the explicit values, store in Params
switch vv := v.(type) {
case bool:
p.Params[loki] = vv
p.params[loki] = vv
case string:
p.Params[loki] = vv
p.params[loki] = vv
case int64, int32, int16, int8, int:
p.Params[loki] = vv
p.params[loki] = vv
case float64, float32:
p.Params[loki] = vv
p.params[loki] = vv
case time.Time:
p.Params[loki] = vv
p.params[loki] = vv
default: // handle array of strings as well
switch vvv := vv.(type) {
case []interface{}:
if len(vvv) > 0 {
switch vvv[0].(type) {
case map[interface{}]interface{}: // Proper parsing structured array from YAML based FrontMatter
p.Params[loki] = vvv
p.params[loki] = vvv
case map[string]interface{}: // Proper parsing structured array from JSON based FrontMatter
p.Params[loki] = vvv
p.params[loki] = vvv
case []interface{}:
p.Params[loki] = vvv
p.params[loki] = vvv
default:
a := make([]string, len(vvv))
for i, u := range vvv {
a[i] = cast.ToString(u)
}
p.Params[loki] = a
p.params[loki] = a
}
} else {
p.Params[loki] = []string{}
p.params[loki] = []string{}
}
default:
p.Params[loki] = vv
p.params[loki] = vv
}
}
}
@ -1263,7 +1315,7 @@ func (p *Page) update(f interface{}) error {
} else if published != nil {
p.Draft = !*published
}
p.Params["draft"] = p.Draft
p.params["draft"] = p.Draft
if p.Date.IsZero() {
p.Date = p.PublishDate
@ -1277,7 +1329,7 @@ func (p *Page) update(f interface{}) error {
fi, err := p.s.Fs.Source.Stat(filepath.Join(p.s.PathSpec.AbsPathify(p.s.Cfg.GetString("contentDir")), p.File.Path()))
if err == nil {
p.Date = fi.ModTime()
p.Params["date"] = p.Date
p.params["date"] = p.Date
}
}
@ -1289,9 +1341,9 @@ func (p *Page) update(f interface{}) error {
}
}
p.Params["lastmod"] = p.Lastmod
p.Params["publishdate"] = p.PublishDate
p.Params["expirydate"] = p.ExpiryDate
p.params["lastmod"] = p.Lastmod
p.params["publishdate"] = p.PublishDate
p.params["expirydate"] = p.ExpiryDate
if isCJKLanguage != nil {
p.isCJKLanguage = *isCJKLanguage
@ -1302,7 +1354,7 @@ func (p *Page) update(f interface{}) error {
p.isCJKLanguage = false
}
}
p.Params["iscjklanguage"] = p.isCJKLanguage
p.params["iscjklanguage"] = p.isCJKLanguage
return nil
@ -1317,7 +1369,7 @@ func (p *Page) getParamToLower(key string) interface{} {
}
func (p *Page) getParam(key string, stringToLower bool) interface{} {
v := p.Params[strings.ToLower(key)]
v := p.params[strings.ToLower(key)]
if v == nil {
return nil
@ -1390,7 +1442,7 @@ func (p *Page) HasMenuCurrent(menuID string, me *MenuEntry) bool {
// The following logic is kept from back when Hugo had both Page and Node types.
// TODO(bep) consolidate / clean
nme := MenuEntry{Page: p, Name: p.Title, URL: p.URL()}
nme := MenuEntry{Page: p, Name: p.title, URL: p.URL()}
for _, child := range me.Children {
if nme.IsSameResource(child) {
@ -1421,7 +1473,7 @@ func (p *Page) IsMenuCurrent(menuID string, inme *MenuEntry) bool {
// The following logic is kept from back when Hugo had both Page and Node types.
// TODO(bep) consolidate / clean
me := MenuEntry{Page: p, Name: p.Title, URL: p.URL()}
me := MenuEntry{Page: p, Name: p.title, URL: p.URL()}
if !me.IsSameResource(inme) {
return false
@ -1465,7 +1517,7 @@ func (p *Page) Menus() PageMenus {
p.pageMenusInit.Do(func() {
p.pageMenus = PageMenus{}
if ms, ok := p.Params["menu"]; ok {
if ms, ok := p.params["menu"]; ok {
link := p.RelPermalink()
me := MenuEntry{Page: p, Name: p.LinkTitle(), Weight: p.Weight, URL: link}
@ -1494,16 +1546,16 @@ func (p *Page) Menus() PageMenus {
menus, err := cast.ToStringMapE(ms)
if err != nil {
p.s.Log.ERROR.Printf("unable to process menus for %q\n", p.Title)
p.s.Log.ERROR.Printf("unable to process menus for %q\n", p.title)
}
for name, menu := range menus {
menuEntry := MenuEntry{Page: p, Name: p.LinkTitle(), URL: link, Weight: p.Weight, Menu: name}
if menu != nil {
p.s.Log.DEBUG.Printf("found menu: %q, in %q\n", name, p.Title)
p.s.Log.DEBUG.Printf("found menu: %q, in %q\n", name, p.title)
ime, err := cast.ToStringMapE(menu)
if err != nil {
p.s.Log.ERROR.Printf("unable to process menus for %q: %s", p.Title, err)
p.s.Log.ERROR.Printf("unable to process menus for %q: %s", p.title, err)
}
menuEntry.marshallMap(ime)
@ -1805,7 +1857,7 @@ func (p *Page) RelRef(refs ...string) (string, error) {
}
func (p *Page) String() string {
return fmt.Sprintf("Page(%q)", p.Title)
return fmt.Sprintf("Page(%q)", p.title)
}
type URLPath struct {
@ -2003,5 +2055,5 @@ func (p *Page) pathOrTitle() string {
if p.Path() != "" {
return p.Path()
}
return p.Title
return p.title
}

View file

@ -49,8 +49,8 @@ func preparePageGroupTestPages(t *testing.T) Pages {
p.Date = cast.ToTime(src.date)
p.PublishDate = cast.ToTime(src.date)
p.ExpiryDate = cast.ToTime(src.date)
p.Params["custom_param"] = src.param
p.Params["custom_date"] = cast.ToTime(src.date)
p.params["custom_param"] = src.param
p.params["custom_date"] = cast.ToTime(src.date)
pages = append(pages, p)
}
return pages
@ -253,7 +253,7 @@ func TestGroupByParamCalledWithCapitalLetterString(t *testing.T) {
if err != nil {
t.Fatalf("failed to prepare test page %s", f)
}
p.Params["custom_param"] = testStr
p.params["custom_param"] = testStr
pages := Pages{p}
groups, err := pages.GroupByParam("custom_param")
@ -268,9 +268,9 @@ func TestGroupByParamCalledWithCapitalLetterString(t *testing.T) {
func TestGroupByParamCalledWithSomeUnavailableParams(t *testing.T) {
t.Parallel()
pages := preparePageGroupTestPages(t)
delete(pages[1].Params, "custom_param")
delete(pages[3].Params, "custom_param")
delete(pages[4].Params, "custom_param")
delete(pages[1].params, "custom_param")
delete(pages[3].params, "custom_param")
delete(pages[4].params, "custom_param")
expect := PagesGroup{
{Key: "foo", Pages: Pages{pages[0], pages[2]}},

View file

@ -129,7 +129,7 @@ func (p Pages) ByTitle() Pages {
key := "pageSort.ByTitle"
title := func(p1, p2 *Page) bool {
return p1.Title < p2.Title
return p1.title < p2.title
}
pages, _ := spc.get(key, p, pageBy(title).Sort)

View file

@ -74,7 +74,7 @@ func TestSortByN(t *testing.T) {
assertFunc func(p Pages) bool
}{
{(Pages).ByWeight, func(p Pages) bool { return p[0].Weight == 1 }},
{(Pages).ByTitle, func(p Pages) bool { return p[0].Title == "ab" }},
{(Pages).ByTitle, func(p Pages) bool { return p[0].title == "ab" }},
{(Pages).ByLinkTitle, func(p Pages) bool { return p[0].LinkTitle() == "abl" }},
{(Pages).ByDate, func(p Pages) bool { return p[0].Date == d4 }},
{(Pages).ByPublishDate, func(p Pages) bool { return p[0].PublishDate == d4 }},
@ -124,7 +124,7 @@ func TestPageSortByParam(t *testing.T) {
s := newTestSite(t)
unsorted := createSortTestPages(s, 10)
delete(unsorted[9].Params, "arbitrarily")
delete(unsorted[9].params, "arbitrarily")
firstSetValue, _ := unsorted[0].Param(k)
secondSetValue, _ := unsorted[1].Param(k)
@ -163,9 +163,9 @@ func setSortVals(dates [4]time.Time, titles [4]string, weights [4]int, pages Pag
pages[i].Date = dates[i]
pages[i].Lastmod = dates[i]
pages[i].Weight = weights[i]
pages[i].Title = titles[i]
pages[i].title = titles[i]
// make sure we compare apples and ... apples ...
pages[len(dates)-1-i].linkTitle = pages[i].Title + "l"
pages[len(dates)-1-i].linkTitle = pages[i].title + "l"
pages[len(dates)-1-i].PublishDate = dates[i]
pages[len(dates)-1-i].ExpiryDate = dates[i]
pages[len(dates)-1-i].Content = template.HTML(titles[i] + "_content")
@ -180,7 +180,7 @@ func createSortTestPages(s *Site, num int) Pages {
for i := 0; i < num; i++ {
p := s.newPage(filepath.FromSlash(fmt.Sprintf("/x/y/p%d.md", i)))
p.Params = map[string]interface{}{
p.params = map[string]interface{}{
"arbitrarily": map[string]interface{}{
"nested": ("xyz" + fmt.Sprintf("%v", 100-i)),
},

View file

@ -254,6 +254,12 @@ func (c *contentHandlers) parsePage(h contentHandler) contentHandler {
return p.Resources[i].RelPermalink() < p.Resources[j].RelPermalink()
})
// Assign metadata from front matter if set
if len(p.resourcesMetadata) > 0 {
resource.AssignMetadata(p.resourcesMetadata, p.Resources...)
}
}
return h(ctx)

View file

@ -136,8 +136,14 @@ func TestPageBundlerSite(t *testing.T) {
"TheContent",
"Sunset RelPermalink: /2017/pageslug/sunset1.jpg",
"Thumb Width: 123",
"Thumb Name: my-sunset-1",
"Short Sunset RelPermalink: /2017/pageslug/sunset2.jpg",
"Short Thumb Width: 56",
"1: Image Title: Sunset Galore 1",
"1: Image Params: map[myparam:My Sunny Param]",
"2: Image Title: Sunset Galore 2",
"2: Image Params: map[myparam:My Sunny Param]",
"1: Image myParam: Lower: My Sunny Param Caps: My Sunny Param",
)
th.assertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug.html"), "TheContent")
@ -205,10 +211,16 @@ date: 2017-10-09
TheContent.
`
pageWithImageShortcodeContent := `---
pageWithImageShortcodeAndResourceMetadataContent := `---
title: "Bundle Galore"
slug: pageslug
date: 2017-10-09
resources:
- src: "*.jpg"
name: "my-sunset-:counter"
title: "Sunset Galore :counter"
params:
myParam: "My Sunny Param"
---
TheContent.
@ -227,17 +239,25 @@ TheContent.
singleLayout := `
Title: {{ .Title }}
Content: {{ .Content }}
{{ $sunset := .Resources.GetByPrefix "sunset1" }}
{{ $sunset := .Resources.GetByPrefix "my-sunset-1" }}
{{ with $sunset }}
Sunset RelPermalink: {{ .RelPermalink }}
{{ $thumb := .Fill "123x123" }}
Thumb Width: {{ $thumb.Width }}
Thumb Name: {{ $thumb.Name }}
Thumb Title: {{ $thumb.Title }}
Thumb RelPermalink: {{ $thumb.RelPermalink }}
{{ end }}
{{ range $i, $e := .Resources.ByType "image" }}
{{ $i }}: Image Title: {{ .Title }}
{{ $i }}: Image Name: {{ .Name }}
{{ $i }}: Image Params: {{ printf "%v" .Params }}
{{ $i }}: Image myParam: Lower: {{ .Params.myparam }} Caps: {{ .Params.MYPARAM }}
{{ end }}
`
myShort := `
{{ $sunset := .Page.Resources.GetByPrefix "sunset2" }}
{{ $sunset := .Page.Resources.GetByPrefix "my-sunset-2" }}
{{ with $sunset }}
Short Sunset RelPermalink: {{ .RelPermalink }}
{{ $thumb := .Fill "56x56" }}
@ -268,7 +288,7 @@ Short Thumb Width: {{ $thumb.Width }}
writeSource(t, fs, filepath.Join(workDir, "base", "assets", "pages", "mypage.md"), pageContent)
// Bundle
writeSource(t, fs, filepath.Join(workDir, "base", "b", "index.md"), pageWithImageShortcodeContent)
writeSource(t, fs, filepath.Join(workDir, "base", "b", "index.md"), pageWithImageShortcodeAndResourceMetadataContent)
writeSource(t, fs, filepath.Join(workDir, "base", "b", "1.md"), pageContent)
writeSource(t, fs, filepath.Join(workDir, "base", "b", "2.md"), pageContent)
writeSource(t, fs, filepath.Join(workDir, "base", "b", "custom-mime.bep"), "bepsays")

View file

@ -134,7 +134,7 @@ func TestGetPage(t *testing.T) {
page := s.getPage(test.kind, test.path...)
assert.NotNil(page, errorMsg)
assert.Equal(test.kind, page.Kind, errorMsg)
assert.Equal(test.expectedTitle, page.Title)
assert.Equal(test.expectedTitle, page.title)
}
}

View file

@ -74,7 +74,7 @@ type targetPathDescriptor struct {
// and URLs for this Page.
func (p *Page) createTargetPathDescriptor(t output.Format) (targetPathDescriptor, error) {
if p.targetPathDescriptorPrototype == nil {
panic(fmt.Sprintf("Must run initTargetPathDescriptor() for page %q, kind %q", p.Title, p.Kind))
panic(fmt.Sprintf("Must run initTargetPathDescriptor() for page %q, kind %q", p.title, p.Kind))
}
d := *p.targetPathDescriptorPrototype
d.Type = t
@ -271,9 +271,9 @@ func (p *Page) createRelativeTargetPath() string {
if len(p.outputFormats) == 0 {
if p.Kind == kindUnknown {
panic(fmt.Sprintf("Page %q has unknown kind", p.Title))
panic(fmt.Sprintf("Page %q has unknown kind", p.title))
}
panic(fmt.Sprintf("Page %q missing output format(s)", p.Title))
panic(fmt.Sprintf("Page %q missing output format(s)", p.title))
}
// Choose the main output format. In most cases, this will be HTML.

View file

@ -468,8 +468,8 @@ func TestDegenerateEmptyPage(t *testing.T) {
}
func checkPageTitle(t *testing.T, page *Page, title string) {
if page.Title != title {
t.Fatalf("Page title is: %s. Expected %s", page.Title, title)
if page.title != title {
t.Fatalf("Page title is: %s. Expected %s", page.title, title)
}
}
@ -1066,8 +1066,8 @@ func TestCalendarParamsVariants(t *testing.T) {
pageTOML, _ := s.NewPage("test/fileTOML.md")
_, _ = pageTOML.ReadFrom(strings.NewReader(pageWithCalendarTOMLFrontmatter))
assert.True(t, compareObjects(pageJSON.Params, pageYAML.Params))
assert.True(t, compareObjects(pageJSON.Params, pageTOML.Params))
assert.True(t, compareObjects(pageJSON.params, pageYAML.params))
assert.True(t, compareObjects(pageJSON.params, pageTOML.params))
}
@ -1095,10 +1095,10 @@ func TestDifferentFrontMatterVarTypes(t *testing.T) {
}
param := page.getParamToLower("a_table")
if param == nil {
t.Errorf("frontmatter not handling tables correctly should be type of %v, got: type of %v", reflect.TypeOf(page.Params["a_table"]), reflect.TypeOf(param))
t.Errorf("frontmatter not handling tables correctly should be type of %v, got: type of %v", reflect.TypeOf(page.params["a_table"]), reflect.TypeOf(param))
}
if cast.ToStringMap(param)["a_key"] != "a_value" {
t.Errorf("frontmatter not handling values inside a table correctly should be %s, got: %s", "a_value", cast.ToStringMap(page.Params["a_table"])["a_key"])
t.Errorf("frontmatter not handling values inside a table correctly should be %s, got: %s", "a_value", cast.ToStringMap(page.params["a_table"])["a_key"])
}
}
@ -1370,7 +1370,7 @@ func TestPageParams(t *testing.T) {
p, err := s.NewPageFrom(strings.NewReader(c), "content/post/params.md")
require.NoError(t, err, "err during parse", "#%d", i)
for key := range wantedMap {
assert.Equal(t, wantedMap[key], p.Params[key], "#%d", key)
assert.Equal(t, wantedMap[key], p.params[key], "#%d", key)
}
}
}

View file

@ -54,22 +54,22 @@ Content
assert.NoError(err)
assert.Len(result, 2)
assert.Equal("Page 2", result[0].Title)
assert.Equal("Page 1", result[1].Title)
assert.Equal("Page 2", result[0].title)
assert.Equal("Page 1", result[1].title)
result, err = s.RegularPages.Related(s.RegularPages[0])
assert.Len(result, 2)
assert.Equal("Page 2", result[0].Title)
assert.Equal("Page 3", result[1].Title)
assert.Equal("Page 2", result[0].title)
assert.Equal("Page 3", result[1].title)
result, err = s.RegularPages.RelatedIndices(s.RegularPages[0], "keywords")
assert.Len(result, 2)
assert.Equal("Page 2", result[0].Title)
assert.Equal("Page 3", result[1].Title)
assert.Equal("Page 2", result[0].title)
assert.Equal("Page 3", result[1].title)
result, err = s.RegularPages.RelatedTo(types.NewKeyValuesStrings("keywords", "bep", "rocks"))
assert.NoError(err)
assert.Len(result, 2)
assert.Equal("Page 2", result[0].Title)
assert.Equal("Page 3", result[1].Title)
assert.Equal("Page 2", result[0].title)
assert.Equal("Page 3", result[1].title)
}

View file

@ -270,7 +270,7 @@ func (p *Page) Paginator(options ...interface{}) (*Pager, error) {
// If it's not, one will be created with all pages in Data["Pages"].
func (p *PageOutput) Paginator(options ...interface{}) (*Pager, error) {
if !p.IsNode() {
return nil, fmt.Errorf("Paginators not supported for pages of type %q (%q)", p.Kind, p.Title)
return nil, fmt.Errorf("Paginators not supported for pages of type %q (%q)", p.Kind, p.title)
}
pagerSize, err := resolvePagerSize(p.s.Cfg, options...)
@ -321,7 +321,7 @@ func (p *Page) Paginate(seq interface{}, options ...interface{}) (*Pager, error)
// Note that repeated calls will return the same result, even if the sequence is different.
func (p *PageOutput) Paginate(seq interface{}, options ...interface{}) (*Pager, error) {
if !p.IsNode() {
return nil, fmt.Errorf("Paginators not supported for pages of type %q (%q)", p.Kind, p.Title)
return nil, fmt.Errorf("Paginators not supported for pages of type %q (%q)", p.Kind, p.title)
}
pagerSize, err := resolvePagerSize(p.s.Cfg, options...)

View file

@ -154,7 +154,7 @@ func pageToPermalinkDate(p *Page, dateField string) (string, error) {
func pageToPermalinkTitle(p *Page, _ string) (string, error) {
// Page contains Node which has Title
// (also contains URLPath which has Slug, sometimes)
return p.s.PathSpec.URLize(p.Title), nil
return p.s.PathSpec.URLize(p.title), nil
}
// pageToPermalinkFilename returns the URL-safe form of the filename

View file

@ -1877,7 +1877,7 @@ func (s *Site) newNodePage(typ string, sections ...string) *Page {
func (s *Site) newHomePage() *Page {
p := s.newNodePage(KindHome)
p.Title = s.Info.Title
p.title = s.Info.Title
pages := Pages{}
p.Data["Pages"] = pages
p.Pages = pages
@ -1892,10 +1892,10 @@ func (s *Site) newTaxonomyPage(plural, key string) *Page {
// Keep (mostly) as is in the title
// We make the first character upper case, mostly because
// it is easier to reason about in the tests.
p.Title = helpers.FirstUpper(key)
p.title = helpers.FirstUpper(key)
key = s.PathSpec.MakePathSanitized(key)
} else {
p.Title = strings.Replace(s.titleFunc(key), "-", " ", -1)
p.title = strings.Replace(s.titleFunc(key), "-", " ", -1)
}
return p
@ -1906,15 +1906,15 @@ func (s *Site) newSectionPage(name string) *Page {
sectionName := helpers.FirstUpper(name)
if s.Cfg.GetBool("pluralizeListTitles") {
p.Title = inflect.Pluralize(sectionName)
p.title = inflect.Pluralize(sectionName)
} else {
p.Title = sectionName
p.title = sectionName
}
return p
}
func (s *Site) newTaxonomyTermsPage(plural string) *Page {
p := s.newNodePage(KindTaxonomyTerm, plural)
p.Title = s.titleFunc(plural)
p.title = s.titleFunc(plural)
return p
}

View file

@ -212,7 +212,7 @@ func (s *Site) renderPaginator(p *PageOutput) error {
if err := s.renderAndWritePage(
&s.PathSpec.ProcessingStats.PaginatorPages,
pagerNode.Title,
pagerNode.title,
targetPath, pagerNode, layouts...); err != nil {
return err
}
@ -252,7 +252,7 @@ func (s *Site) renderRSS(p *PageOutput) error {
return err
}
return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Pages, p.Title,
return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Pages, p.title,
targetPath, p, layouts...)
}
@ -267,7 +267,7 @@ func (s *Site) render404() error {
p := s.newNodePage(kind404)
p.Title = "404 Page not found"
p.title = "404 Page not found"
p.Data["Pages"] = s.Pages
p.Pages = s.Pages
p.URLPath.URL = "404.html"

View file

@ -143,13 +143,13 @@ PAG|{{ .Title }}|{{ $sect.InSection . }}
// > b,c,d where b and d have content files.
b := p.s.getPage(KindSection, "empty2", "b")
assert.NotNil(b)
assert.Equal("T40_-1", b.Title)
assert.Equal("T40_-1", b.title)
c := p.s.getPage(KindSection, "empty2", "b", "c")
assert.NotNil(c)
assert.Equal("Cs", c.Title)
assert.Equal("Cs", c.title)
d := p.s.getPage(KindSection, "empty2", "b", "c", "d")
assert.NotNil(d)
assert.Equal("T41_-1", d.Title)
assert.Equal("T41_-1", d.title)
assert.False(c.Eq(d))
assert.True(c.Eq(c))
@ -165,7 +165,7 @@ PAG|{{ .Title }}|{{ $sect.InSection . }}
}},
{"top", func(p *Page) {
assert.Equal("Tops", p.Title)
assert.Equal("Tops", p.title)
assert.Len(p.Pages, 2)
assert.Equal("mypage2.md", p.Pages[0].LogicalName())
assert.Equal("mypage3.md", p.Pages[1].LogicalName())
@ -178,16 +178,16 @@ PAG|{{ .Title }}|{{ $sect.InSection . }}
assert.True(active)
}},
{"l1", func(p *Page) {
assert.Equal("L1s", p.Title)
assert.Equal("L1s", p.title)
assert.Len(p.Pages, 2)
assert.True(p.Parent().IsHome())
assert.Len(p.Sections(), 2)
}},
{"l1,l2", func(p *Page) {
assert.Equal("T2_-1", p.Title)
assert.Equal("T2_-1", p.title)
assert.Len(p.Pages, 3)
assert.Equal(p, p.Pages[0].Parent())
assert.Equal("L1s", p.Parent().Title)
assert.Equal("L1s", p.Parent().title)
assert.Equal("/l1/l2/", p.URLPath.URL)
assert.Equal("/l1/l2/", p.RelPermalink())
assert.Len(p.Sections(), 1)
@ -223,16 +223,16 @@ PAG|{{ .Title }}|{{ $sect.InSection . }}
}},
{"l1,l2_2", func(p *Page) {
assert.Equal("T22_-1", p.Title)
assert.Equal("T22_-1", p.title)
assert.Len(p.Pages, 2)
assert.Equal(filepath.FromSlash("l1/l2_2/page_2_2_1.md"), p.Pages[0].Path())
assert.Equal("L1s", p.Parent().Title)
assert.Equal("L1s", p.Parent().title)
assert.Len(p.Sections(), 0)
}},
{"l1,l2,l3", func(p *Page) {
assert.Equal("T3_-1", p.Title)
assert.Equal("T3_-1", p.title)
assert.Len(p.Pages, 2)
assert.Equal("T2_-1", p.Parent().Title)
assert.Equal("T2_-1", p.Parent().title)
assert.Len(p.Sections(), 0)
l1 := p.s.getPage(KindSection, "l1")
@ -252,7 +252,7 @@ PAG|{{ .Title }}|{{ $sect.InSection . }}
}},
{"perm a,link", func(p *Page) {
assert.Equal("T9_-1", p.Title)
assert.Equal("T9_-1", p.title)
assert.Equal("/perm-a/link/", p.RelPermalink())
assert.Len(p.Pages, 4)
first := p.Pages[0]

View file

@ -160,7 +160,7 @@ func TestFutureExpirationRender(t *testing.T) {
}
}
if s.AllPages[0].Title == "doc2" {
if s.AllPages[0].title == "doc2" {
t.Fatal("Expired content published unexpectedly")
}
}
@ -642,40 +642,40 @@ func TestOrderedPages(t *testing.T) {
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
if s.getPage(KindSection, "sect").Pages[1].Title != "Three" || s.getPage(KindSection, "sect").Pages[2].Title != "Four" {
if s.getPage(KindSection, "sect").Pages[1].title != "Three" || s.getPage(KindSection, "sect").Pages[2].title != "Four" {
t.Error("Pages in unexpected order.")
}
bydate := s.RegularPages.ByDate()
if bydate[0].Title != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bydate[0].Title)
if bydate[0].title != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bydate[0].title)
}
rev := bydate.Reverse()
if rev[0].Title != "Three" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Three", rev[0].Title)
if rev[0].title != "Three" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Three", rev[0].title)
}
bypubdate := s.RegularPages.ByPublishDate()
if bypubdate[0].Title != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bypubdate[0].Title)
if bypubdate[0].title != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bypubdate[0].title)
}
rbypubdate := bypubdate.Reverse()
if rbypubdate[0].Title != "Three" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Three", rbypubdate[0].Title)
if rbypubdate[0].title != "Three" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Three", rbypubdate[0].title)
}
bylength := s.RegularPages.ByLength()
if bylength[0].Title != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bylength[0].Title)
if bylength[0].title != "One" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "One", bylength[0].title)
}
rbylength := bylength.Reverse()
if rbylength[0].Title != "Four" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Four", rbylength[0].Title)
if rbylength[0].title != "Four" {
t.Errorf("Pages in unexpected order. First should be '%s', got '%s'", "Four", rbylength[0].title)
}
}
@ -714,8 +714,8 @@ func TestGroupedPages(t *testing.T) {
if rbysection[2].Key != "sect1" {
t.Errorf("PageGroup array in unexpected order. Third group key should be '%s', got '%s'", "sect1", rbysection[2].Key)
}
if rbysection[0].Pages[0].Title != "Four" {
t.Errorf("PageGroup has an unexpected page. First group's pages should have '%s', got '%s'", "Four", rbysection[0].Pages[0].Title)
if rbysection[0].Pages[0].title != "Four" {
t.Errorf("PageGroup has an unexpected page. First group's pages should have '%s', got '%s'", "Four", rbysection[0].Pages[0].title)
}
if len(rbysection[2].Pages) != 2 {
t.Errorf("PageGroup has unexpected number of pages. Third group should have '%d' pages, got '%d' pages", 2, len(rbysection[2].Pages))
@ -734,8 +734,8 @@ func TestGroupedPages(t *testing.T) {
if bytype[2].Key != "sect3" {
t.Errorf("PageGroup array in unexpected order. Third group key should be '%s', got '%s'", "sect3", bytype[2].Key)
}
if bytype[2].Pages[0].Title != "Four" {
t.Errorf("PageGroup has an unexpected page. Third group's data should have '%s', got '%s'", "Four", bytype[0].Pages[0].Title)
if bytype[2].Pages[0].title != "Four" {
t.Errorf("PageGroup has an unexpected page. Third group's data should have '%s', got '%s'", "Four", bytype[0].Pages[0].title)
}
if len(bytype[0].Pages) != 2 {
t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(bytype[2].Pages))
@ -762,8 +762,8 @@ func TestGroupedPages(t *testing.T) {
if bypubdate[1].Key != "0001" {
t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "0001", bypubdate[1].Key)
}
if bypubdate[0].Pages[0].Title != "Three" {
t.Errorf("PageGroup has an unexpected page. Third group's pages should have '%s', got '%s'", "Three", bypubdate[0].Pages[0].Title)
if bypubdate[0].Pages[0].title != "Three" {
t.Errorf("PageGroup has an unexpected page. Third group's pages should have '%s', got '%s'", "Three", bypubdate[0].Pages[0].title)
}
if len(bypubdate[0].Pages) != 3 {
t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 3, len(bypubdate[0].Pages))
@ -782,8 +782,8 @@ func TestGroupedPages(t *testing.T) {
if byparam[2].Key != "bar" {
t.Errorf("PageGroup array in unexpected order. Third group key should be '%s', got '%s'", "bar", byparam[2].Key)
}
if byparam[2].Pages[0].Title != "Three" {
t.Errorf("PageGroup has an unexpected page. Third group's pages should have '%s', got '%s'", "Three", byparam[2].Pages[0].Title)
if byparam[2].Pages[0].title != "Three" {
t.Errorf("PageGroup has an unexpected page. Third group's pages should have '%s', got '%s'", "Three", byparam[2].Pages[0].title)
}
if len(byparam[0].Pages) != 2 {
t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(byparam[0].Pages))
@ -815,8 +815,8 @@ func TestGroupedPages(t *testing.T) {
if byParamDate[1].Key != "1979-05" {
t.Errorf("PageGroup array in unexpected order. Second group key should be '%s', got '%s'", "1979-05", byParamDate[1].Key)
}
if byParamDate[1].Pages[0].Title != "One" {
t.Errorf("PageGroup has an unexpected page. Second group's pages should have '%s', got '%s'", "One", byParamDate[1].Pages[0].Title)
if byParamDate[1].Pages[0].title != "One" {
t.Errorf("PageGroup has an unexpected page. Second group's pages should have '%s', got '%s'", "One", byParamDate[1].Pages[0].title)
}
if len(byParamDate[0].Pages) != 2 {
t.Errorf("PageGroup has unexpected number of pages. First group should have '%d' pages, got '%d' pages", 2, len(byParamDate[2].Pages))
@ -872,16 +872,16 @@ func TestWeightedTaxonomies(t *testing.T) {
writeSourcesToSource(t, "content", fs, sources...)
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
if s.Taxonomies["tags"]["a"][0].Page.Title != "foo" {
t.Errorf("Pages in unexpected order, 'foo' expected first, got '%v'", s.Taxonomies["tags"]["a"][0].Page.Title)
if s.Taxonomies["tags"]["a"][0].Page.title != "foo" {
t.Errorf("Pages in unexpected order, 'foo' expected first, got '%v'", s.Taxonomies["tags"]["a"][0].Page.title)
}
if s.Taxonomies["categories"]["d"][0].Page.Title != "bar" {
t.Errorf("Pages in unexpected order, 'bar' expected first, got '%v'", s.Taxonomies["categories"]["d"][0].Page.Title)
if s.Taxonomies["categories"]["d"][0].Page.title != "bar" {
t.Errorf("Pages in unexpected order, 'bar' expected first, got '%v'", s.Taxonomies["categories"]["d"][0].Page.title)
}
if s.Taxonomies["categories"]["e"][0].Page.Title != "bza" {
t.Errorf("Pages in unexpected order, 'bza' expected first, got '%v'", s.Taxonomies["categories"]["e"][0].Page.Title)
if s.Taxonomies["categories"]["e"][0].Page.title != "bza" {
t.Errorf("Pages in unexpected order, 'bza' expected first, got '%v'", s.Taxonomies["categories"]["e"][0].Page.title)
}
}

View file

@ -43,7 +43,7 @@ type WeightedPage struct {
}
func (w WeightedPage) String() string {
return fmt.Sprintf("WeightedPage(%d,%q)", w.Weight, w.Page.Title)
return fmt.Sprintf("WeightedPage(%d,%q)", w.Weight, w.Page.title)
}
// OrderedTaxonomy is another representation of an Taxonomy using an array rather than a map.
@ -214,7 +214,7 @@ func (wp WeightedPages) Count() int { return len(wp) }
func (wp WeightedPages) Less(i, j int) bool {
if wp[i].Weight == wp[j].Weight {
if wp[i].Page.Date.Equal(wp[j].Page.Date) {
return wp[i].Page.Title < wp[j].Page.Title
return wp[i].Page.title < wp[j].Page.title
}
return wp[i].Page.Date.After(wp[i].Page.Date)
}

View file

@ -200,11 +200,11 @@ permalinkeds:
if preserveTaxonomyNames {
helloWorld := s.getPage(KindTaxonomy, "others", "Hello Hugo world")
require.NotNil(t, helloWorld)
require.Equal(t, "Hello Hugo world", helloWorld.Title)
require.Equal(t, "Hello Hugo world", helloWorld.title)
} else {
helloWorld := s.getPage(KindTaxonomy, "others", "hello-hugo-world")
require.NotNil(t, helloWorld)
require.Equal(t, "Hello Hugo World", helloWorld.Title)
require.Equal(t, "Hello Hugo World", helloWorld.title)
}
// Issue #2977

View file

@ -217,7 +217,7 @@ func dumpPages(pages ...*Page) {
for i, p := range pages {
fmt.Printf("%d: Kind: %s Title: %-10s RelPermalink: %-10s Path: %-10s sections: %s Len Sections(): %d\n",
i+1,
p.Kind, p.Title, p.RelPermalink(), p.Path(), p.sections, len(p.Sections()))
p.Kind, p.title, p.RelPermalink(), p.Path(), p.sections, len(p.Sections()))
}
}

View file

@ -231,6 +231,9 @@ func TestCoverHTML() error {
}
b, err := ioutil.ReadFile(cover)
if err != nil {
if os.IsNotExist(err) {
continue
}
return err
}
idx := bytes.Index(b, []byte{'\n'})

View file

@ -208,7 +208,7 @@ func (i *Image) doWithImageConfig(action, spec string, f func(src image.Image, c
key := i.relTargetPathForRel(i.filenameFromConfig(conf), false)
return i.spec.imageCache.getOrCreate(i.spec, key, func(resourceCacheFilename string) (*Image, error) {
return i.spec.imageCache.getOrCreate(i, key, func(resourceCacheFilename string) (*Image, error) {
ci := i.clone()
ci.setBasePath(conf)

View file

@ -15,7 +15,6 @@ package resource
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
@ -50,7 +49,7 @@ func (c *imageCache) deleteByPrefix(prefix string) {
}
func (c *imageCache) getOrCreate(
spec *Spec, key string, create func(resourceCacheFilename string) (*Image, error)) (*Image, error) {
parent *Image, key string, create func(resourceCacheFilename string) (*Image, error)) (*Image, error) {
relTargetFilename := key
@ -77,19 +76,20 @@ func (c *imageCache) getOrCreate(
// but the count of processed image variations for this site.
c.pathSpec.ProcessingStats.Incr(&c.pathSpec.ProcessingStats.ProcessedImages)
r, err := spec.NewResourceFromFilename(nil, c.absPublishDir, cacheFilename, relTargetFilename)
notFound := err != nil && os.IsNotExist(err)
if err != nil && !os.IsNotExist(err) {
exists, err := helpers.Exists(cacheFilename, c.pathSpec.Fs.Source)
if err != nil {
return nil, err
}
if notFound {
if exists {
img = parent.clone()
img.relTargetPath = relTargetFilename
img.absSourceFilename = cacheFilename
} else {
img, err = create(cacheFilename)
if err != nil {
return nil, err
}
} else {
img = r.(*Image)
}
c.mu.Lock()
@ -102,7 +102,7 @@ func (c *imageCache) getOrCreate(
c.mu.Unlock()
if notFound {
if !exists {
// File already written to destination
return img, nil
}

View file

@ -147,3 +147,25 @@ func TestDecodeImaging(t *testing.T) {
assert.Equal(42, imaging.Quality)
assert.Equal("nearestneighbor", imaging.ResampleFilter)
}
func TestImageWithMetadata(t *testing.T) {
assert := require.New(t)
image := fetchSunset(assert)
var meta = []map[string]interface{}{
map[string]interface{}{
"title": "My Sunset",
"name": "Sunset #:counter",
"src": "*.jpg",
},
}
assert.NoError(AssignMetadata(meta, image))
assert.Equal("Sunset #1", image.Name())
resized, err := image.Resize("200x")
assert.NoError(err)
assert.Equal("Sunset #1", resized.Name())
}

View file

@ -19,8 +19,11 @@ import (
"os"
"path"
"path/filepath"
"strconv"
"strings"
"github.com/spf13/cast"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/source"
@ -29,6 +32,7 @@ import (
var (
_ Resource = (*genericResource)(nil)
_ metaAssigner = (*genericResource)(nil)
_ Source = (*genericResource)(nil)
_ Cloner = (*genericResource)(nil)
)
@ -48,11 +52,38 @@ type Cloner interface {
WithNewBase(base string) Resource
}
type metaAssigner interface {
setTitle(title string)
setName(name string)
setParams(params map[string]interface{})
}
// Resource represents a linkable resource, i.e. a content page, image etc.
type Resource interface {
// Permalink represents the absolute link to this resource.
Permalink() string
// RelPermalink represents the host relative link to this resource.
RelPermalink() string
// ResourceType is the resource type. For most file types, this is the main
// part of the MIME type, e.g. "image", "application", "text" etc.
// For content pages, this value is "page".
ResourceType() string
// Name is the logical name of this resource. This can be set in the front matter
// metadata for this resource. If not set, Hugo will assign a value.
// This will in most cases be the base filename.
// So, for the image "/some/path/sunset.jpg" this will be "sunset.jpg".
// The value returned by this method will be used in the GetByPrefix and ByPrefix methods
// on Resources.
Name() string
// Title returns the title if set in front matter. For content pages, this will be the expected value.
Title() string
// Params set in front matter for this resource.
Params() map[string]interface{}
}
// Resources represents a slice of resources, which can be a mix of different types.
@ -97,16 +128,7 @@ func (r Resources) ByPrefix(prefix string) Resources {
}
func matchesPrefix(r Resource, prefix string) bool {
var name string
f, ok := r.(source.File)
if ok {
name = f.BaseFileName()
} else {
_, name = filepath.Split(r.RelPermalink())
}
name = strings.ToLower(name)
return strings.HasPrefix(name, prefix)
return strings.HasPrefix(strings.ToLower(r.Name()), prefix)
}
type Spec struct {
@ -238,6 +260,10 @@ type genericResource struct {
// Base is set when the output format's path has a offset, e.g. for AMP.
base string
title string
name string
params map[string]interface{}
// Absolute filename to the source, including any content folder path.
absSourceFilename string
absPublishDir string
@ -256,6 +282,30 @@ func (l *genericResource) RelPermalink() string {
return l.relPermalinkForRel(l.relTargetPath, true)
}
func (l *genericResource) Name() string {
return l.name
}
func (l *genericResource) Title() string {
return l.title
}
func (l *genericResource) Params() map[string]interface{} {
return l.params
}
func (l *genericResource) setTitle(title string) {
l.title = title
}
func (l *genericResource) setName(name string) {
l.name = name
}
func (l *genericResource) setParams(params map[string]interface{}) {
l.params = params
}
// Implement the Cloner interface.
func (l genericResource) WithNewBase(base string) Resource {
l.base = base
@ -306,6 +356,98 @@ func (l *genericResource) Publish() error {
return helpers.WriteToDisk(target, f, l.spec.Fs.Destination)
}
// AssignMetadata assigns the given metadata to those resources that supports updates
// and matching by wildcard given in `src` using `filepath.Match` with lower cased values.
// This assignment is additive, but the most specific match needs to be first.
// The `name` and `title` metadata field support shell-matched collection it got a match in.
// See https://golang.org/pkg/path/filepath/#Match
func AssignMetadata(metadata []map[string]interface{}, resources ...Resource) error {
counters := make(map[string]int)
for _, r := range resources {
if _, ok := r.(metaAssigner); !ok {
continue
}
var (
nameSet, titleSet, paramsSet bool
currentCounter = 0
resourceSrcKey = strings.ToLower(r.Name())
)
ma := r.(metaAssigner)
for _, meta := range metadata {
if nameSet && titleSet && paramsSet {
// No need to look further
break
}
src, found := meta["src"]
if !found {
return fmt.Errorf("missing 'src' in metadata for resource")
}
srcKey := strings.ToLower(cast.ToString(src))
match, err := filepath.Match(srcKey, resourceSrcKey)
if err != nil {
return fmt.Errorf("failed to match resource with metadata: %s", err)
}
if match {
if !nameSet {
name, found := meta["name"]
if found {
if currentCounter == 0 {
currentCounter = counters[srcKey] + 1
counters[srcKey] = currentCounter
}
ma.setName(replaceResourcePlaceholders(cast.ToString(name), currentCounter))
nameSet = true
}
}
if !titleSet {
title, found := meta["title"]
if found {
if currentCounter == 0 {
currentCounter = counters[srcKey] + 1
counters[srcKey] = currentCounter
}
ma.setTitle((replaceResourcePlaceholders(cast.ToString(title), currentCounter)))
titleSet = true
}
}
if !paramsSet {
params, found := meta["params"]
if found {
m := cast.ToStringMap(params)
// Needed for case insensitive fetching of params values
helpers.ToLowerMap(m)
ma.setParams(m)
if currentCounter == 0 {
currentCounter = counters[srcKey] + 1
counters[srcKey] = currentCounter
}
paramsSet = true
}
}
}
}
}
return nil
}
func replaceResourcePlaceholders(in string, counter int) string {
return strings.Replace(in, ":counter", strconv.Itoa(counter), -1)
}
func (l *genericResource) target() string {
target := l.relTargetPathForRel(l.relTargetPath, false)
if l.spec.PathSpec.Languages.IsMultihost() {
@ -330,5 +472,8 @@ func (r *Spec) newGenericResource(
relTargetPath: baseFilename,
resourceType: resourceType,
spec: r,
params: make(map[string]interface{}),
name: baseFilename,
title: baseFilename,
}
}

View file

@ -14,6 +14,7 @@
package resource
import (
"fmt"
"path"
"path/filepath"
"testing"
@ -129,4 +130,195 @@ func TestResourcesGetByPrefix(t *testing.T) {
assert.Equal(2, len(resources.ByPrefix("logo")))
assert.Equal(1, len(resources.ByPrefix("logo2")))
logo := resources.GetByPrefix("logo")
assert.NotNil(logo.Params())
assert.Equal("logo1.png", logo.Name())
assert.Equal("logo1.png", logo.Title())
}
func TestAssignMetadata(t *testing.T) {
assert := require.New(t)
spec := newTestResourceSpec(assert)
var foo1, foo2, foo3, logo1, logo2, logo3 Resource
var resources Resources
for _, this := range []struct {
metaData []map[string]interface{}
assertFunc func(err error)
}{
{[]map[string]interface{}{
map[string]interface{}{
"title": "My Resource",
"name": "My Name",
"src": "*",
},
}, func(err error) {
assert.Equal("My Resource", logo1.Title())
assert.Equal("My Name", logo1.Name())
assert.Equal("My Name", foo2.Name())
}},
{[]map[string]interface{}{
map[string]interface{}{
"title": "My Logo",
"src": "*loGo*",
},
map[string]interface{}{
"title": "My Resource",
"name": "My Name",
"src": "*",
},
}, func(err error) {
assert.Equal("My Logo", logo1.Title())
assert.Equal("My Logo", logo2.Title())
assert.Equal("My Name", logo1.Name())
assert.Equal("My Name", foo2.Name())
assert.Equal("My Name", foo3.Name())
assert.Equal("My Resource", foo3.Title())
}},
{[]map[string]interface{}{
map[string]interface{}{
"title": "My Logo",
"src": "*loGo*",
"params": map[string]interface{}{
"Param1": true,
},
},
map[string]interface{}{
"title": "My Resource",
"src": "*",
"params": map[string]interface{}{
"Param2": true,
},
},
}, func(err error) {
assert.NoError(err)
assert.Equal("My Logo", logo1.Title())
assert.Equal("My Resource", foo3.Title())
_, p1 := logo2.Params()["param1"]
_, p2 := foo2.Params()["param2"]
assert.True(p1)
assert.True(p2)
}},
{[]map[string]interface{}{
map[string]interface{}{
"name": "Logo Name #:counter",
"src": "*logo*",
},
map[string]interface{}{
"title": "Resource #:counter",
"name": "Name #:counter",
"src": "*",
},
}, func(err error) {
assert.NoError(err)
assert.Equal("Resource #1", logo2.Title())
assert.Equal("Logo Name #1", logo2.Name())
assert.Equal("Resource #2", logo1.Title())
assert.Equal("Logo Name #2", logo1.Name())
assert.Equal("Resource #1", foo2.Title())
assert.Equal("Resource #2", foo1.Title())
assert.Equal("Name #2", foo1.Name())
assert.Equal("Resource #3", foo3.Title())
assert.Equal(logo2, resources.GetByPrefix("logo name #1"))
}},
{[]map[string]interface{}{
map[string]interface{}{
"title": "Third Logo #:counter",
"src": "logo3.png",
},
map[string]interface{}{
"title": "Other Logo #:counter",
"name": "Name #:counter",
"src": "logo*",
},
}, func(err error) {
assert.NoError(err)
assert.Equal("Third Logo #1", logo3.Title())
assert.Equal("Name #1", logo3.Name())
assert.Equal("Other Logo #1", logo2.Title())
assert.Equal("Name #1", logo2.Name())
assert.Equal("Other Logo #2", logo1.Title())
assert.Equal("Name #2", logo1.Name())
}},
{[]map[string]interface{}{
map[string]interface{}{
"title": "Third Logo #:counter",
},
}, func(err error) {
// Missing src
assert.Error(err)
}},
{[]map[string]interface{}{
map[string]interface{}{
"title": "Title",
"src": "[]",
},
}, func(err error) {
// Invalid pattern
assert.Error(err)
}},
} {
foo2 = spec.newGenericResource(nil, nil, "/public", "/b/foo2.css", "foo2.css", "css")
logo2 = spec.newGenericResource(nil, nil, "/public", "/b/Logo2.png", "Logo2.png", "image")
foo1 = spec.newGenericResource(nil, nil, "/public", "/a/foo1.css", "foo1.css", "css")
logo1 = spec.newGenericResource(nil, nil, "/public", "/a/logo1.png", "logo1.png", "image")
foo3 = spec.newGenericResource(nil, nil, "/public", "/b/foo3.css", "foo3.css", "css")
logo3 = spec.newGenericResource(nil, nil, "/public", "/b/logo3.png", "logo3.png", "image")
resources = Resources{
foo2,
logo2,
foo1,
logo1,
foo3,
logo3,
}
this.assertFunc(AssignMetadata(this.metaData, resources...))
}
}
func BenchmarkAssignMetadata(b *testing.B) {
assert := require.New(b)
spec := newTestResourceSpec(assert)
for i := 0; i < b.N; i++ {
b.StopTimer()
var resources Resources
var meta = []map[string]interface{}{
map[string]interface{}{
"title": "Foo #:counter",
"name": "Foo Name #:counter",
"src": "foo1*",
},
map[string]interface{}{
"title": "Rest #:counter",
"name": "Rest Name #:counter",
"src": "*",
},
}
for i := 0; i < 20; i++ {
name := fmt.Sprintf("foo%d_%d.css", i%5, i)
resources = append(resources, spec.newGenericResource(nil, nil, "/public", "/a/"+name, name, "css"))
}
b.StartTimer()
if err := AssignMetadata(meta, resources...); err != nil {
b.Fatal(err)
}
}
}