1
0
mirror of https://github.com/danog/gojekyll.git synced 2024-11-26 21:14:48 +01:00

Report markdown error filename

This commit is contained in:
Oliver Steele 2017-08-22 10:47:34 -04:00
parent a4f352c505
commit 32b1d0e7c4
9 changed files with 133 additions and 47 deletions

View File

@ -1,6 +1,8 @@
package pages package pages
import ( import (
"bytes"
"fmt"
"io" "io"
"testing" "testing"
@ -21,11 +23,15 @@ func (s siteFake) RendererManager() renderers.Renderers { return &renderManagerF
type renderManagerFake struct{ t *testing.T } type renderManagerFake struct{ t *testing.T }
func (rm renderManagerFake) ApplyLayout(layout string, src []byte, vars liquid.Bindings) ([]byte, error) { func (rm renderManagerFake) ApplyLayout(layout string, content []byte, vars liquid.Bindings) ([]byte, error) {
require.Equal(rm.t, "layout1", layout) require.Equal(rm.t, "layout1", layout)
return nil, nil return content, nil
} }
func (rm renderManagerFake) Render(w io.Writer, src []byte, vars liquid.Bindings, filename string, lineNo int) error { func (rm renderManagerFake) Render(w io.Writer, src []byte, vars liquid.Bindings, filename string, lineNo int) error {
if bytes.Contains(src, []byte("{% error %}")) {
return fmt.Errorf("render error")
}
_, err := io.WriteString(w, "rendered: ") _, err := io.WriteString(w, "rendered: ")
if err != nil { if err != nil {
return err return err

View File

@ -12,6 +12,7 @@ import (
"time" "time"
"github.com/osteele/gojekyll/frontmatter" "github.com/osteele/gojekyll/frontmatter"
"github.com/osteele/gojekyll/utils"
"github.com/osteele/gojekyll/version" "github.com/osteele/gojekyll/version"
"github.com/osteele/liquid/evaluator" "github.com/osteele/liquid/evaluator"
) )
@ -169,24 +170,23 @@ func (f *file) PostDate() time.Time {
// Write applies Liquid and Markdown, as appropriate. // Write applies Liquid and Markdown, as appropriate.
func (p *page) Write(w io.Writer) error { func (p *page) Write(w io.Writer) error {
err := p.Render() if err := p.Render(); err != nil {
if err != nil {
return err return err
} }
p.RLock() p.RLock()
defer p.RUnlock() defer p.RUnlock()
content := p.content cn := p.content
layout, ok := p.frontMatter["layout"].(string) lo, ok := p.frontMatter["layout"].(string)
if ok && layout != "" { if ok && lo != "" {
rp := p.site.RendererManager() rm := p.site.RendererManager()
b, e := rp.ApplyLayout(layout, []byte(content), p.TemplateContext()) b, err := rm.ApplyLayout(lo, []byte(cn), p.TemplateContext())
if e != nil { if err != nil {
return e return err
} }
_, err = w.Write(b) _, err = w.Write(b)
} else { return err
_, err = io.WriteString(w, content)
} }
_, err := io.WriteString(w, cn)
return err return err
} }
@ -197,7 +197,7 @@ func (p *page) Render() error {
p.Lock() p.Lock()
defer p.Unlock() defer p.Unlock()
p.content = cn p.content = cn
p.contentError = err p.contentError = utils.WrapPathError(err, p.filename)
p.excerpt = ex p.excerpt = ex
p.rendered = true p.rendered = true
}) })

View File

@ -2,10 +2,14 @@ package pages
import ( import (
"bytes" "bytes"
"io/ioutil"
"os" "os"
"path/filepath"
"testing" "testing"
"github.com/osteele/gojekyll/config" "github.com/osteele/gojekyll/config"
"github.com/osteele/gojekyll/frontmatter"
"github.com/osteele/gojekyll/utils"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -27,17 +31,43 @@ func TestPage_TemplateContext(t *testing.T) {
func TestPage_Categories(t *testing.T) { func TestPage_Categories(t *testing.T) {
s := siteFake{t, config.Default()} s := siteFake{t, config.Default()}
fm := map[string]interface{}{"categories": "b a"} fm := frontmatter.FrontMatter{"categories": "b a"}
f := file{site: s, frontMatter: fm} f := file{site: s, frontMatter: fm}
p := page{file: f} p := page{file: f}
require.Equal(t, []string{"a", "b"}, p.Categories()) require.Equal(t, []string{"a", "b"}, p.Categories())
} }
func TestPage_Write(t *testing.T) { func TestPage_Write(t *testing.T) {
cfg := config.Default() t.Run("rendering", func(t *testing.T) {
p, err := NewFile(siteFake{t, cfg}, "testdata/page_with_layout.md", "page_with_layout.md", map[string]interface{}{}) p := requirePageFromFile(t, "page_with_layout.md")
buf := new(bytes.Buffer)
require.NoError(t, p.Write(buf))
require.Contains(t, buf.String(), "page with layout")
})
t.Run("rendering error", func(t *testing.T) {
p := requirePageFromFile(t, "liquid_error.md")
err := p.Write(ioutil.Discard)
require.NotNil(t, err)
require.Contains(t, err.Error(), "render error")
pe, ok := err.(utils.PathError)
require.True(t, ok)
require.Equal(t, "testdata/liquid_error.md", pe.Path())
})
}
func fakePageFromFile(t *testing.T, file string) (Document, error) {
return NewFile(
siteFake{t, config.Default()},
filepath.Join("testdata", file),
file,
frontmatter.FrontMatter{},
)
}
func requirePageFromFile(t *testing.T, file string) Document {
p, err := fakePageFromFile(t, file)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, p) require.NotNil(t, p)
buf := new(bytes.Buffer) return p
require.NoError(t, p.Write(buf))
} }

4
pages/testdata/liquid_error.md vendored Normal file
View File

@ -0,0 +1,4 @@
---
---
{% error %}

View File

@ -13,8 +13,8 @@ import (
"github.com/osteele/liquid" "github.com/osteele/liquid"
) )
// ApplyLayout applies the named layout to the data. // ApplyLayout applies the named layout to the content.
func (p *Manager) ApplyLayout(name string, data []byte, vars liquid.Bindings) ([]byte, error) { func (p *Manager) ApplyLayout(name string, content []byte, vars liquid.Bindings) ([]byte, error) {
for name != "" { for name != "" {
var lfm map[string]interface{} var lfm map[string]interface{}
tpl, err := p.FindLayout(name, &lfm) tpl, err := p.FindLayout(name, &lfm)
@ -22,16 +22,16 @@ func (p *Manager) ApplyLayout(name string, data []byte, vars liquid.Bindings) ([
return nil, err return nil, err
} }
b := utils.MergeStringMaps(vars, map[string]interface{}{ b := utils.MergeStringMaps(vars, map[string]interface{}{
"content": string(data), "content": string(content),
"layout": lfm, "layout": lfm,
}) })
data, err = tpl.Render(b) content, err = tpl.Render(b)
if err != nil { if err != nil {
return nil, utils.WrapPathError(err, name) return nil, utils.WrapPathError(err, name)
} }
name = templates.VariableMap(lfm).String("layout", "") name = templates.VariableMap(lfm).String("layout", "")
} }
return data, nil return content, nil
} }
// FindLayout returns a template for the named layout. // FindLayout returns a template for the named layout.

View File

@ -2,6 +2,7 @@ package renderers
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"regexp" "regexp"
@ -29,17 +30,31 @@ const blackfridayExtensions = 0 |
// added relative to commonExtensions // added relative to commonExtensions
blackfriday.EXTENSION_AUTO_HEADER_IDS blackfriday.EXTENSION_AUTO_HEADER_IDS
func renderMarkdown(md []byte) []byte { func renderMarkdown(md []byte) ([]byte, error) {
renderer := blackfriday.HtmlRenderer(blackfridayFlags, "", "") renderer := blackfriday.HtmlRenderer(blackfridayFlags, "", "")
html := blackfriday.MarkdownOptions(md, renderer, blackfriday.Options{ html := blackfriday.MarkdownOptions(
Extensions: blackfridayExtensions}) md,
renderer,
blackfriday.Options{Extensions: blackfridayExtensions},
)
html, err := renderInnerMarkdown(html) html, err := renderInnerMarkdown(html)
if err != nil { if err != nil {
panic(err) return nil, fmt.Errorf("%s while rendering markdown", err)
} }
return html return html, nil
} }
func _renderMarkdown(md []byte) ([]byte, error) {
renderer := blackfriday.HtmlRenderer(blackfridayFlags, "", "")
html := blackfriday.MarkdownOptions(
md,
renderer,
blackfriday.Options{Extensions: blackfridayExtensions},
)
return html, nil
}
// search HTML for markdown=1, and process if found
func renderInnerMarkdown(b []byte) ([]byte, error) { func renderInnerMarkdown(b []byte) ([]byte, error) {
z := html.NewTokenizer(bytes.NewReader(b)) z := html.NewTokenizer(bytes.NewReader(b))
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
@ -54,7 +69,7 @@ outer:
return nil, z.Err() return nil, z.Err()
case html.StartTagToken: case html.StartTagToken:
if hasMarkdownAttr(z) { if hasMarkdownAttr(z) {
_, err := buf.Write(removeMarkdownAttr(z.Raw())) _, err := buf.Write(stripMarkdownAttr(z.Raw()))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -62,6 +77,7 @@ outer:
return nil, err return nil, err
} }
// the above leaves z set to the end token // the above leaves z set to the end token
// fall through to render it
} }
} }
_, err := buf.Write(z.Raw()) _, err := buf.Write(z.Raw())
@ -72,8 +88,6 @@ outer:
return buf.Bytes(), nil return buf.Bytes(), nil
} }
var markdownAttrRE = regexp.MustCompile(`\s*markdown\s*=\s*("1"|'1'|1)\s*`)
func hasMarkdownAttr(z *html.Tokenizer) bool { func hasMarkdownAttr(z *html.Tokenizer) bool {
for { for {
k, v, more := z.TagAttr() k, v, more := z.TagAttr()
@ -86,12 +100,18 @@ func hasMarkdownAttr(z *html.Tokenizer) bool {
} }
} }
func removeMarkdownAttr(tag []byte) []byte { var markdownAttrRE = regexp.MustCompile(`\s*markdown\s*=[^\s>]*\s*`)
// return the text of a start tag, w/out the markdown attribute
func stripMarkdownAttr(tag []byte) []byte {
tag = markdownAttrRE.ReplaceAll(tag, []byte(" ")) tag = markdownAttrRE.ReplaceAll(tag, []byte(" "))
tag = bytes.Replace(tag, []byte(" >"), []byte(">"), 1) tag = bytes.Replace(tag, []byte(" >"), []byte(">"), 1)
return tag return tag
} }
// called once markdown="1" attribute is detected.
// Collects the HTML tokens into a string, applies markdown to them,
// and writes the result
func processInnerMarkdown(w io.Writer, z *html.Tokenizer) error { func processInnerMarkdown(w io.Writer, z *html.Tokenizer) error {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
depth := 1 depth := 1
@ -111,10 +131,13 @@ loop:
} }
_, err := buf.Write(z.Raw()) _, err := buf.Write(z.Raw())
if err != nil { if err != nil {
panic(err) return err
} }
} }
html := renderMarkdown(buf.Bytes()) html, err := _renderMarkdown(buf.Bytes())
_, err := w.Write(html) if err != nil {
return err
}
_, err = w.Write(html)
return err return err
} }

View File

@ -1,19 +1,35 @@
package renderers package renderers
import ( import (
"log"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func renderMarkdownString(s string) string { func TestRenderMarkdown(t *testing.T) {
return string(renderMarkdown([]byte(s))) require.Equal(t, "<p><em>b</em></p>\n", mustMarkdownString("*b*"))
require.Equal(t, "<div>*b*</div>\n", mustMarkdownString("<div>*b*</div>"))
require.Equal(t, "<div a=1><p><em>b</em></p>\n</div>\n", mustMarkdownString(`<div a=1 markdown="1">*b*</div>`))
require.Equal(t, "<div a=1><p><em>b</em></p>\n</div>\n", mustMarkdownString(`<div a=1 markdown='1'>*b*</div>`))
require.Equal(t, "<div a=1><p><em>b</em></p>\n</div>\n", mustMarkdownString(`<div a=1 markdown=1>*b*</div>`))
_, err := renderMarkdownString(`<div a=1 markdown=1><p></div>`)
require.NotNil(t, err)
} }
func TestRenderMarkdown(t *testing.T) { func mustMarkdownString(md string) string {
require.Equal(t, "<p><em>b</em></p>\n", renderMarkdownString("*b*")) s, err := renderMarkdown([]byte(md))
require.Equal(t, "<div>*b*</div>\n", renderMarkdownString("<div>*b*</div>")) if err != nil {
require.Equal(t, "<div a=1><p><em>b</em></p>\n</div>\n", renderMarkdownString(`<div a=1 markdown="1">*b*</div>`)) log.Fatal(err)
require.Equal(t, "<div a=1><p><em>b</em></p>\n</div>\n", renderMarkdownString(`<div a=1 markdown='1'>*b*</div>`)) }
require.Equal(t, "<div a=1><p><em>b</em></p>\n</div>\n", renderMarkdownString(`<div a=1 markdown=1>*b*</div>`)) return string(s)
}
func renderMarkdownString(md string) (string, error) {
s, err := renderMarkdown([]byte(md))
if err != nil {
return "", err
}
return string(s), err
} }

View File

@ -64,7 +64,10 @@ func (p *Manager) Render(w io.Writer, src []byte, vars liquid.Bindings, filename
return err return err
} }
if p.cfg.IsMarkdown(filename) { if p.cfg.IsMarkdown(filename) {
src = renderMarkdown(src) src, err = renderMarkdown(src)
if err != nil {
return err
}
} }
_, err = w.Write(src) _, err = w.Write(src)
return err return err

View File

@ -19,8 +19,12 @@ type pathError struct {
path string path string
} }
func (p *pathError) Error() string { func (pe *pathError) Error() string {
return fmt.Sprintf("%s: %s", p.path, p.cause) return fmt.Sprintf("%s: %s", pe.path, pe.cause)
}
func (pe *pathError) Path() string {
return pe.path
} }
// WrapPathError returns an error that will print with a path.\ // WrapPathError returns an error that will print with a path.\