shithub: hugo

Download patch

ref: d8e1834910d2b845ee5066571a61be49a7a1451c
parent: a82efe5bb131f1d4a811d3220c2ce40d56aa9eaf
author: Noah Campbell <[email protected]>
date: Wed Sep 18 05:15:46 EDT 2013

Fix parsing edge case of frontmatter

When the frontmatter contains a - (or other delimiter) close to the
closing frontmatter delimiter, frontmatter detection would fail.

--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -19,10 +19,10 @@
 	"errors"
 	"fmt"
 	"github.com/BurntSushi/toml"
+	"github.com/spf13/hugo/parser"
 	helper "github.com/spf13/hugo/template"
 	"github.com/spf13/hugo/template/bundle"
 	"github.com/theplant/blackfriday"
-	"github.com/spf13/hugo/parser"
 	"html/template"
 	"io"
 	"launchpad.net/goyaml"
--- a/hugolib/page_test.go
+++ b/hugolib/page_test.go
@@ -10,14 +10,9 @@
 
 var EMPTY_PAGE = ""
 
-var SIMPLE_PAGE = `---
-title: Simple
----
-Simple Page
-`
+var SIMPLE_PAGE = "---\ntitle: Simple\n---\nSimple Page\n"
+var INVALID_FRONT_MATTER_MISSING = "This is a test"
 
-var INVALID_FRONT_MATTER_MISSING = `This is a test`
-
 var INVALID_FRONT_MATTER_SHORT_DELIM = `
 --
 title: Short delim start
@@ -95,7 +90,7 @@
 var SIMPLE_PAGE_WITH_SUMMARY_DELIMITER = `---
 title: Simple
 ---
-Simple Page
+Summary Next Line
 
 <!--more-->
 Some more text
@@ -104,7 +99,7 @@
 var SIMPLE_PAGE_WITH_SUMMARY_DELIMITER_SAME_LINE = `---
 title: Simple
 ---
-Simple Page<!--more-->
+Summary Same Line<!--more-->
 
 Some more text
 `
@@ -144,7 +139,7 @@
 
 func checkPageContent(t *testing.T, page *Page, content string) {
 	if page.Content != template.HTML(content) {
-		t.Fatalf("Page content is: %s.  Expected %s", page.Content, content)
+		t.Fatalf("Page content mismatch\nexp: %q\ngot: %q", content, page.Content)
 	}
 }
 
@@ -190,8 +185,8 @@
 		t.Fatalf("Unable to create a page with frontmatter and body content: %s", err)
 	}
 	checkPageTitle(t, p, "Simple")
-	checkPageContent(t, p, "<p>Simple Page</p>\n\n<p>Some more text</p>\n")
-	checkPageSummary(t, p, "<p>Simple Page</p>\n")
+	checkPageContent(t, p, "<p>Summary Next Line</p>\n\n<p>Some more text</p>\n")
+	checkPageSummary(t, p, "<p>Summary Next Line</p>\n")
 	checkPageType(t, p, "page")
 	checkPageLayout(t, p, "page/single.html")
 
@@ -203,8 +198,8 @@
 		t.Fatalf("Unable to create a page with frontmatter and body content: %s", err)
 	}
 	checkPageTitle(t, p, "Simple")
-	checkPageContent(t, p, "<p>Simple Page</p>\n\n<p>Some more text</p>\n")
-	checkPageSummary(t, p, "<p>Simple Page</p>\n")
+	checkPageContent(t, p, "<p>Summary Same Line</p>\n\n<p>Some more text</p>\n")
+	checkPageSummary(t, p, "<p>Summary Same Line</p>\n")
 	checkPageType(t, p, "page")
 	checkPageLayout(t, p, "page/single.html")
 }
@@ -243,7 +238,7 @@
 		err string
 	}{
 		{INVALID_FRONT_MATTER_SHORT_DELIM, "Unable to locate frontmatter"},
-		{INVALID_FRONT_MATTER_SHORT_DELIM_ENDING, "EOF"},
+		{INVALID_FRONT_MATTER_SHORT_DELIM_ENDING, "Unable to read frontmatter at filepos 45: EOF"},
 		{INVALID_FRONT_MATTER_MISSING, "Unable to locate frontmatter"},
 	}
 	for _, test := range tests {
--- a/hugolib/site.go
+++ b/hugolib/site.go
@@ -14,7 +14,6 @@
 package hugolib
 
 import (
-	"io"
 	"bitbucket.org/pkg/inflect"
 	"bytes"
 	"fmt"
@@ -25,6 +24,7 @@
 	"github.com/spf13/hugo/transform"
 	"github.com/spf13/nitro"
 	"html/template"
+	"io"
 	"os"
 	"path"
 	"strings"
@@ -68,18 +68,18 @@
 //
 // 5. The entire collection of files is written to disk.
 type Site struct {
-	Config     Config
-	Pages      Pages
-	Tmpl       bundle.Template
-	Indexes    IndexList
-	Source     source.Input
-	Sections   Index
-	Info       SiteInfo
-	Shortcodes map[string]ShortcodeFunc
-	timer      *nitro.B
+	Config      Config
+	Pages       Pages
+	Tmpl        bundle.Template
+	Indexes     IndexList
+	Source      source.Input
+	Sections    Index
+	Info        SiteInfo
+	Shortcodes  map[string]ShortcodeFunc
+	timer       *nitro.B
 	Transformer *transform.Transformer
-	Target     target.Output
-	Alias      target.AliasPublisher
+	Target      target.Output
+	Alias       target.AliasPublisher
 }
 
 type SiteInfo struct {
--- a/hugolib/site_url_test.go
+++ b/hugolib/site_url_test.go
@@ -10,7 +10,12 @@
 
 const SLUG_DOC_1 = "---\ntitle: slug doc 1\nslug: slug-doc-1\naliases:\n - sd1/foo/\n - sd2\n - sd3/\n - sd4.html\n---\nslug doc 1 content\n"
 
-const SLUG_DOC_2 = "---\ntitle: slug doc 2\nslug: slug-doc-2\n---\nslug doc 2 content\n"
+const SLUG_DOC_2 = `---
+title: slug doc 2
+slug: slug-doc-2
+---
+slug doc 2 content
+`
 
 const INDEX_TEMPLATE = "{{ range .Data.Pages }}.{{ end }}"
 
@@ -58,7 +63,7 @@
 
 var urlFakeSource = []byteSource{
 	{"content/blue/doc1.md", []byte(SLUG_DOC_1)},
-//	{"content/blue/doc2.md", []byte(SLUG_DOC_2)},
+	{"content/blue/doc2.md", []byte(SLUG_DOC_2)},
 }
 
 func TestPageCount(t *testing.T) {
@@ -95,7 +100,7 @@
 		t.Errorf("No indexed rendered. %v", target.files)
 	}
 
-	expected := "<html><head></head><body>.</body></html>"
+	expected := "<html><head></head><body>..</body></html>"
 	if string(blueIndex) != expected {
 		t.Errorf("Index template does not match expected: %q, got: %q", expected, string(blueIndex))
 	}
--- a/parser/page.go
+++ b/parser/page.go
@@ -2,9 +2,9 @@
 
 import (
 	"bufio"
-	"fmt"
 	"bytes"
 	"errors"
+	"fmt"
 	"io"
 	"unicode"
 )
@@ -164,22 +164,34 @@
 }
 
 func extractFrontMatterDelims(r *bufio.Reader, left, right []byte) (fm FrontMatter, err error) {
-	var level int = 0
-	var sameDelim = bytes.Equal(left, right)
+	var (
+		c         byte
+		level     int = 0
+		bytesRead int = 0
+		sameDelim     = bytes.Equal(left, right)
+	)
+
 	wr := new(bytes.Buffer)
 	for {
-		c, err := r.ReadByte()
-		if err != nil {
-			return nil, err
+		if c, err = r.ReadByte(); err != nil {
+			return nil, fmt.Errorf("Unable to read frontmatter at filepos %d: %s", bytesRead, err)
 		}
+		bytesRead += 1
 
 		switch c {
 		case left[0]:
-			match, err := matches(r, wr, []byte{c}, left)
-			if err != nil {
+			var (
+				buf       []byte = []byte{c}
+				remaining []byte
+			)
+
+			if remaining, err = r.Peek(len(left) - 1); err != nil {
 				return nil, err
 			}
-			if match {
+
+			buf = append(buf, remaining...)
+
+			if bytes.Equal(buf, left) {
 				if sameDelim {
 					if level == 0 {
 						level = 1
@@ -190,6 +202,19 @@
 					level += 1
 				}
 			}
+
+			if _, err = wr.Write([]byte{c}); err != nil {
+				return nil, err
+			}
+
+			if level == 0 {
+				if _, err = r.Read(remaining); err != nil {
+					return nil, err
+				}
+				if _, err = wr.Write(remaining); err != nil {
+					return nil, err
+				}
+			}
 		case right[0]:
 			match, err := matches(r, wr, []byte{c}, right)
 			if err != nil {
@@ -216,6 +241,10 @@
 	return nil, errors.New("Could not find front matter.")
 }
 
+func matches_quick(buf, expected []byte) (ok bool, err error) {
+	return bytes.Equal(expected, buf), nil
+}
+
 func matches(r *bufio.Reader, wr io.Writer, c, expected []byte) (ok bool, err error) {
 	if len(expected) == 1 {
 		if _, err = wr.Write(c); err != nil {
@@ -223,16 +252,13 @@
 		}
 		return bytes.Equal(c, expected), nil
 	}
+
 	buf := make([]byte, len(expected)-1)
-	if _, err = r.Read(buf); err != nil {
+	if buf, err = r.Peek(len(expected) - 1); err != nil {
 		return
 	}
 
 	buf = append(c, buf...)
-	if _, err = wr.Write(buf); err != nil {
-		return
-	}
-
 	return bytes.Equal(expected, buf), nil
 }
 
--- a/parser/parse_frontmatter_test.go
+++ b/parser/parse_frontmatter_test.go
@@ -24,12 +24,15 @@
 	CONTENT_INCOMPLETE_BEG_FM_DELIM = "--\ntitle: incomplete beg fm delim\n---\nincomplete frontmatter delim"
 	CONTENT_INCOMPLETE_END_FM_DELIM = "---\ntitle: incomplete end fm delim\n--\nincomplete frontmatter delim"
 	CONTENT_MISSING_END_FM_DELIM    = "---\ntitle: incomplete end fm delim\nincomplete frontmatter delim"
+	CONTENT_SLUG_WORKING            = "---\ntitle: slug doc 2\nslug: slug-doc-2\n\n---\nslug doc 2 content"
+	CONTENT_SLUG_WORKING_VARIATION  = "---\ntitle: slug doc 3\nslug: slug-doc 3\n---\nslug doc 3 content"
+	CONTENT_SLUG_BUG                = "---\ntitle: slug doc 2\nslug: slug-doc-2\n---\nslug doc 2 content"
 	CONTENT_FM_NO_DOC               = "---\ntitle: no doc\n---"
-	CONTENT_WITH_JS_FM = "{\n  \"categories\": \"d\",\n  \"tags\": [\n    \"a\", \n    \"b\", \n    \"c\"\n  ]\n}\nJSON Front Matter with tags and categories"
+	CONTENT_WITH_JS_FM              = "{\n  \"categories\": \"d\",\n  \"tags\": [\n    \"a\", \n    \"b\", \n    \"c\"\n  ]\n}\nJSON Front Matter with tags and categories"
 )
 
 var lineEndings = []string{"\n", "\r\n"}
-var delimiters = []string{"-", "+"}
+var delimiters = []string{"---", "+++"}
 
 func pageMust(p Page, err error) *page {
 	if err != nil {
@@ -83,13 +86,13 @@
 		return
 	}
 	if !bytes.Equal(p.frontmatter, []byte(frontMatter)) {
-		t.Errorf("expected frontmatter %q, got %q", frontMatter, p.frontmatter)
+		t.Errorf("frontmatter mismatch\nexp: %q\ngot: %q", frontMatter, p.frontmatter)
 	}
 }
 
 func checkPageContent(t *testing.T, p *page, expected string) {
 	if !bytes.Equal(p.content, []byte(expected)) {
-		t.Errorf("expected content %q, got %q", expected, p.content)
+		t.Errorf("content mismatch\nexp: %q\ngot: %q", expected, p.content)
 	}
 }
 
@@ -101,6 +104,7 @@
 		frontMatter        string
 		bodycontent        string
 	}{
+
 		{CONTENT_NO_FRONTMATTER, true, true, "", "a page with no front matter"},
 		{CONTENT_WITH_FRONTMATTER, true, false, "---\ntitle: front matter\n---\n", "Content with front matter"},
 		{CONTENT_HTML_NODOCTYPE, false, true, "", "<html>\n\t<body>\n\t</body>\n</html>"},
@@ -109,6 +113,9 @@
 		{CONTENT_LWS_HTML, false, true, "", "<html><body></body></html>"},
 		{CONTENT_LWS_LF_HTML, false, true, "", "<html><body></body></html>"},
 		{CONTENT_WITH_JS_FM, true, false, "{\n  \"categories\": \"d\",\n  \"tags\": [\n    \"a\", \n    \"b\", \n    \"c\"\n  ]\n}", "JSON Front Matter with tags and categories"},
+		{CONTENT_SLUG_WORKING, true, false, "---\ntitle: slug doc 2\nslug: slug-doc-2\n\n---\n", "slug doc 2 content"},
+		{CONTENT_SLUG_WORKING_VARIATION, true, false, "---\ntitle: slug doc 3\nslug: slug-doc 3\n---\n", "slug doc 3 content"},
+		{CONTENT_SLUG_BUG, true, false, "---\ntitle: slug doc 2\nslug: slug-doc-2\n---\n", "slug doc 2 content"},
 	}
 
 	for _, test := range tests {
@@ -224,6 +231,7 @@
 		{"---\nralb\n---\n", []byte("---\nralb\n---\n"), true},
 		{"---\nminc\n---\ncontent", []byte("---\nminc\n---\n"), true},
 		{"---\ncnim\n---\ncontent\n", []byte("---\ncnim\n---\n"), true},
+		{"---\ntitle: slug doc 2\nslug: slug-doc-2\n---\ncontent\n", []byte("---\ntitle: slug doc 2\nslug: slug-doc-2\n---\n"), true},
 	}
 
 	for _, test := range tests {
@@ -231,8 +239,8 @@
 			test.frontmatter = strings.Replace(test.frontmatter, "\n", ending, -1)
 			test.extracted = bytes.Replace(test.extracted, []byte("\n"), []byte(ending), -1)
 			for _, delim := range delimiters {
-				test.frontmatter = strings.Replace(test.frontmatter, "-", delim, -1)
-				test.extracted = bytes.Replace(test.extracted, []byte("-"), []byte(delim), -1)
+				test.frontmatter = strings.Replace(test.frontmatter, "---", delim, -1)
+				test.extracted = bytes.Replace(test.extracted, []byte("---"), []byte(delim), -1)
 				line, err := peekLine(bufio.NewReader(strings.NewReader(test.frontmatter)))
 				if err != nil {
 					continue
@@ -245,8 +253,7 @@
 					continue
 				}
 				if !bytes.Equal(fm, test.extracted) {
-					t.Logf("\n%q\n", string(test.frontmatter))
-					t.Errorf("Expected front matter %q. got %q", string(test.extracted), fm)
+					t.Errorf("Frontmatter did not match:\nexp: %q\ngot: %q", string(test.extracted), fm)
 				}
 			}
 		}
@@ -285,8 +292,7 @@
 		}
 		if !bytes.Equal(fm, []byte(test.extracted)) {
 			t.Logf("\n%q\n", string(test.frontmatter))
-			t.Errorf("Expected front matter %q. got %q", string(test.extracted), fm)
+			t.Errorf("Frontmatter did not match:\nexp: %q\ngot: %q", string(test.extracted), fm)
 		}
 	}
 }
-
--- a/transform/post.go
+++ b/transform/post.go
@@ -1,9 +1,9 @@
 package transform
 
 import (
+	htmltran "code.google.com/p/go-html-transform/html/transform"
 	"io"
 	"net/url"
-	htmltran "code.google.com/p/go-html-transform/html/transform"
 )
 
 type Transformer struct {
--- a/transform/posttrans_test.go
+++ b/transform/posttrans_test.go
@@ -1,23 +1,23 @@
 package transform
 
 import (
-	"testing"
-	"strings"
 	"bytes"
+	"strings"
+	"testing"
 )
 
 const H5_JS_CONTENT_DOUBLE_QUOTE = "<!DOCTYPE html><html><head><script src=\"foobar.js\"></script></head><body><nav><h1>title</h1></nav><article>content <a href='/foobar'>foobar</a>. Follow up</article></body></html>"
 const H5_JS_CONTENT_SINGLE_QUOTE = "<!DOCTYPE html><html><head><script src='foobar.js'></script></head><body><nav><h1>title</h1></nav><article>content <a href='/foobar'>foobar</a>. Follow up</article></body></html>"
 const H5_JS_CONTENT_ABS_URL = "<!DOCTYPE html><html><head><script src=\"http://user@host:10234/foobar.js\"></script></head><body><nav><h1>title</h1></nav><article>content <a href=\"https://host/foobar\">foobar</a>. Follow up</article></body></html>"
+
 // URL doesn't recognize authorities.  BUG?
 //const H5_JS_CONTENT_ABS_URL = "<!DOCTYPE html><html><head><script src=\"//host/foobar.js\"></script></head><body><nav><h1>title</h1></nav><article>content <a href=\"https://host/foobar\">foobar</a>. Follow up</article></body></html>"
 
 const CORRECT_OUTPUT_SRC_HREF = "<!DOCTYPE html><html><head><script src=\"http://base/foobar.js\"></script></head><body><nav><h1>title</h1></nav><article>content <a href=\"http://base/foobar\">foobar</a>. Follow up</article></body></html>"
 
-
 func TestAbsUrlify(t *testing.T) {
 	tests := []struct {
-		content string
+		content  string
 		expected string
 	}{
 		{H5_JS_CONTENT_DOUBLE_QUOTE, CORRECT_OUTPUT_SRC_HREF},
@@ -29,13 +29,13 @@
 		tr := &Transformer{
 			BaseURL: "http://base",
 		}
-	out := new(bytes.Buffer)
-	err := tr.Apply(strings.NewReader(test.content), out)
-	if err != nil {
-		t.Errorf("Unexpected error: %s", err)
+		out := new(bytes.Buffer)
+		err := tr.Apply(strings.NewReader(test.content), out)
+		if err != nil {
+			t.Errorf("Unexpected error: %s", err)
+		}
+		if test.expected != string(out.Bytes()) {
+			t.Errorf("Expected:\n%s\nGot:\n%s", test.expected, string(out.Bytes()))
+		}
 	}
-	if test.expected != string(out.Bytes()) {
-		t.Errorf("Expected:\n%s\nGot:\n%s", test.expected, string(out.Bytes()))
-	}
-}
 }