// Copyright 2018 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package pageparser import ( "fmt" "strings" "testing" qt "github.com/frankban/quicktest" ) type lexerTest struct { name string input string items []typeText err error } type typeText struct { typ ItemType text string } func nti(tp ItemType, val string) typeText { return typeText{typ: tp, text: val} } var ( tstJSON = `{ "a": { "b": "\"Hugo\"}" } }` tstFrontMatterTOML = nti(TypeFrontMatterTOML, "foo = \"bar\"\n") tstFrontMatterYAML = nti(TypeFrontMatterYAML, "foo: \"bar\"\n") tstFrontMatterYAMLCRLF = nti(TypeFrontMatterYAML, "foo: \"bar\"\r\n") tstFrontMatterJSON = nti(TypeFrontMatterJSON, tstJSON+"\r\n") tstSomeText = nti(tText, "\nSome text.\n") tstSummaryDivider = nti(TypeLeadSummaryDivider, "\n") tstNewline = nti(tText, "\n") tstORG = ` #+TITLE: T1 #+AUTHOR: A1 #+DESCRIPTION: D1 ` tstFrontMatterORG = nti(TypeFrontMatterORG, tstORG) ) var crLfReplacer = strings.NewReplacer("\r", "#", "\n", "$") // TODO(bep) a way to toggle ORG mode vs the rest. var frontMatterTests = []lexerTest{ {"empty", "", []typeText{tstEOF}, nil}, {"Byte order mark", "\ufeff\nSome text.\n", []typeText{nti(TypeIgnore, "\ufeff"), tstSomeText, tstEOF}, nil}, {"No front matter", "\nSome text.\n", []typeText{tstSomeText, tstEOF}, nil}, {"YAML front matter", "---\nfoo: \"bar\"\n---\n\nSome text.\n", []typeText{tstFrontMatterYAML, tstSomeText, tstEOF}, nil}, {"YAML empty front matter", "---\n---\n\nSome text.\n", []typeText{nti(TypeFrontMatterYAML, ""), tstSomeText, tstEOF}, nil}, // Note that we keep all bytes as they are, but we need to handle CRLF {"YAML front matter CRLF", "---\r\nfoo: \"bar\"\r\n---\n\nSome text.\n", []typeText{tstFrontMatterYAMLCRLF, tstSomeText, tstEOF}, nil}, {"TOML front matter", "+++\nfoo = \"bar\"\n+++\n\nSome text.\n", []typeText{tstFrontMatterTOML, tstSomeText, tstEOF}, nil}, {"JSON front matter", tstJSON + "\r\n\nSome text.\n", []typeText{tstFrontMatterJSON, tstSomeText, tstEOF}, nil}, {"ORG front matter", tstORG + "\nSome text.\n", []typeText{tstFrontMatterORG, tstSomeText, tstEOF}, nil}, {"Summary divider ORG", tstORG + "\nSome text.\n# more\nSome text.\n", []typeText{tstFrontMatterORG, tstSomeText, nti(TypeLeadSummaryDivider, "# more\n"), nti(tText, "Some text.\n"), tstEOF}, nil}, {"Summary divider", "+++\nfoo = \"bar\"\n+++\n\nSome text.\n\nSome text.\n", []typeText{tstFrontMatterTOML, tstSomeText, tstSummaryDivider, nti(tText, "Some text.\n"), tstEOF}, nil}, {"Summary divider same line", "+++\nfoo = \"bar\"\n+++\n\nSome text.Some text.\n", []typeText{tstFrontMatterTOML, nti(tText, "\nSome text."), nti(TypeLeadSummaryDivider, ""), nti(tText, "Some text.\n"), tstEOF}, nil}, // https://github.com/gohugoio/hugo/issues/5402 {"Summary and shortcode, no space", "+++\nfoo = \"bar\"\n+++\n\nSome text.\n{{< sc1 >}}\nSome text.\n", []typeText{tstFrontMatterTOML, tstSomeText, nti(TypeLeadSummaryDivider, ""), tstLeftNoMD, tstSC1, tstRightNoMD, tstSomeText, tstEOF}, nil}, // https://github.com/gohugoio/hugo/issues/5464 {"Summary and shortcode only", "+++\nfoo = \"bar\"\n+++\n{{< sc1 >}}\n\n{{< sc2 >}}", []typeText{tstFrontMatterTOML, tstLeftNoMD, tstSC1, tstRightNoMD, tstNewline, tstSummaryDivider, tstLeftNoMD, tstSC2, tstRightNoMD, tstEOF}, nil}, } func TestFrontMatter(t *testing.T) { t.Parallel() c := qt.New(t) for i, test := range frontMatterTests { items, err := collect([]byte(test.input), false, lexIntroSection) if err != nil { c.Assert(err, qt.Equals, test.err) continue } else { c.Assert(test.err, qt.IsNil) } if !equal(test.input, items, test.items) { got := itemsToString(items, []byte(test.input)) expected := testItemsToString(test.items) c.Assert(got, qt.Equals, expected, qt.Commentf("Test %d: %s", i, test.name)) } } } func itemsToString(items []Item, source []byte) string { var sb strings.Builder for i, item := range items { var s string if item.Err != nil { s = item.Err.Error() } else { s = string(item.Val(source)) } sb.WriteString(fmt.Sprintf("%s: %s\n", item.Type, s)) if i < len(items)-1 { sb.WriteString("\n") } } return crLfReplacer.Replace(sb.String()) } func testItemsToString(items []typeText) string { var sb strings.Builder for i, item := range items { sb.WriteString(fmt.Sprintf("%s: %s\n", item.typ, item.text)) if i < len(items)-1 { sb.WriteString("\n") } } return crLfReplacer.Replace(sb.String()) } func collectWithConfig(input []byte, skipFrontMatter bool, stateStart stateFunc, cfg Config) (items []Item, err error) { l := newPageLexer(input, stateStart, cfg) l.run() iter := NewIterator(l.items) for { if l.err != nil { return nil, l.err } item := iter.Next() items = append(items, item) if item.Type == tEOF || item.Type == tError { break } } return } func collect(input []byte, skipFrontMatter bool, stateStart stateFunc) (items []Item, err error) { var cfg Config return collectWithConfig(input, skipFrontMatter, stateStart, cfg) } func collectStringMain(input string) ([]Item, error) { return collect([]byte(input), true, lexMainSection) } // no positional checking, for now ... func equal(source string, got []Item, expect []typeText) bool { if len(got) != len(expect) { return false } sourceb := []byte(source) for k := range got { g := got[k] e := expect[k] if g.Type != e.typ { return false } var s string if g.Err != nil { s = g.Err.Error() } else { s = string(g.Val(sourceb)) } if s != e.text { return false } } return true }