From d8e1834910d2b845ee5066571a61be49a7a1451c Mon Sep 17 00:00:00 2001 From: Noah Campbell Date: Wed, 18 Sep 2013 09:15:46 -0700 Subject: [PATCH] Fix parsing edge case of frontmatter When the frontmatter contains a - (or other delimiter) close to the closing frontmatter delimiter, frontmatter detection would fail. --- hugolib/page.go | 2 +- hugolib/page_test.go | 25 ++++++--------- hugolib/site.go | 24 +++++++------- hugolib/site_url_test.go | 11 +++++-- parser/page.go | 54 +++++++++++++++++++++++--------- parser/parse_frontmatter_test.go | 26 +++++++++------ transform/post.go | 2 +- transform/posttrans_test.go | 24 +++++++------- 8 files changed, 100 insertions(+), 68 deletions(-) diff --git a/hugolib/page.go b/hugolib/page.go index 3738483c7..79c414aaf 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -19,10 +19,10 @@ import ( "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" diff --git a/hugolib/page_test.go b/hugolib/page_test.go index a2a4999df..64395decf 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -10,13 +10,8 @@ import ( var EMPTY_PAGE = "" -var SIMPLE_PAGE = `--- -title: Simple ---- -Simple Page -` - -var INVALID_FRONT_MATTER_MISSING = `This is a test` +var SIMPLE_PAGE = "---\ntitle: Simple\n---\nSimple Page\n" +var INVALID_FRONT_MATTER_MISSING = "This is a test" var INVALID_FRONT_MATTER_SHORT_DELIM = ` -- @@ -95,7 +90,7 @@ type and layout set` var SIMPLE_PAGE_WITH_SUMMARY_DELIMITER = `--- title: Simple --- -Simple Page +Summary Next Line Some more text @@ -104,7 +99,7 @@ Some more text var SIMPLE_PAGE_WITH_SUMMARY_DELIMITER_SAME_LINE = `--- title: Simple --- -Simple Page +Summary Same Line Some more text ` @@ -144,7 +139,7 @@ func checkPageTitle(t *testing.T, page *Page, title string) { 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 @@ func TestPageWithDelimiter(t *testing.T) { t.Fatalf("Unable to create a page with frontmatter and body content: %s", err) } checkPageTitle(t, p, "Simple") - checkPageContent(t, p, "

Simple Page

\n\n

Some more text

\n") - checkPageSummary(t, p, "

Simple Page

\n") + checkPageContent(t, p, "

Summary Next Line

\n\n

Some more text

\n") + checkPageSummary(t, p, "

Summary Next Line

\n") checkPageType(t, p, "page") checkPageLayout(t, p, "page/single.html") @@ -203,8 +198,8 @@ func TestPageWithMoreTag(t *testing.T) { t.Fatalf("Unable to create a page with frontmatter and body content: %s", err) } checkPageTitle(t, p, "Simple") - checkPageContent(t, p, "

Simple Page

\n\n

Some more text

\n") - checkPageSummary(t, p, "

Simple Page

\n") + checkPageContent(t, p, "

Summary Same Line

\n\n

Some more text

\n") + checkPageSummary(t, p, "

Summary Same Line

\n") checkPageType(t, p, "page") checkPageLayout(t, p, "page/single.html") } @@ -243,7 +238,7 @@ func TestDegenerateInvalidFrontMatterShortDelim(t *testing.T) { 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 { diff --git a/hugolib/site.go b/hugolib/site.go index bfbb4ab24..2c8165543 100644 --- 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 @@ import ( "github.com/spf13/hugo/transform" "github.com/spf13/nitro" "html/template" + "io" "os" "path" "strings" @@ -68,18 +68,18 @@ func PrintErr(str string, a ...interface{}) { // // 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 { diff --git a/hugolib/site_url_test.go b/hugolib/site_url_test.go index ef226fe7f..b9c30fec4 100644 --- a/hugolib/site_url_test.go +++ b/hugolib/site_url_test.go @@ -10,7 +10,12 @@ import ( 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 @@ func (t *InMemoryAliasTarget) Publish(label string, permalink template.HTML) (er 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 @@ func TestPageCount(t *testing.T) { t.Errorf("No indexed rendered. %v", target.files) } - expected := "." + expected := ".." if string(blueIndex) != expected { t.Errorf("Index template does not match expected: %q, got: %q", expected, string(blueIndex)) } diff --git a/parser/page.go b/parser/page.go index 2df4ce635..eb8672bd4 100644 --- a/parser/page.go +++ b/parser/page.go @@ -2,9 +2,9 @@ package parser import ( "bufio" - "fmt" "bytes" "errors" + "fmt" "io" "unicode" ) @@ -164,22 +164,34 @@ func determineDelims(firstLine []byte) (left, right []byte) { } 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 @@ func extractFrontMatterDelims(r *bufio.Reader, left, right []byte) (fm FrontMatt 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 @@ func extractFrontMatterDelims(r *bufio.Reader, left, right []byte) (fm FrontMatt 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 @@ func matches(r *bufio.Reader, wr io.Writer, c, expected []byte) (ok bool, err er } 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 } diff --git a/parser/parse_frontmatter_test.go b/parser/parse_frontmatter_test.go index 1da38a185..6d65d9dcb 100644 --- a/parser/parse_frontmatter_test.go +++ b/parser/parse_frontmatter_test.go @@ -24,12 +24,15 @@ var ( 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 @@ func checkPageFrontMatterContent(t *testing.T, p *page, frontMatter string) { 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 @@ func TestStandaloneCreatePageFrom(t *testing.T) { 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, "", "\n\t\n\t\n"}, @@ -109,6 +113,9 @@ func TestStandaloneCreatePageFrom(t *testing.T) { {CONTENT_LWS_HTML, false, true, "", ""}, {CONTENT_LWS_LF_HTML, false, true, "", ""}, {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 @@ func TestExtractFrontMatter(t *testing.T) { {"---\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 @@ func TestExtractFrontMatter(t *testing.T) { 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 @@ func TestExtractFrontMatter(t *testing.T) { 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 @@ func TestExtractFrontMatterDelim(t *testing.T) { } 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) } } } - diff --git a/transform/post.go b/transform/post.go index f63f1683b..369b2d6ca 100644 --- 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 { diff --git a/transform/posttrans_test.go b/transform/posttrans_test.go index de2d2277c..08fe18c11 100644 --- 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 = "
content foobar. Follow up
" const H5_JS_CONTENT_SINGLE_QUOTE = "
content foobar. Follow up
" const H5_JS_CONTENT_ABS_URL = "
content foobar. Follow up
" + // URL doesn't recognize authorities. BUG? //const H5_JS_CONTENT_ABS_URL = "
content foobar. Follow up
" const CORRECT_OUTPUT_SRC_HREF = "
content foobar. Follow up
" - 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 @@ func TestAbsUrlify(t *testing.T) { 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) - } - if test.expected != string(out.Bytes()) { - t.Errorf("Expected:\n%s\nGot:\n%s", test.expected, string(out.Bytes())) + 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())) + } } } -}