mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
ede452d34e
Accept JSON frontmatter without leading "{\n" so that one line frontmatters such as `{"param":"paramvalue"}` no longer silently render empty html.
313 lines
12 KiB
Go
313 lines
12 KiB
Go
// Copyright 2015 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 parser
|
|
|
|
// TODO Support Mac Encoding (\r)
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
const (
|
|
contentNoFrontmatter = "a page with no front matter"
|
|
contentWithFrontmatter = "---\ntitle: front matter\n---\nContent with front matter"
|
|
contentHTMLNoDoctype = "<html>\n\t<body>\n\t</body>\n</html>"
|
|
contentHTMLWithDoctype = "<!doctype html><html><body></body></html>"
|
|
contentHTMLWithFrontmatter = "---\ntitle: front matter\n---\n<!doctype><html><body></body></html>"
|
|
contentHTML = " <html><body></body></html>"
|
|
contentLinefeedAndHTML = "\n<html><body></body></html>"
|
|
contentIncompleteEndFrontmatterDelim = "---\ntitle: incomplete end fm delim\n--\nincomplete frontmatter delim"
|
|
contentMissingEndFrontmatterDelim = "---\ntitle: incomplete end fm delim\nincomplete frontmatter delim"
|
|
contentSlugWorking = "---\ntitle: slug doc 2\nslug: slug-doc-2\n\n---\nslug doc 2 content"
|
|
contentSlugWorkingVariation = "---\ntitle: slug doc 3\nslug: slug-doc 3\n---\nslug doc 3 content"
|
|
contentSlugBug = "---\ntitle: slug doc 2\nslug: slug-doc-2\n---\nslug doc 2 content"
|
|
contentSlugWithJSONFrontMatter = "{\n \"categories\": \"d\",\n \"tags\": [\n \"a\", \n \"b\", \n \"c\"\n ]\n}\nJSON Front Matter with tags and categories"
|
|
contentWithJSONLooseFrontmatter = "{\n \"categories\": \"d\"\n \"tags\": [\n \"a\" \n \"b\" \n \"c\"\n ]\n}\nJSON Front Matter with tags and categories"
|
|
contentSlugWithJSONFrontMatterFirstLineOnly = "{\"categories\":\"d\",\"tags\":[\"a\",\"b\",\"c\"]}\nJSON Front Matter with tags and categories"
|
|
contentSlugWithJSONFrontMatterFirstLine = "{\"categories\":\"d\",\n \"tags\":[\"a\",\"b\",\"c\"]}\nJSON Front Matter with tags and categories"
|
|
)
|
|
|
|
var lineEndings = []string{"\n", "\r\n"}
|
|
var delimiters = []string{"---", "+++"}
|
|
|
|
func pageMust(p Page, err error) *page {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return p.(*page)
|
|
}
|
|
|
|
func TestDegenerateCreatePageFrom(t *testing.T) {
|
|
tests := []struct {
|
|
content string
|
|
}{
|
|
{contentMissingEndFrontmatterDelim},
|
|
{contentIncompleteEndFrontmatterDelim},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
for _, ending := range lineEndings {
|
|
test.content = strings.Replace(test.content, "\n", ending, -1)
|
|
_, err := ReadFrom(strings.NewReader(test.content))
|
|
if err == nil {
|
|
t.Errorf("Content should return an err:\n%q\n", test.content)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func checkPageRender(t *testing.T, p *page, expected bool) {
|
|
if p.render != expected {
|
|
t.Errorf("page.render should be %t, got: %t", expected, p.render)
|
|
}
|
|
}
|
|
|
|
func checkPageFrontMatterIsNil(t *testing.T, p *page, content string, expected bool) {
|
|
if bool(p.frontmatter == nil) != expected {
|
|
t.Logf("\n%q\n", content)
|
|
t.Errorf("page.frontmatter == nil? %t, got %t", expected, p.frontmatter == nil)
|
|
}
|
|
}
|
|
|
|
func checkPageFrontMatterContent(t *testing.T, p *page, frontMatter string) {
|
|
if p.frontmatter == nil {
|
|
return
|
|
}
|
|
if !bytes.Equal(p.frontmatter, []byte(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("content mismatch\nexp: %q\ngot: %q", expected, p.content)
|
|
}
|
|
}
|
|
|
|
func TestStandaloneCreatePageFrom(t *testing.T) {
|
|
tests := []struct {
|
|
content string
|
|
expectedMustRender bool
|
|
frontMatterIsNil bool
|
|
frontMatter string
|
|
bodycontent string
|
|
}{
|
|
|
|
{contentNoFrontmatter, true, true, "", "a page with no front matter"},
|
|
{contentWithFrontmatter, true, false, "---\ntitle: front matter\n---\n", "Content with front matter"},
|
|
{contentHTMLNoDoctype, false, true, "", "<html>\n\t<body>\n\t</body>\n</html>"},
|
|
{contentHTMLWithDoctype, false, true, "", "<!doctype html><html><body></body></html>"},
|
|
{contentHTMLWithFrontmatter, true, false, "---\ntitle: front matter\n---\n", "<!doctype><html><body></body></html>"},
|
|
{contentHTML, false, true, "", "<html><body></body></html>"},
|
|
{contentLinefeedAndHTML, false, true, "", "<html><body></body></html>"},
|
|
{contentSlugWithJSONFrontMatter, true, false, "{\n \"categories\": \"d\",\n \"tags\": [\n \"a\", \n \"b\", \n \"c\"\n ]\n}", "JSON Front Matter with tags and categories"},
|
|
{contentWithJSONLooseFrontmatter, true, false, "{\n \"categories\": \"d\"\n \"tags\": [\n \"a\" \n \"b\" \n \"c\"\n ]\n}", "JSON Front Matter with tags and categories"},
|
|
{contentSlugWithJSONFrontMatterFirstLineOnly, true, false, "{\"categories\":\"d\",\"tags\":[\"a\",\"b\",\"c\"]}", "JSON Front Matter with tags and categories"},
|
|
{contentSlugWithJSONFrontMatterFirstLine, true, false, "{\"categories\":\"d\",\n \"tags\":[\"a\",\"b\",\"c\"]}", "JSON Front Matter with tags and categories"},
|
|
{contentSlugWorking, true, false, "---\ntitle: slug doc 2\nslug: slug-doc-2\n\n---\n", "slug doc 2 content"},
|
|
{contentSlugWorkingVariation, true, false, "---\ntitle: slug doc 3\nslug: slug-doc 3\n---\n", "slug doc 3 content"},
|
|
{contentSlugBug, true, false, "---\ntitle: slug doc 2\nslug: slug-doc-2\n---\n", "slug doc 2 content"},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
for _, ending := range lineEndings {
|
|
test.content = strings.Replace(test.content, "\n", ending, -1)
|
|
test.frontMatter = strings.Replace(test.frontMatter, "\n", ending, -1)
|
|
test.bodycontent = strings.Replace(test.bodycontent, "\n", ending, -1)
|
|
|
|
p := pageMust(ReadFrom(strings.NewReader(test.content)))
|
|
|
|
checkPageRender(t, p, test.expectedMustRender)
|
|
checkPageFrontMatterIsNil(t, p, test.content, test.frontMatterIsNil)
|
|
checkPageFrontMatterContent(t, p, test.frontMatter)
|
|
checkPageContent(t, p, test.bodycontent)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkLongFormRender(b *testing.B) {
|
|
|
|
tests := []struct {
|
|
filename string
|
|
buf []byte
|
|
}{
|
|
{filename: "long_text_test.md"},
|
|
}
|
|
for i, test := range tests {
|
|
path := filepath.FromSlash(test.filename)
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
b.Fatalf("Unable to open %s: %s", path, err)
|
|
}
|
|
defer f.Close()
|
|
membuf := new(bytes.Buffer)
|
|
if _, err := io.Copy(membuf, f); err != nil {
|
|
b.Fatalf("Unable to read %s: %s", path, err)
|
|
}
|
|
tests[i].buf = membuf.Bytes()
|
|
}
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i <= b.N; i++ {
|
|
for _, test := range tests {
|
|
ReadFrom(bytes.NewReader(test.buf))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPageShouldRender(t *testing.T) {
|
|
tests := []struct {
|
|
content []byte
|
|
expected bool
|
|
}{
|
|
{[]byte{}, false},
|
|
{[]byte{'<'}, false},
|
|
{[]byte{'-'}, true},
|
|
{[]byte("--"), true},
|
|
{[]byte("---"), true},
|
|
{[]byte("---\n"), true},
|
|
{[]byte{'a'}, true},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
for _, ending := range lineEndings {
|
|
test.content = bytes.Replace(test.content, []byte("\n"), []byte(ending), -1)
|
|
if render := shouldRender(test.content); render != test.expected {
|
|
|
|
t.Errorf("Expected %s to shouldRender = %t, got: %t", test.content, test.expected, render)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPageHasFrontMatter(t *testing.T) {
|
|
tests := []struct {
|
|
content []byte
|
|
expected bool
|
|
}{
|
|
{[]byte{'-'}, false},
|
|
{[]byte("--"), false},
|
|
{[]byte("---"), false},
|
|
{[]byte("---\n"), true},
|
|
{[]byte("---\n"), true},
|
|
{[]byte("--- \n"), true},
|
|
{[]byte("--- \n"), true},
|
|
{[]byte{'a'}, false},
|
|
{[]byte{'{'}, true},
|
|
{[]byte("{\n "), true},
|
|
{[]byte{'}'}, false},
|
|
}
|
|
for _, test := range tests {
|
|
for _, ending := range lineEndings {
|
|
test.content = bytes.Replace(test.content, []byte("\n"), []byte(ending), -1)
|
|
if isFrontMatterDelim := isFrontMatterDelim(test.content); isFrontMatterDelim != test.expected {
|
|
t.Errorf("Expected %q isFrontMatterDelim = %t, got: %t", test.content, test.expected, isFrontMatterDelim)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtractFrontMatter(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
frontmatter string
|
|
extracted []byte
|
|
errIsNil bool
|
|
}{
|
|
{"", nil, false},
|
|
{"-", nil, false},
|
|
{"---\n", nil, false},
|
|
{"---\nfoobar", nil, false},
|
|
{"---\nfoobar\nbarfoo\nfizbaz\n", nil, false},
|
|
{"---\nblar\n-\n", nil, false},
|
|
{"---\nralb\n---\n", []byte("---\nralb\n---\n"), true},
|
|
{"---\neof\n---", []byte("---\neof\n---"), true},
|
|
{"--- \neof\n---", []byte("---\neof\n---"), true},
|
|
{"---\nminc\n---\ncontent", []byte("---\nminc\n---\n"), true},
|
|
{"---\nminc\n--- \ncontent", []byte("---\nminc\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},
|
|
{"---\npermalink: '/blog/title---subtitle.html'\n---\ncontent\n", []byte("---\npermalink: '/blog/title---subtitle.html'\n---\n"), true},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
for _, ending := range lineEndings {
|
|
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)
|
|
line, err := peekLine(bufio.NewReader(strings.NewReader(test.frontmatter)))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
l, r := determineDelims(line)
|
|
fm, err := extractFrontMatterDelims(bufio.NewReader(strings.NewReader(test.frontmatter)), l, r)
|
|
if (err == nil) != test.errIsNil {
|
|
t.Logf("\n%q\n", string(test.frontmatter))
|
|
t.Errorf("Expected err == nil => %t, got: %t. err: %s", test.errIsNil, err == nil, err)
|
|
continue
|
|
}
|
|
if !bytes.Equal(fm, test.extracted) {
|
|
t.Errorf("Frontmatter did not match:\nexp: %q\ngot: %q", string(test.extracted), fm)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtractFrontMatterDelim(t *testing.T) {
|
|
var (
|
|
noErrExpected = true
|
|
errExpected = false
|
|
)
|
|
tests := []struct {
|
|
frontmatter string
|
|
extracted string
|
|
errIsNil bool
|
|
}{
|
|
{"", "", errExpected},
|
|
{"{", "", errExpected},
|
|
{"{}", "{}", noErrExpected},
|
|
{"{} ", "{}", noErrExpected},
|
|
{"{ } ", "{ }", noErrExpected},
|
|
{"{ { }", "", errExpected},
|
|
{"{ { } }", "{ { } }", noErrExpected},
|
|
{"{ { } { } }", "{ { } { } }", noErrExpected},
|
|
{"{\n{\n}\n}\n", "{\n{\n}\n}", noErrExpected},
|
|
{"{\n \"categories\": \"d\",\n \"tags\": [\n \"a\", \n \"b\", \n \"c\"\n ]\n}\nJSON Front Matter with tags and categories", "{\n \"categories\": \"d\",\n \"tags\": [\n \"a\", \n \"b\", \n \"c\"\n ]\n}", noErrExpected},
|
|
{"{\n \"categories\": \"d\"\n \"tags\": [\n \"a\" \n \"b\" \n \"c\"\n ]\n}\nJSON Front Matter with tags and categories", "{\n \"categories\": \"d\"\n \"tags\": [\n \"a\" \n \"b\" \n \"c\"\n ]\n}", noErrExpected},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
fm, err := extractFrontMatterDelims(bufio.NewReader(strings.NewReader(test.frontmatter)), []byte("{"), []byte("}"))
|
|
if (err == nil) != test.errIsNil {
|
|
t.Logf("\n%q\n", string(test.frontmatter))
|
|
t.Errorf("Expected err == nil => %t, got: %t. err: %s", test.errIsNil, err == nil, err)
|
|
continue
|
|
}
|
|
if !bytes.Equal(fm, []byte(test.extracted)) {
|
|
t.Logf("\n%q\n", string(test.frontmatter))
|
|
t.Errorf("Frontmatter did not match:\nexp: %q\ngot: %q", string(test.extracted), fm)
|
|
}
|
|
}
|
|
}
|