mirror of
https://github.com/gohugoio/hugo.git
synced 2025-05-02 00:20:21 +03:00
hugolib: Add .Position to shortcode
To allow for better error logging in shortcodes. Note that this may be expensive to calculate, so this is primarily for error situations. See #5371
This commit is contained in:
parent
6180c85fb8
commit
33a7b36fd4
4 changed files with 112 additions and 10 deletions
|
@ -17,6 +17,8 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/source"
|
||||||
|
|
||||||
errors "github.com/pkg/errors"
|
errors "github.com/pkg/errors"
|
||||||
|
|
||||||
bp "github.com/gohugoio/hugo/bufferpool"
|
bp "github.com/gohugoio/hugo/bufferpool"
|
||||||
|
@ -68,7 +70,7 @@ func (p *Page) mapContent() error {
|
||||||
iter := p.source.parsed.Iterator()
|
iter := p.source.parsed.Iterator()
|
||||||
|
|
||||||
fail := func(err error, i pageparser.Item) error {
|
fail := func(err error, i pageparser.Item) error {
|
||||||
return parseError(err, iter.Input(), i.Pos)
|
return p.parseError(err, iter.Input(), i.Pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the parser is guaranteed to return items in proper order or fail, so …
|
// the parser is guaranteed to return items in proper order or fail, so …
|
||||||
|
@ -194,15 +196,30 @@ func (p *Page) parse(reader io.Reader) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseError(err error, input []byte, pos int) error {
|
func (p *Page) parseError(err error, input []byte, offset int) error {
|
||||||
if herrors.UnwrapFileError(err) != nil {
|
if herrors.UnwrapFileError(err) != nil {
|
||||||
// Use the most specific location.
|
// Use the most specific location.
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lf := []byte("\n")
|
pos := p.posFromInput(input, offset)
|
||||||
input = input[:pos]
|
return herrors.NewFileError("md", -1, pos.LineNumber, pos.ColumnNumber, err)
|
||||||
lineNumber := bytes.Count(input, lf) + 1
|
|
||||||
endOfLastLine := bytes.LastIndex(input, lf)
|
|
||||||
return herrors.NewFileError("md", -1, lineNumber, pos-endOfLastLine, err)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Page) posFromInput(input []byte, offset int) source.Position {
|
||||||
|
lf := []byte("\n")
|
||||||
|
input = input[:offset]
|
||||||
|
lineNumber := bytes.Count(input, lf) + 1
|
||||||
|
endOfLastLine := bytes.LastIndex(input, lf)
|
||||||
|
|
||||||
|
return source.Position{
|
||||||
|
Filename: p.pathOrTitle(),
|
||||||
|
LineNumber: lineNumber,
|
||||||
|
ColumnNumber: offset - endOfLastLine,
|
||||||
|
Offset: offset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Page) posFromPage(offset int) source.Position {
|
||||||
|
return p.posFromInput(p.source.parsed.Input(), offset)
|
||||||
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/source"
|
||||||
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -53,9 +55,23 @@ type ShortcodeWithPage struct {
|
||||||
// this ordinal will represent the position of this shortcode in the page content.
|
// this ordinal will represent the position of this shortcode in the page content.
|
||||||
Ordinal int
|
Ordinal int
|
||||||
|
|
||||||
|
// pos is the position in bytes in the source file. Used for error logging.
|
||||||
|
posInit sync.Once
|
||||||
|
posOffset int
|
||||||
|
pos source.Position
|
||||||
|
|
||||||
scratch *maps.Scratch
|
scratch *maps.Scratch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Position returns this shortcode's detailed position. Note that this information
|
||||||
|
// may be expensive to calculate, so only use this in error situations.
|
||||||
|
func (scp *ShortcodeWithPage) Position() source.Position {
|
||||||
|
scp.posInit.Do(func() {
|
||||||
|
scp.pos = scp.Page.posFromPage(scp.posOffset)
|
||||||
|
})
|
||||||
|
return scp.pos
|
||||||
|
}
|
||||||
|
|
||||||
// Site returns information about the current site.
|
// Site returns information about the current site.
|
||||||
func (scp *ShortcodeWithPage) Site() *SiteInfo {
|
func (scp *ShortcodeWithPage) Site() *SiteInfo {
|
||||||
return scp.Page.Site
|
return scp.Page.Site
|
||||||
|
@ -313,7 +329,7 @@ func renderShortcode(
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
data := &ShortcodeWithPage{Ordinal: sc.ordinal, Params: sc.params, Page: p, Parent: parent}
|
data := &ShortcodeWithPage{Ordinal: sc.ordinal, posOffset: sc.pos, Params: sc.params, Page: p, Parent: parent}
|
||||||
if sc.params != nil {
|
if sc.params != nil {
|
||||||
data.IsNamedParams = reflect.TypeOf(sc.params).Kind() == reflect.Map
|
data.IsNamedParams = reflect.TypeOf(sc.params).Kind() == reflect.Map
|
||||||
}
|
}
|
||||||
|
@ -463,7 +479,7 @@ func (s *shortcodeHandler) executeShortcodesForDelta(p *PageWithoutContent) erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sc := s.shortcodes.getShortcode(k.(scKey).ShortcodePlaceholder)
|
sc := s.shortcodes.getShortcode(k.(scKey).ShortcodePlaceholder)
|
||||||
if sc != nil {
|
if sc != nil {
|
||||||
err = p.errWithFileContext(parseError(_errors.Wrapf(err, "failed to render shortcode %q", sc.name), p.source.parsed.Input(), sc.pos))
|
err = p.errWithFileContext(p.parseError(_errors.Wrapf(err, "failed to render shortcode %q", sc.name), p.source.parsed.Input(), sc.pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
p.s.SendError(err)
|
p.s.SendError(err)
|
||||||
|
@ -505,7 +521,7 @@ func (s *shortcodeHandler) extractShortcode(ordinal int, pt *pageparser.Iterator
|
||||||
var nestedOrdinal = 0
|
var nestedOrdinal = 0
|
||||||
|
|
||||||
fail := func(err error, i pageparser.Item) error {
|
fail := func(err error, i pageparser.Item) error {
|
||||||
return parseError(err, pt.Input(), i.Pos)
|
return p.parseError(err, pt.Input(), i.Pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
Loop:
|
Loop:
|
||||||
|
|
|
@ -1026,3 +1026,39 @@ ordinal: 2 scratch ordinal: 3 scratch get ordinal: 2
|
||||||
ordinal: 4 scratch ordinal: 5 scratch get ordinal: 4`)
|
ordinal: 4 scratch ordinal: 5 scratch get ordinal: 4`)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShortcodePosition(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
assert := require.New(t)
|
||||||
|
|
||||||
|
builder := newTestSitesBuilder(t).WithSimpleConfigFile()
|
||||||
|
|
||||||
|
builder.WithContent("page.md", `---
|
||||||
|
title: "Hugo Rocks!"
|
||||||
|
---
|
||||||
|
|
||||||
|
# doc
|
||||||
|
|
||||||
|
{{< s1 >}}
|
||||||
|
|
||||||
|
`).WithTemplatesAdded("layouts/shortcodes/s1.html", `
|
||||||
|
{{ with .Position }}
|
||||||
|
File: {{ .Filename }}
|
||||||
|
Offset: {{ .Offset }}
|
||||||
|
Line: {{ .LineNumber }}
|
||||||
|
Column: {{ .ColumnNumber }}
|
||||||
|
String: {{ . | safeHTML }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
`).CreateSites().Build(BuildCfg{})
|
||||||
|
|
||||||
|
s := builder.H.Sites[0]
|
||||||
|
assert.Equal(1, len(s.RegularPages))
|
||||||
|
|
||||||
|
builder.AssertFileContent("public/page/index.html",
|
||||||
|
"File: content/page.md",
|
||||||
|
"Line: 7", "Column: 4", "Offset: 40",
|
||||||
|
"String: content/page.md:7:4",
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
33
source/position.go
Normal file
33
source/position.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// 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 source
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Position holds a source position.
|
||||||
|
type Position struct {
|
||||||
|
Filename string // filename, if any
|
||||||
|
Offset int // byte offset, starting at 0
|
||||||
|
LineNumber int // line number, starting at 1
|
||||||
|
ColumnNumber int // column number, starting at 1 (character count per line)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pos Position) String() string {
|
||||||
|
filename := pos.Filename
|
||||||
|
if filename == "" {
|
||||||
|
filename = "<stream>"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%d:%d", filename, pos.LineNumber, pos.ColumnNumber)
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue