return -1
}
+// ContainsMatcher is a line matcher that matches by line content.
+func ContainsMatcher(text string) func(m LineMatcher) int {
+ return func(m LineMatcher) int {
+ if idx := strings.Index(m.Line, text); idx != -1 {
+ return idx + 1
+ }
+ return -1
+ }
+}
+
// ErrorContext contains contextual information about an error. This will
// typically be the lines surrounding some problem in a file.
type ErrorContext struct {
}
}
- return herrors.NewFileErrorFromFile(err, filename, p.s.SourceSpec.Fs.Source, herrors.NopLineMatcher)
+ lineMatcher := herrors.NopLineMatcher
+
+ if textSegmentErr, ok := err.(*herrors.TextSegmentError); ok {
+ lineMatcher = herrors.ContainsMatcher(textSegmentErr.Segment)
+ }
+
+ return herrors.NewFileErrorFromFile(err, filename, p.s.SourceSpec.Fs.Source, lineMatcher)
}
"strings"
"testing"
+ qt "github.com/frankban/quicktest"
+
"github.com/gohugoio/hugo/hugolib"
)
}
}
+
+// Issue 10835
+func TestAttributesValidation(t *testing.T) {
+ t.Parallel()
+
+ files := `
+-- hugo.toml --
+disableKinds = ["taxonomy", "term"]
+-- content/p1.md --
+---
+title: "p1"
+---
+
+## Issue 10835
+
+§§§bash { color=red dimensions=300x200 }
+Hello, World!
+§§§
+
+-- layouts/index.html --
+-- layouts/_default/single.html --
+{{ .Content }}
+-- layouts/_default/_markup/render-codeblock.html --
+Attributes: {{ .Attributes }}|Type: {{ .Type }}|
+`
+
+ b, err := hugolib.NewIntegrationTestBuilder(
+ hugolib.IntegrationTestConfig{
+ T: t,
+ TxtarString: files,
+ },
+ ).BuildE()
+
+ b.Assert(err, qt.Not(qt.IsNil))
+ b.Assert(err.Error(), qt.Contains, "p1.md:7:9\": failed to parse Markdown attributes; you may need to quote the values")
+
+}
import (
"bytes"
+ "errors"
"fmt"
"strings"
"sync"
}
// IsDefaultCodeBlockRendererProvider
- attrs := getAttributes(n.b, info)
+ attrs, attrStr, err := getAttributes(n.b, info)
+ if err != nil {
+ return ast.WalkStop, &herrors.TextSegmentError{Err: err, Segment: attrStr}
+ }
cbctx := &codeBlockContext{
page: ctx.DocumentContext().Document,
lang: lang,
cr := renderer.(hooks.CodeBlockRenderer)
- err := cr.RenderCodeblock(
+ err = cr.RenderCodeblock(
ctx.RenderContext().Ctx,
w,
cbctx,
return lang
}
-func getAttributes(node *ast.FencedCodeBlock, infostr []byte) []ast.Attribute {
+func getAttributes(node *ast.FencedCodeBlock, infostr []byte) ([]ast.Attribute, string, error) {
if node.Attributes() != nil {
- return node.Attributes()
+ return node.Attributes(), "", nil
}
if infostr != nil {
attrStartIdx := -1
+ attrEndIdx := -1
for idx, char := range infostr {
- if char == '{' {
+ if attrEndIdx == -1 && char == '{' {
attrStartIdx = idx
+ }
+ if attrStartIdx != -1 && char == '}' {
+ attrEndIdx = idx
break
}
}
- if attrStartIdx != -1 {
+ if attrStartIdx != -1 && attrEndIdx != -1 {
n := ast.NewTextBlock() // dummy node for storing attributes
- attrStr := infostr[attrStartIdx:]
+ attrStr := infostr[attrStartIdx : attrEndIdx+1]
if attrs, hasAttr := parser.ParseAttributes(text.NewReader(attrStr)); hasAttr {
for _, attr := range attrs {
n.SetAttribute(attr.Name, attr.Value)
}
- return n.Attributes()
+ return n.Attributes(), "", nil
+ } else {
+ return nil, string(attrStr), errors.New("failed to parse Markdown attributes; you may need to quote the values")
}
}
}
- return nil
+ return nil, "", nil
}