shithub: hugo

Download patch

ref: 91bb774ae4e129f7ed0624754b31479c960ef774
parent: 3f0379adb72389954ca2be6a9f2ebfcd65c6c440
author: Vas Sudanagunta <[email protected]>
date: Thu Jan 25 17:54:15 EST 2018

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

--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -1231,9 +1231,7 @@
 		return
 	}
 
-	config := tomlMeta.(map[string]interface{})
-
-	if minVersion, ok := config["min_version"]; ok {
+	if minVersion, ok := tomlMeta["min_version"]; ok {
 		return helpers.CompareVersion(minVersion) > 0, fmt.Sprint(minVersion)
 	}
 
--- a/commands/import_jekyll.go
+++ b/commands/import_jekyll.go
@@ -255,7 +255,7 @@
 		return nil
 	}
 
-	return c.(map[string]interface{})
+	return c
 }
 
 func createConfigFromJekyll(fs afero.Fs, inpath string, kind string, jekyllConfig map[string]interface{}) (err error) {
--- a/commands/undraft.go
+++ b/commands/undraft.go
@@ -99,7 +99,7 @@
 	var isDraft, gotDate bool
 	var date string
 L:
-	for k, v := range meta.(map[string]interface{}) {
+	for k, v := range meta {
 		switch k {
 		case "draft":
 			if !v.(bool) {
--- a/commands/undraft_test.go
+++ b/commands/undraft_test.go
@@ -69,7 +69,7 @@
 				t.Errorf("[%d] unexpected error %q", i, err)
 				continue
 			}
-			for k, v := range meta.(map[string]interface{}) {
+			for k, v := range meta {
 				if k == "draft" {
 					if v.(bool) {
 						t.Errorf("[%d] Expected %q to be \"false\", got \"true\"", i, k)
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -1108,19 +1108,18 @@
 
 var ErrHasDraftAndPublished = errors.New("both draft and published parameters were found in page's frontmatter")
 
-func (p *Page) update(f interface{}) error {
-	if f == nil {
-		return errors.New("no metadata found")
+func (p *Page) update(frontmatter map[string]interface{}) error {
+	if frontmatter == nil {
+		return errors.New("missing frontmatter data")
 	}
-	m := f.(map[string]interface{})
 	// Needed for case insensitive fetching of params values
-	helpers.ToLowerMap(m)
+	helpers.ToLowerMap(frontmatter)
 
 	var modified time.Time
 
 	var err error
 	var draft, published, isCJKLanguage *bool
-	for k, v := range m {
+	for k, v := range frontmatter {
 		loki := strings.ToLower(k)
 		switch loki {
 		case "title":
@@ -1371,7 +1370,6 @@
 	p.params["iscjklanguage"] = p.isCJKLanguage
 
 	return nil
-
 }
 
 func (p *Page) GetParam(key string) interface{} {
@@ -1614,14 +1612,12 @@
 	if err != nil {
 		return fmt.Errorf("failed to parse page metadata for %q: %s", p.File.Path(), err)
 	}
-
-	if meta != nil {
-		if err = p.update(meta); err != nil {
-			return err
-		}
+	if meta == nil {
+		// missing frontmatter equivalent to empty frontmatter
+		meta = map[string]interface{}{}
 	}
 
-	return nil
+	return p.update(meta)
 }
 
 func (p *Page) RawContent() string {
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -912,8 +912,8 @@
 	L = "2017-09-03T22:22:22Z"
 	M = "2018-01-24T12:21:39Z"
 	E = "2025-12-31T23:59:59Z"
-	o = "0001-01-01T00:00:00Z"
-	x = ""
+	o = "0001-01-01T00:00:00Z" // zero value of type Time, default for some date fields
+	x = ""                     // nil date value, default for some date fields
 
 	p_D____ = `---
 title: Simple
@@ -981,20 +981,41 @@
 
 ---
 Page With empty front matter`
+
+	zero_FM = "Page With empty front matter"
 )
 
 func TestMetadataDates(t *testing.T) {
 	t.Parallel()
 	var tests = []struct {
-		text     string
-		filename string
-		fallback bool
-		expDate  string
-		expPub   string
-		expLast  string
-		expMod   string
-		expExp   string
-	}{ //                           D  P  L  M  E
+		text        string
+		filename    string
+		modFallback bool
+		expDate     string
+		expPub      string
+		expLast     string
+		expMod      string
+		expExp      string
+	}{
+		// 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____, "testy.md", true, D, D, D, x, x},
 		{p__P___, "test.md", false, P, P, P, x, x}, // pubdate copied across
@@ -1010,12 +1031,14 @@
 		{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
+		{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
+		{zero_FM, "testy.md", true, s, o, s, x, x}, // Issue #4320
 	}
 
 	for i, test := range tests {
 		s := newTestSite(t)
-		s.Cfg.Set("useModTimeAsFallback", test.fallback)
+		s.Cfg.Set("useModTimeAsFallback", test.modFallback)
 		fs := hugofs.NewMem(s.Cfg)
 
 		writeToFs(t, fs.Source, test.filename, test.text)
--- a/parser/frontmatter.go
+++ b/parser/frontmatter.go
@@ -31,7 +31,7 @@
 // FrontmatterType represents a type of frontmatter.
 type FrontmatterType struct {
 	// 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
 	includeMark        bool   // include start and end mark in output
@@ -168,7 +168,7 @@
 
 // HandleTOMLMetaData unmarshals TOML-encoded datum and returns a Go interface
 // representing the encoded data structure.
-func HandleTOMLMetaData(datum []byte) (interface{}, error) {
+func HandleTOMLMetaData(datum []byte) (map[string]interface{}, error) {
 	m := map[string]interface{}{}
 	datum = removeTOMLIdentifier(datum)
 
@@ -198,7 +198,7 @@
 
 // HandleYAMLMetaData unmarshals YAML-encoded datum and returns a Go interface
 // representing the encoded data structure.
-func HandleYAMLMetaData(datum []byte) (interface{}, error) {
+func HandleYAMLMetaData(datum []byte) (map[string]interface{}, error) {
 	m := map[string]interface{}{}
 	err := yaml.Unmarshal(datum, &m)
 	return m, err
@@ -206,7 +206,7 @@
 
 // HandleJSONMetaData unmarshals JSON-encoded datum and returns a Go interface
 // representing the encoded data structure.
-func HandleJSONMetaData(datum []byte) (interface{}, error) {
+func HandleJSONMetaData(datum []byte) (map[string]interface{}, error) {
 	if datum == nil {
 		// Package json returns on error on nil input.
 		// Return an empty map to be consistent with our other supported
@@ -214,7 +214,7 @@
 		return make(map[string]interface{}), nil
 	}
 
-	var f interface{}
+	var f map[string]interface{}
 	err := json.Unmarshal(datum, &f)
 	return f, err
 }
@@ -221,6 +221,6 @@
 
 // HandleOrgMetaData unmarshals org-mode encoded datum and returns a Go
 // interface representing the encoded data structure.
-func HandleOrgMetaData(datum []byte) (interface{}, error) {
+func HandleOrgMetaData(datum []byte) (map[string]interface{}, error) {
 	return goorgeous.OrgHeaders(datum)
 }
--- a/parser/page.go
+++ b/parser/page.go
@@ -74,7 +74,7 @@
 	IsRenderable() bool
 
 	// Metadata returns the unmarshalled frontmatter data.
-	Metadata() (interface{}, error)
+	Metadata() (map[string]interface{}, error)
 }
 
 // page implements the Page interface.
@@ -100,7 +100,7 @@
 }
 
 // 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()
 
 	if len(frontmatter) != 0 {
@@ -107,9 +107,6 @@
 		fm := DetectFrontMatter(rune(frontmatter[0]))
 		if fm != nil {
 			meta, err = fm.Parse(frontmatter)
-			if err != nil {
-				return
-			}
 		}
 	}
 	return