mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
Support pages without front matter
* Page without front matter now treated same as a page with empty front matter. * Test cases added to cover this and repro issue #4320. * Type safety of front matter code improved. Fixes #4320
This commit is contained in:
parent
3f0379adb7
commit
91bb774ae4
8 changed files with 56 additions and 42 deletions
|
@ -1231,9 +1231,7 @@ func (c *commandeer) isThemeVsHugoVersionMismatch() (mismatch bool, requiredMinV
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config := tomlMeta.(map[string]interface{})
|
if minVersion, ok := tomlMeta["min_version"]; ok {
|
||||||
|
|
||||||
if minVersion, ok := config["min_version"]; ok {
|
|
||||||
return helpers.CompareVersion(minVersion) > 0, fmt.Sprint(minVersion)
|
return helpers.CompareVersion(minVersion) > 0, fmt.Sprint(minVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -255,7 +255,7 @@ func loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string]interface{} {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.(map[string]interface{})
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func createConfigFromJekyll(fs afero.Fs, inpath string, kind string, jekyllConfig map[string]interface{}) (err error) {
|
func createConfigFromJekyll(fs afero.Fs, inpath string, kind string, jekyllConfig map[string]interface{}) (err error) {
|
||||||
|
|
|
@ -99,7 +99,7 @@ func undraftContent(p parser.Page) (bytes.Buffer, error) {
|
||||||
var isDraft, gotDate bool
|
var isDraft, gotDate bool
|
||||||
var date string
|
var date string
|
||||||
L:
|
L:
|
||||||
for k, v := range meta.(map[string]interface{}) {
|
for k, v := range meta {
|
||||||
switch k {
|
switch k {
|
||||||
case "draft":
|
case "draft":
|
||||||
if !v.(bool) {
|
if !v.(bool) {
|
||||||
|
|
|
@ -69,7 +69,7 @@ func TestUndraftContent(t *testing.T) {
|
||||||
t.Errorf("[%d] unexpected error %q", i, err)
|
t.Errorf("[%d] unexpected error %q", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for k, v := range meta.(map[string]interface{}) {
|
for k, v := range meta {
|
||||||
if k == "draft" {
|
if k == "draft" {
|
||||||
if v.(bool) {
|
if v.(bool) {
|
||||||
t.Errorf("[%d] Expected %q to be \"false\", got \"true\"", i, k)
|
t.Errorf("[%d] Expected %q to be \"false\", got \"true\"", i, k)
|
||||||
|
|
|
@ -1108,19 +1108,18 @@ func (p *Page) prepareForRender(cfg *BuildCfg) error {
|
||||||
|
|
||||||
var ErrHasDraftAndPublished = errors.New("both draft and published parameters were found in page's frontmatter")
|
var ErrHasDraftAndPublished = errors.New("both draft and published parameters were found in page's frontmatter")
|
||||||
|
|
||||||
func (p *Page) update(f interface{}) error {
|
func (p *Page) update(frontmatter map[string]interface{}) error {
|
||||||
if f == nil {
|
if frontmatter == nil {
|
||||||
return errors.New("no metadata found")
|
return errors.New("missing frontmatter data")
|
||||||
}
|
}
|
||||||
m := f.(map[string]interface{})
|
|
||||||
// Needed for case insensitive fetching of params values
|
// Needed for case insensitive fetching of params values
|
||||||
helpers.ToLowerMap(m)
|
helpers.ToLowerMap(frontmatter)
|
||||||
|
|
||||||
var modified time.Time
|
var modified time.Time
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var draft, published, isCJKLanguage *bool
|
var draft, published, isCJKLanguage *bool
|
||||||
for k, v := range m {
|
for k, v := range frontmatter {
|
||||||
loki := strings.ToLower(k)
|
loki := strings.ToLower(k)
|
||||||
switch loki {
|
switch loki {
|
||||||
case "title":
|
case "title":
|
||||||
|
@ -1371,7 +1370,6 @@ func (p *Page) update(f interface{}) error {
|
||||||
p.params["iscjklanguage"] = p.isCJKLanguage
|
p.params["iscjklanguage"] = p.isCJKLanguage
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Page) GetParam(key string) interface{} {
|
func (p *Page) GetParam(key string) interface{} {
|
||||||
|
@ -1614,14 +1612,12 @@ func (p *Page) parse(reader io.Reader) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to parse page metadata for %q: %s", p.File.Path(), err)
|
return fmt.Errorf("failed to parse page metadata for %q: %s", p.File.Path(), err)
|
||||||
}
|
}
|
||||||
|
if meta == nil {
|
||||||
if meta != nil {
|
// missing frontmatter equivalent to empty frontmatter
|
||||||
if err = p.update(meta); err != nil {
|
meta = map[string]interface{}{}
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return p.update(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Page) RawContent() string {
|
func (p *Page) RawContent() string {
|
||||||
|
|
|
@ -912,8 +912,8 @@ const (
|
||||||
L = "2017-09-03T22:22:22Z"
|
L = "2017-09-03T22:22:22Z"
|
||||||
M = "2018-01-24T12:21:39Z"
|
M = "2018-01-24T12:21:39Z"
|
||||||
E = "2025-12-31T23:59:59Z"
|
E = "2025-12-31T23:59:59Z"
|
||||||
o = "0001-01-01T00:00:00Z"
|
o = "0001-01-01T00:00:00Z" // zero value of type Time, default for some date fields
|
||||||
x = ""
|
x = "" // nil date value, default for some date fields
|
||||||
|
|
||||||
p_D____ = `---
|
p_D____ = `---
|
||||||
title: Simple
|
title: Simple
|
||||||
|
@ -981,20 +981,41 @@ Page With Date, PublishDate and LastMod`
|
||||||
|
|
||||||
---
|
---
|
||||||
Page With empty front matter`
|
Page With empty front matter`
|
||||||
|
|
||||||
|
zero_FM = "Page With empty front matter"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMetadataDates(t *testing.T) {
|
func TestMetadataDates(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
text string
|
text string
|
||||||
filename string
|
filename string
|
||||||
fallback bool
|
modFallback bool
|
||||||
expDate string
|
expDate string
|
||||||
expPub string
|
expPub string
|
||||||
expLast string
|
expLast string
|
||||||
expMod string
|
expMod string
|
||||||
expExp string
|
expExp string
|
||||||
}{ // D P L M E
|
}{
|
||||||
|
// The three columns on the left are the test case inputs:
|
||||||
|
// page content: The name indicates which dates are set in the front matter,
|
||||||
|
// (D)ate, (P)ublishDate, (L)astModified
|
||||||
|
// (M)odified, (E)xpiryDate. So, for example,
|
||||||
|
// p__PL__ is content with PublishDate and LastModified
|
||||||
|
// specified in the front matter.
|
||||||
|
// file path: For when we start deriving metadata from it
|
||||||
|
// modFallback: Whether or not useModTimeAsFallback is enabled.
|
||||||
|
//
|
||||||
|
// The single character columns on the right are the expected outputs
|
||||||
|
// for each metadata date given by the column heading.
|
||||||
|
// Since each date type (D/P/L/M/E) in the input is always set
|
||||||
|
// to the same value (the constants referenced in these columns), it
|
||||||
|
// is easy to visualize and test which input date gets copied to which
|
||||||
|
// output date fields. "s" signifies the file's filesystem time stamp,
|
||||||
|
// "x" signifies a nil value, and "o" the "zero date".
|
||||||
|
//
|
||||||
|
// ------- inputs --------|--- outputs ---|
|
||||||
|
//content filename modfb? D P L M E
|
||||||
{p_D____, "test.md", false, D, D, D, x, x}, // date copied across
|
{p_D____, "test.md", false, D, D, D, x, x}, // date copied across
|
||||||
{p_D____, "testy.md", true, D, D, D, x, x},
|
{p_D____, "testy.md", true, D, D, D, x, x},
|
||||||
{p__P___, "test.md", false, P, P, P, x, x}, // pubdate copied across
|
{p__P___, "test.md", false, P, P, P, x, x}, // pubdate copied across
|
||||||
|
@ -1010,12 +1031,14 @@ func TestMetadataDates(t *testing.T) {
|
||||||
{p_DPLME, "testy.md", true, D, P, L, M, E}, // all dates
|
{p_DPLME, "testy.md", true, D, P, L, M, E}, // all dates
|
||||||
|
|
||||||
{emptyFM, "test.md", false, o, o, o, x, x}, // 3 year-one dates, 2 empty dates
|
{emptyFM, "test.md", false, o, o, o, x, x}, // 3 year-one dates, 2 empty dates
|
||||||
|
{zero_FM, "test.md", false, o, o, o, x, x},
|
||||||
{emptyFM, "testy.md", true, s, o, s, x, x}, // 2 filesys, 1 year-one, 2 empty
|
{emptyFM, "testy.md", true, s, o, s, x, x}, // 2 filesys, 1 year-one, 2 empty
|
||||||
|
{zero_FM, "testy.md", true, s, o, s, x, x}, // Issue #4320
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
s := newTestSite(t)
|
s := newTestSite(t)
|
||||||
s.Cfg.Set("useModTimeAsFallback", test.fallback)
|
s.Cfg.Set("useModTimeAsFallback", test.modFallback)
|
||||||
fs := hugofs.NewMem(s.Cfg)
|
fs := hugofs.NewMem(s.Cfg)
|
||||||
|
|
||||||
writeToFs(t, fs.Source, test.filename, test.text)
|
writeToFs(t, fs.Source, test.filename, test.text)
|
||||||
|
|
|
@ -31,7 +31,7 @@ import (
|
||||||
// FrontmatterType represents a type of frontmatter.
|
// FrontmatterType represents a type of frontmatter.
|
||||||
type FrontmatterType struct {
|
type FrontmatterType struct {
|
||||||
// Parse decodes content into a Go interface.
|
// Parse decodes content into a Go interface.
|
||||||
Parse func([]byte) (interface{}, error)
|
Parse func([]byte) (map[string]interface{}, error)
|
||||||
|
|
||||||
markstart, markend []byte // starting and ending delimiters
|
markstart, markend []byte // starting and ending delimiters
|
||||||
includeMark bool // include start and end mark in output
|
includeMark bool // include start and end mark in output
|
||||||
|
@ -168,7 +168,7 @@ func DetectFrontMatter(mark rune) (f *FrontmatterType) {
|
||||||
|
|
||||||
// HandleTOMLMetaData unmarshals TOML-encoded datum and returns a Go interface
|
// HandleTOMLMetaData unmarshals TOML-encoded datum and returns a Go interface
|
||||||
// representing the encoded data structure.
|
// representing the encoded data structure.
|
||||||
func HandleTOMLMetaData(datum []byte) (interface{}, error) {
|
func HandleTOMLMetaData(datum []byte) (map[string]interface{}, error) {
|
||||||
m := map[string]interface{}{}
|
m := map[string]interface{}{}
|
||||||
datum = removeTOMLIdentifier(datum)
|
datum = removeTOMLIdentifier(datum)
|
||||||
|
|
||||||
|
@ -198,7 +198,7 @@ func removeTOMLIdentifier(datum []byte) []byte {
|
||||||
|
|
||||||
// HandleYAMLMetaData unmarshals YAML-encoded datum and returns a Go interface
|
// HandleYAMLMetaData unmarshals YAML-encoded datum and returns a Go interface
|
||||||
// representing the encoded data structure.
|
// representing the encoded data structure.
|
||||||
func HandleYAMLMetaData(datum []byte) (interface{}, error) {
|
func HandleYAMLMetaData(datum []byte) (map[string]interface{}, error) {
|
||||||
m := map[string]interface{}{}
|
m := map[string]interface{}{}
|
||||||
err := yaml.Unmarshal(datum, &m)
|
err := yaml.Unmarshal(datum, &m)
|
||||||
return m, err
|
return m, err
|
||||||
|
@ -206,7 +206,7 @@ func HandleYAMLMetaData(datum []byte) (interface{}, error) {
|
||||||
|
|
||||||
// HandleJSONMetaData unmarshals JSON-encoded datum and returns a Go interface
|
// HandleJSONMetaData unmarshals JSON-encoded datum and returns a Go interface
|
||||||
// representing the encoded data structure.
|
// representing the encoded data structure.
|
||||||
func HandleJSONMetaData(datum []byte) (interface{}, error) {
|
func HandleJSONMetaData(datum []byte) (map[string]interface{}, error) {
|
||||||
if datum == nil {
|
if datum == nil {
|
||||||
// Package json returns on error on nil input.
|
// Package json returns on error on nil input.
|
||||||
// Return an empty map to be consistent with our other supported
|
// Return an empty map to be consistent with our other supported
|
||||||
|
@ -214,13 +214,13 @@ func HandleJSONMetaData(datum []byte) (interface{}, error) {
|
||||||
return make(map[string]interface{}), nil
|
return make(map[string]interface{}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var f interface{}
|
var f map[string]interface{}
|
||||||
err := json.Unmarshal(datum, &f)
|
err := json.Unmarshal(datum, &f)
|
||||||
return f, err
|
return f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleOrgMetaData unmarshals org-mode encoded datum and returns a Go
|
// HandleOrgMetaData unmarshals org-mode encoded datum and returns a Go
|
||||||
// interface representing the encoded data structure.
|
// interface representing the encoded data structure.
|
||||||
func HandleOrgMetaData(datum []byte) (interface{}, error) {
|
func HandleOrgMetaData(datum []byte) (map[string]interface{}, error) {
|
||||||
return goorgeous.OrgHeaders(datum)
|
return goorgeous.OrgHeaders(datum)
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ type Page interface {
|
||||||
IsRenderable() bool
|
IsRenderable() bool
|
||||||
|
|
||||||
// Metadata returns the unmarshalled frontmatter data.
|
// Metadata returns the unmarshalled frontmatter data.
|
||||||
Metadata() (interface{}, error)
|
Metadata() (map[string]interface{}, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// page implements the Page interface.
|
// page implements the Page interface.
|
||||||
|
@ -100,16 +100,13 @@ func (p *page) IsRenderable() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metadata returns the unmarshalled frontmatter data.
|
// Metadata returns the unmarshalled frontmatter data.
|
||||||
func (p *page) Metadata() (meta interface{}, err error) {
|
func (p *page) Metadata() (meta map[string]interface{}, err error) {
|
||||||
frontmatter := p.FrontMatter()
|
frontmatter := p.FrontMatter()
|
||||||
|
|
||||||
if len(frontmatter) != 0 {
|
if len(frontmatter) != 0 {
|
||||||
fm := DetectFrontMatter(rune(frontmatter[0]))
|
fm := DetectFrontMatter(rune(frontmatter[0]))
|
||||||
if fm != nil {
|
if fm != nil {
|
||||||
meta, err = fm.Parse(frontmatter)
|
meta, err = fm.Parse(frontmatter)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in a new issue