Add file (line/col) info to ref/relref errors
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 1 Nov 2018 10:28:30 +0000 (11:28 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Thu, 1 Nov 2018 20:06:35 +0000 (21:06 +0100)
See #5371

23 files changed:
commands/server_errors.go
common/herrors/error_locator.go
common/herrors/error_locator_test.go
common/herrors/file_error.go
common/herrors/file_error_test.go
common/text/position.go [new file with mode: 0644]
common/text/position_test.go [new file with mode: 0644]
common/urls/ref.go [new file with mode: 0644]
go.mod
go.sum
hugolib/hugo_sites_build_errors_test.go
hugolib/page.go
hugolib/page_content.go
hugolib/page_ref.go [new file with mode: 0644]
hugolib/shortcode.go
hugolib/shortcode_test.go
hugolib/site.go
source/position.go [deleted file]
tpl/template.go
tpl/tplimpl/embedded/templates.autogen.go
tpl/tplimpl/embedded/templates/shortcodes/ref.html
tpl/tplimpl/embedded/templates/shortcodes/relref.html
tpl/urls/urls.go

index 11f2ab2811a9bccfe460dc86b600aeb04a7675f7..6a56a1d1915a535095fe3395c18443c87ea63ec4 100644 (file)
@@ -72,7 +72,7 @@ var buildErrorTemplate = `<!doctype html>
                <main>
                        {{ highlight .Error "apl" "noclasses=true,style=monokai" }}
                        {{ with .File }}
-                       {{ $params := printf "noclasses=true,style=monokai,linenos=table,hl_lines=%d,linenostart=%d" (add .Pos 1) (sub .LineNumber .Pos) }}
+                       {{ $params := printf "noclasses=true,style=monokai,linenos=table,hl_lines=%d,linenostart=%d" (add .LinesPos 1) (sub .LineNumber .LinesPos) }}
                        {{ $lexer := .ChromaLexer | default "go-html-template" }}
                        {{  highlight (delimit .Lines "\n") $lexer $params }}
                        {{ end }}
index 5d3f079be6e93ca948ee8bae46b209eb08a69ab4..88cb06c8cf921b8e01dee1fdf51f4aaa4f73ce0a 100644 (file)
 package herrors
 
 import (
-       "fmt"
        "io"
        "io/ioutil"
-       "os"
        "strings"
 
-       "github.com/gohugoio/hugo/common/terminal"
+       "github.com/gohugoio/hugo/common/text"
        "github.com/gohugoio/hugo/helpers"
 
        "github.com/spf13/afero"
 )
 
-var fileErrorFormatFunc func(e ErrorContext) string
-
-func createFileLogFormatter(formatStr string) func(e ErrorContext) string {
-
-       if formatStr == "" {
-               formatStr = "\":file::line::col\""
-       }
-
-       var identifiers = []string{":file", ":line", ":col"}
-       var identifiersFound []string
-
-       for i := range formatStr {
-               for _, id := range identifiers {
-                       if strings.HasPrefix(formatStr[i:], id) {
-                               identifiersFound = append(identifiersFound, id)
-                       }
-               }
-       }
-
-       replacer := strings.NewReplacer(":file", "%s", ":line", "%d", ":col", "%d")
-       format := replacer.Replace(formatStr)
-
-       f := func(e ErrorContext) string {
-               args := make([]interface{}, len(identifiersFound))
-               for i, id := range identifiersFound {
-                       switch id {
-                       case ":file":
-                               args[i] = e.Filename
-                       case ":line":
-                               args[i] = e.LineNumber
-                       case ":col":
-                               args[i] = e.ColumnNumber
-                       }
-               }
-
-               msg := fmt.Sprintf(format, args...)
-
-               if terminal.IsTerminal(os.Stdout) {
-                       return terminal.Notice(msg)
-               }
-
-               return msg
-       }
-
-       return f
-}
-
-func init() {
-       fileErrorFormatFunc = createFileLogFormatter(os.Getenv("HUGO_FILE_LOG_FORMAT"))
-}
-
 // LineMatcher contains the elements used to match an error to a line
 type LineMatcher struct {
-       FileError  FileError
+       Position text.Position
+       Error    error
+
        LineNumber int
        Offset     int
        Line       string
@@ -91,33 +40,34 @@ type LineMatcherFn func(m LineMatcher) bool
 
 // SimpleLineMatcher simply matches by line number.
 var SimpleLineMatcher = func(m LineMatcher) bool {
-       return m.FileError.LineNumber() == m.LineNumber
+       return m.Position.LineNumber == m.LineNumber
 }
 
+var _ text.Positioner = ErrorContext{}
+
 // ErrorContext contains contextual information about an error. This will
 // typically be the lines surrounding some problem in a file.
 type ErrorContext struct {
-       // The source filename.
-       Filename string
 
        // If a match will contain the matched line and up to 2 lines before and after.
        // Will be empty if no match.
        Lines []string
 
        // The position of the error in the Lines above. 0 based.
-       Pos int
-
-       // The linenumber in the source file from where the Lines start. Starting at 1.
-       LineNumber int
+       LinesPos int
 
-       // The column number in the source file. Starting at 1.
-       ColumnNumber int
+       position text.Position
 
        // The lexer to use for syntax highlighting.
        // https://gohugo.io/content-management/syntax-highlighting/#list-of-chroma-highlighting-languages
        ChromaLexer string
 }
 
+// Position returns the text position of this error.
+func (e ErrorContext) Position() text.Position {
+       return e.position
+}
+
 var _ causer = (*ErrorWithFileContext)(nil)
 
 // ErrorWithFileContext is an error with some additional file context related
@@ -128,7 +78,11 @@ type ErrorWithFileContext struct {
 }
 
 func (e *ErrorWithFileContext) Error() string {
-       return fileErrorFormatFunc(e.ErrorContext) + ": " + e.cause.Error()
+       pos := e.Position()
+       if pos.IsValid() {
+               return pos.String() + ": " + e.cause.Error()
+       }
+       return e.cause.Error()
 }
 
 func (e *ErrorWithFileContext) Cause() error {
@@ -163,24 +117,27 @@ func WithFileContext(e error, realFilename string, r io.Reader, matcher LineMatc
 
        var errCtx ErrorContext
 
-       if le.Offset() != -1 {
+       posle := le.Position()
+
+       if posle.Offset != -1 {
                errCtx = locateError(r, le, func(m LineMatcher) bool {
-                       if le.Offset() >= m.Offset && le.Offset() < m.Offset+len(m.Line) {
-                               fe := m.FileError
-                               m.FileError = ToFileErrorWithOffset(fe, -fe.LineNumber()+m.LineNumber)
+                       if posle.Offset >= m.Offset && posle.Offset < m.Offset+len(m.Line) {
+                               lno := posle.LineNumber - m.Position.LineNumber + m.LineNumber
+                               m.Position = text.Position{LineNumber: lno}
                        }
                        return matcher(m)
                })
-
        } else {
                errCtx = locateError(r, le, matcher)
        }
 
-       if errCtx.LineNumber == -1 {
+       pos := &errCtx.position
+
+       if pos.LineNumber == -1 {
                return e, false
        }
 
-       errCtx.Filename = realFilename
+       pos.Filename = realFilename
 
        if le.Type() != "" {
                errCtx.ChromaLexer = chromaLexerFromType(le.Type())
@@ -233,17 +190,20 @@ func locateError(r io.Reader, le FileError, matches LineMatcherFn) ErrorContext
                panic("must provide an error")
        }
 
-       errCtx := ErrorContext{LineNumber: -1, ColumnNumber: 1, Pos: -1}
+       errCtx := ErrorContext{position: text.Position{LineNumber: -1, ColumnNumber: 1, Offset: -1}, LinesPos: -1}
 
        b, err := ioutil.ReadAll(r)
        if err != nil {
                return errCtx
        }
 
+       pos := &errCtx.position
+       lepos := le.Position()
+
        lines := strings.Split(string(b), "\n")
 
-       if le != nil && le.ColumnNumber() >= 0 {
-               errCtx.ColumnNumber = le.ColumnNumber()
+       if le != nil && lepos.ColumnNumber >= 0 {
+               pos.ColumnNumber = lepos.ColumnNumber
        }
 
        lineNo := 0
@@ -252,32 +212,33 @@ func locateError(r io.Reader, le FileError, matches LineMatcherFn) ErrorContext
        for li, line := range lines {
                lineNo = li + 1
                m := LineMatcher{
-                       FileError:  le,
+                       Position:   le.Position(),
+                       Error:      le,
                        LineNumber: lineNo,
                        Offset:     posBytes,
                        Line:       line,
                }
-               if errCtx.Pos == -1 && matches(m) {
-                       errCtx.LineNumber = lineNo
+               if errCtx.LinesPos == -1 && matches(m) {
+                       pos.LineNumber = lineNo
                        break
                }
 
                posBytes += len(line)
        }
 
-       if errCtx.LineNumber != -1 {
-               low := errCtx.LineNumber - 3
+       if pos.LineNumber != -1 {
+               low := pos.LineNumber - 3
                if low < 0 {
                        low = 0
                }
 
-               if errCtx.LineNumber > 2 {
-                       errCtx.Pos = 2
+               if pos.LineNumber > 2 {
+                       errCtx.LinesPos = 2
                } else {
-                       errCtx.Pos = errCtx.LineNumber - 1
+                       errCtx.LinesPos = pos.LineNumber - 1
                }
 
-               high := errCtx.LineNumber + 2
+               high := pos.LineNumber + 2
                if high > len(lines) {
                        high = len(lines)
                }
index 84c0faf89ab7e7369818585e67310e7f8ad3f9b1..2d007016d83def20abd0dfa6f7e071e0372574fc 100644 (file)
@@ -21,18 +21,6 @@ import (
        "github.com/stretchr/testify/require"
 )
 
-func TestCreateFileLogFormatter(t *testing.T) {
-       assert := require.New(t)
-
-       ctx := ErrorContext{Filename: "/my/file.txt", LineNumber: 12, ColumnNumber: 13}
-
-       assert.Equal("/my/file.txt|13|12", createFileLogFormatter(":file|:col|:line")(ctx))
-       assert.Equal("13|/my/file.txt|12", createFileLogFormatter(":col|:file|:line")(ctx))
-       assert.Equal("好:13", createFileLogFormatter("好::col")(ctx))
-       assert.Equal("\"/my/file.txt:12:13\"", createFileLogFormatter("")(ctx))
-
-}
-
 func TestErrorLocator(t *testing.T) {
        assert := require.New(t)
 
@@ -53,8 +41,9 @@ LINE 8
        location := locateErrorInString(lines, lineMatcher)
        assert.Equal([]string{"LINE 3", "LINE 4", "This is THEONE", "LINE 6", "LINE 7"}, location.Lines)
 
-       assert.Equal(5, location.LineNumber)
-       assert.Equal(2, location.Pos)
+       pos := location.Position()
+       assert.Equal(5, pos.LineNumber)
+       assert.Equal(2, location.LinesPos)
 
        assert.Equal([]string{"This is THEONE"}, locateErrorInString(`This is THEONE`, lineMatcher).Lines)
 
@@ -62,32 +51,32 @@ LINE 8
 This is THEONE
 L2
 `, lineMatcher)
-       assert.Equal(2, location.LineNumber)
-       assert.Equal(1, location.Pos)
+       assert.Equal(2, location.Position().LineNumber)
+       assert.Equal(1, location.LinesPos)
        assert.Equal([]string{"L1", "This is THEONE", "L2", ""}, location.Lines)
 
        location = locateErrorInString(`This is THEONE
 L2
 `, lineMatcher)
-       assert.Equal(0, location.Pos)
+       assert.Equal(0, location.LinesPos)
        assert.Equal([]string{"This is THEONE", "L2", ""}, location.Lines)
 
        location = locateErrorInString(`L1
 This THEONE
 `, lineMatcher)
        assert.Equal([]string{"L1", "This THEONE", ""}, location.Lines)
-       assert.Equal(1, location.Pos)
+       assert.Equal(1, location.LinesPos)
 
        location = locateErrorInString(`L1
 L2
 This THEONE
 `, lineMatcher)
        assert.Equal([]string{"L1", "L2", "This THEONE", ""}, location.Lines)
-       assert.Equal(2, location.Pos)
+       assert.Equal(2, location.LinesPos)
 
        location = locateErrorInString("NO MATCH", lineMatcher)
-       assert.Equal(-1, location.LineNumber)
-       assert.Equal(-1, location.Pos)
+       assert.Equal(-1, location.Position().LineNumber)
+       assert.Equal(-1, location.LinesPos)
        assert.Equal(0, len(location.Lines))
 
        lineMatcher = func(m LineMatcher) bool {
@@ -106,8 +95,8 @@ I
 J`, lineMatcher)
 
        assert.Equal([]string{"D", "E", "F", "G", "H"}, location.Lines)
-       assert.Equal(6, location.LineNumber)
-       assert.Equal(2, location.Pos)
+       assert.Equal(6, location.Position().LineNumber)
+       assert.Equal(2, location.LinesPos)
 
        // Test match EOF
        lineMatcher = func(m LineMatcher) bool {
@@ -120,8 +109,8 @@ C
 `, lineMatcher)
 
        assert.Equal([]string{"B", "C", ""}, location.Lines)
-       assert.Equal(4, location.LineNumber)
-       assert.Equal(2, location.Pos)
+       assert.Equal(4, location.Position().LineNumber)
+       assert.Equal(2, location.LinesPos)
 
        offsetMatcher := func(m LineMatcher) bool {
                return m.Offset == 1
@@ -134,7 +123,7 @@ D
 E`, offsetMatcher)
 
        assert.Equal([]string{"A", "B", "C", "D"}, location.Lines)
-       assert.Equal(2, location.LineNumber)
-       assert.Equal(1, location.Pos)
+       assert.Equal(2, location.Position().LineNumber)
+       assert.Equal(1, location.LinesPos)
 
 }
index 49b9f808a48f41e2ed87da64f03112081319fd90..929cc800ff980c70ef2996298579e48dad52cfde 100644 (file)
@@ -16,25 +16,21 @@ package herrors
 import (
        "encoding/json"
 
+       "github.com/gohugoio/hugo/common/text"
+
        "github.com/pkg/errors"
 )
 
-var _ causer = (*fileError)(nil)
+var (
+       _ causer = (*fileError)(nil)
+)
 
 // FileError represents an error when handling a file: Parsing a config file,
 // execute a template etc.
 type FileError interface {
        error
 
-       // Offset gets the error location offset in bytes, starting at 0.
-       // It will return -1 if not provided.
-       Offset() int
-
-       // LineNumber gets the error location, starting at line 1.
-       LineNumber() int
-
-       // Column number gets the column location, starting at 1.
-       ColumnNumber() int
+       text.Positioner
 
        // A string identifying the type of file, e.g. JSON, TOML, markdown etc.
        Type() string
@@ -43,33 +39,16 @@ type FileError interface {
 var _ FileError = (*fileError)(nil)
 
 type fileError struct {
-       offset       int
-       lineNumber   int
-       columnNumber int
-       fileType     string
-
-       cause error
-}
+       position text.Position
 
-type fileErrorWithLineOffset struct {
-       FileError
-       offset int
-}
-
-func (e *fileErrorWithLineOffset) LineNumber() int {
-       return e.FileError.LineNumber() + e.offset
-}
+       fileType string
 
-func (e *fileError) LineNumber() int {
-       return e.lineNumber
-}
-
-func (e *fileError) Offset() int {
-       return e.offset
+       cause error
 }
 
-func (e *fileError) ColumnNumber() int {
-       return e.columnNumber
+// Position returns the text position of this error.
+func (e fileError) Position() text.Position {
+       return e.position
 }
 
 func (e *fileError) Type() string {
@@ -89,7 +68,8 @@ func (f *fileError) Cause() error {
 
 // NewFileError creates a new FileError.
 func NewFileError(fileType string, offset, lineNumber, columnNumber int, err error) FileError {
-       return &fileError{cause: err, fileType: fileType, offset: offset, lineNumber: lineNumber, columnNumber: columnNumber}
+       pos := text.Position{Offset: offset, LineNumber: lineNumber, ColumnNumber: columnNumber}
+       return &fileError{cause: err, fileType: fileType, position: pos}
 }
 
 // UnwrapFileError tries to unwrap a FileError from err.
@@ -111,7 +91,9 @@ func UnwrapFileError(err error) FileError {
 // ToFileErrorWithOffset will return a new FileError with a line number
 // with the given offset from the original.
 func ToFileErrorWithOffset(fe FileError, offset int) FileError {
-       return &fileErrorWithLineOffset{FileError: fe, offset: offset}
+       pos := fe.Position()
+       pos.LineNumber = pos.LineNumber + offset
+       return &fileError{cause: fe, fileType: fe.Type(), position: pos}
 }
 
 // ToFileError will convert the given error to an error supporting
@@ -123,6 +105,7 @@ func ToFileError(fileType string, err error) FileError {
                if fileType == "" {
                        fileType = typ
                }
+
                if lno > 0 || offset != -1 {
                        return NewFileError(fileType, offset, lno, col, err)
                }
index 6acb49603105d312ba9d42c0ffea7c39cbfdc6a9..4108983d32f8fd864dd1034b87b46679c8ea68f5 100644 (file)
@@ -42,17 +42,15 @@ func TestToLineNumberError(t *testing.T) {
        } {
 
                got := ToFileError("template", test.in)
-               if test.offset > 0 {
-                       got = ToFileErrorWithOffset(got.(FileError), test.offset)
-               }
 
                errMsg := fmt.Sprintf("[%d][%T]", i, got)
                le, ok := got.(FileError)
                assert.True(ok)
 
                assert.True(ok, errMsg)
-               assert.Equal(test.lineNumber, le.LineNumber(), errMsg)
-               assert.Equal(test.columnNumber, le.ColumnNumber(), errMsg)
+               pos := le.Position()
+               assert.Equal(test.lineNumber, pos.LineNumber, errMsg)
+               assert.Equal(test.columnNumber, pos.ColumnNumber, errMsg)
                assert.Error(errors.Cause(got))
        }
 
diff --git a/common/text/position.go b/common/text/position.go
new file mode 100644 (file)
index 0000000..0c43c5a
--- /dev/null
@@ -0,0 +1,99 @@
+// 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 text
+
+import (
+       "fmt"
+       "os"
+       "strings"
+
+       "github.com/gohugoio/hugo/common/terminal"
+)
+
+// Positioner represents a thing that knows its position in a text file or stream,
+// typically an error.
+type Positioner interface {
+       Position() Position
+}
+
+// Position holds a source position in a text file or stream.
+type Position struct {
+       Filename     string // filename, if any
+       Offset       int    // byte offset, starting at 0. It's set to -1 if not provided.
+       LineNumber   int    // line number, starting at 1
+       ColumnNumber int    // column number, starting at 1 (character count per line)
+}
+
+func (pos Position) String() string {
+       if pos.Filename == "" {
+               pos.Filename = "<stream>"
+       }
+       return positionStringFormatfunc(pos)
+}
+
+// IsValid returns true if line number is > 0.
+func (pos Position) IsValid() bool {
+       return pos.LineNumber > 0
+}
+
+var positionStringFormatfunc func(p Position) string
+
+func createPositionStringFormatter(formatStr string) func(p Position) string {
+
+       if formatStr == "" {
+               formatStr = "\":file::line::col\""
+       }
+
+       var identifiers = []string{":file", ":line", ":col"}
+       var identifiersFound []string
+
+       for i := range formatStr {
+               for _, id := range identifiers {
+                       if strings.HasPrefix(formatStr[i:], id) {
+                               identifiersFound = append(identifiersFound, id)
+                       }
+               }
+       }
+
+       replacer := strings.NewReplacer(":file", "%s", ":line", "%d", ":col", "%d")
+       format := replacer.Replace(formatStr)
+
+       f := func(pos Position) string {
+               args := make([]interface{}, len(identifiersFound))
+               for i, id := range identifiersFound {
+                       switch id {
+                       case ":file":
+                               args[i] = pos.Filename
+                       case ":line":
+                               args[i] = pos.LineNumber
+                       case ":col":
+                               args[i] = pos.ColumnNumber
+                       }
+               }
+
+               msg := fmt.Sprintf(format, args...)
+
+               if terminal.IsTerminal(os.Stdout) {
+                       return terminal.Notice(msg)
+               }
+
+               return msg
+       }
+
+       return f
+}
+
+func init() {
+       positionStringFormatfunc = createPositionStringFormatter(os.Getenv("HUGO_FILE_LOG_FORMAT"))
+}
diff --git a/common/text/position_test.go b/common/text/position_test.go
new file mode 100644 (file)
index 0000000..a25a3ed
--- /dev/null
@@ -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 text
+
+import (
+       "testing"
+
+       "github.com/stretchr/testify/require"
+)
+
+func TestPositionStringFormatter(t *testing.T) {
+       assert := require.New(t)
+
+       pos := Position{Filename: "/my/file.txt", LineNumber: 12, ColumnNumber: 13, Offset: 14}
+
+       assert.Equal("/my/file.txt|13|12", createPositionStringFormatter(":file|:col|:line")(pos))
+       assert.Equal("13|/my/file.txt|12", createPositionStringFormatter(":col|:file|:line")(pos))
+       assert.Equal("好:13", createPositionStringFormatter("好::col")(pos))
+       assert.Equal("\"/my/file.txt:12:13\"", createPositionStringFormatter("")(pos))
+       assert.Equal("\"/my/file.txt:12:13\"", pos.String())
+
+}
diff --git a/common/urls/ref.go b/common/urls/ref.go
new file mode 100644 (file)
index 0000000..71b00b7
--- /dev/null
@@ -0,0 +1,22 @@
+// 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 urls
+
+// RefLinker is implemented by those who support reference linking.
+// args must contain a path, but can also point to the target
+// language or output format.
+type RefLinker interface {
+       Ref(args map[string]interface{}) (string, error)
+       RelRef(args map[string]interface{}) (string, error)
+}
diff --git a/go.mod b/go.mod
index effb3e03e8ab318be7e826926dd04080b99e8f07..58091d465f8363786097ad57ec12d4b13c56a4db 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -12,6 +12,7 @@ require (
        github.com/bep/debounce v1.1.0
        github.com/bep/gitmap v1.0.0
        github.com/bep/go-tocss v0.5.0
+       github.com/bep/mapstructure v0.0.0-20180511142126-bb74f1db0675
        github.com/chaseadamsio/goorgeous v1.1.0
        github.com/cpuguy83/go-md2man v1.0.8 // indirect
        github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
diff --git a/go.sum b/go.sum
index c54e3b18b2e9b543d9fbdd22db4237d76086a830..b41505de1abffd4f06de6b54c593a0182673b3a6 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -20,6 +20,8 @@ github.com/bep/gitmap v1.0.0 h1:cTTZwq7vpGuhwefKCBDV9UrHnZAPVJTvoWobimrqkUc=
 github.com/bep/gitmap v1.0.0/go.mod h1:g9VRETxFUXNWzMiuxOwcudo6DfZkW9jOsOW0Ft4kYaY=
 github.com/bep/go-tocss v0.5.0 h1:yDIYy1G9hWi7KVTJjvPPDVZB7gxXRRRiv3ds5mqAaCY=
 github.com/bep/go-tocss v0.5.0/go.mod h1:c/+hEVoVvkufrV9Is/CPRHWGGdpcTwNuB48hfxzyYBI=
+github.com/bep/mapstructure v0.0.0-20180511142126-bb74f1db0675 h1:FsEl9Z/kzas/wM6yhI0AQ0H+hHOL0b5EERNs3N5KYcU=
+github.com/bep/mapstructure v0.0.0-20180511142126-bb74f1db0675/go.mod h1:i5WrwxccbJYFpJIcQxfKglzwXT0NE71ElOzBJrO0ODE=
 github.com/chaseadamsio/goorgeous v1.1.0 h1:J9UrYDhzucUMHXsCKG+kICvpR5dT1cqZdVFTYvSlUBk=
 github.com/chaseadamsio/goorgeous v1.1.0/go.mod h1:6QaC0vFoKWYDth94dHFNgRT2YkT5FHdQp/Yx15aAAi0=
 github.com/cpuguy83/go-md2man v1.0.8 h1:DwoNytLphI8hzS2Af4D0dfaEaiSq2bN05mEm4R6vf8M=
index 3af596d7ca3a539558521b9b96bfe613bdb14c5e..fce6ec91527bb7261d6370862df949dc12169072 100644 (file)
@@ -25,7 +25,7 @@ func (t testSiteBuildErrorAsserter) getFileError(err error) *herrors.ErrorWithFi
 
 func (t testSiteBuildErrorAsserter) assertLineNumber(lineNumber int, err error) {
        fe := t.getFileError(err)
-       t.assert.Equal(lineNumber, fe.LineNumber, fmt.Sprintf("[%s]  got => %s\n%s", t.name, fe, trace()))
+       t.assert.Equal(lineNumber, fe.Position().LineNumber, fmt.Sprintf("[%s]  got => %s\n%s", t.name, fe, trace()))
 }
 
 func (t testSiteBuildErrorAsserter) assertErrorMessage(e1, e2 string) {
@@ -42,6 +42,7 @@ func TestSiteBuildErrors(t *testing.T) {
        const (
                yamlcontent = "yamlcontent"
                tomlcontent = "tomlcontent"
+               jsoncontent = "jsoncontent"
                shortcode   = "shortcode"
                base        = "base"
                single      = "single"
@@ -86,8 +87,8 @@ func TestSiteBuildErrors(t *testing.T) {
                        },
                        assertCreateError: func(a testSiteBuildErrorAsserter, err error) {
                                fe := a.getFileError(err)
-                               assert.Equal(5, fe.LineNumber)
-                               assert.Equal(1, fe.ColumnNumber)
+                               assert.Equal(5, fe.Position().LineNumber)
+                               assert.Equal(1, fe.Position().ColumnNumber)
                                assert.Equal("go-html-template", fe.ChromaLexer)
                                a.assertErrorMessage("\"layouts/_default/single.html:5:1\": parse failed: template: _default/single.html:5: unexpected \"}\" in operand", fe.Error())
 
@@ -101,8 +102,8 @@ func TestSiteBuildErrors(t *testing.T) {
                        },
                        assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
                                fe := a.getFileError(err)
-                               assert.Equal(5, fe.LineNumber)
-                               assert.Equal(14, fe.ColumnNumber)
+                               assert.Equal(5, fe.Position().LineNumber)
+                               assert.Equal(14, fe.Position().ColumnNumber)
                                assert.Equal("go-html-template", fe.ChromaLexer)
                                a.assertErrorMessage("\"layouts/_default/single.html:5:14\": execute of template failed", fe.Error())
 
@@ -116,8 +117,8 @@ func TestSiteBuildErrors(t *testing.T) {
                        },
                        assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
                                fe := a.getFileError(err)
-                               assert.Equal(5, fe.LineNumber)
-                               assert.Equal(14, fe.ColumnNumber)
+                               assert.Equal(5, fe.Position().LineNumber)
+                               assert.Equal(14, fe.Position().ColumnNumber)
                                assert.Equal("go-html-template", fe.ChromaLexer)
                                a.assertErrorMessage("\"layouts/_default/single.html:5:14\": execute of template failed", fe.Error())
 
@@ -141,7 +142,7 @@ func TestSiteBuildErrors(t *testing.T) {
                        },
                        assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
                                fe := a.getFileError(err)
-                               assert.Equal(7, fe.LineNumber)
+                               assert.Equal(7, fe.Position().LineNumber)
                                assert.Equal("md", fe.ChromaLexer)
                                // Make sure that it contains both the content file and template
                                a.assertErrorMessage(`content/myyaml.md:7:10": failed to render shortcode "sc"`, fe.Error())
@@ -156,8 +157,8 @@ func TestSiteBuildErrors(t *testing.T) {
                        },
                        assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
                                fe := a.getFileError(err)
-                               assert.Equal(7, fe.LineNumber)
-                               assert.Equal(14, fe.ColumnNumber)
+                               assert.Equal(7, fe.Position().LineNumber)
+                               assert.Equal(14, fe.Position().ColumnNumber)
                                assert.Equal("md", fe.ChromaLexer)
                                a.assertErrorMessage("\"content/myyaml.md:7:14\": failed to extract shortcode: template for shortcode \"nono\" not found", fe.Error())
                        },
@@ -180,21 +181,21 @@ func TestSiteBuildErrors(t *testing.T) {
                        },
                        assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
                                fe := a.getFileError(err)
-                               assert.Equal(6, fe.LineNumber)
+                               assert.Equal(6, fe.Position().LineNumber)
                                assert.Equal("toml", fe.ErrorContext.ChromaLexer)
 
                        },
                },
                {
                        name:     "Invalid JSON front matter",
-                       fileType: tomlcontent,
+                       fileType: jsoncontent,
                        fileFixer: func(content string) string {
                                return strings.Replace(content, "\"description\":", "\"description\"", 1)
                        },
                        assertBuildError: func(a testSiteBuildErrorAsserter, err error) {
                                fe := a.getFileError(err)
 
-                               assert.Equal(3, fe.LineNumber)
+                               assert.Equal(3, fe.Position().LineNumber)
                                assert.Equal("json", fe.ErrorContext.ChromaLexer)
 
                        },
@@ -212,8 +213,8 @@ func TestSiteBuildErrors(t *testing.T) {
                                // This is fixed in latest Go source
                                if strings.Contains(runtime.Version(), "devel") {
                                        fe := a.getFileError(err)
-                                       assert.Equal(5, fe.LineNumber)
-                                       assert.Equal(21, fe.ColumnNumber)
+                                       assert.Equal(5, fe.Position().LineNumber)
+                                       assert.Equal(21, fe.Position().ColumnNumber)
                                } else {
                                        assert.Contains(err.Error(), `execute of template failed: panic in Execute`)
                                }
@@ -286,7 +287,7 @@ Some content.
 
 `))
 
-               b.WithContent("myjson.md", f(tomlcontent, `{
+               b.WithContent("myjson.md", f(jsoncontent, `{
        "title": "This is a title",
        "description": "This is a description."
 }
index 47083e9ef3ee6b4ca8cc159cc08a59f2c36425f6..d91082b3bc674c954130e513a55eeefb9492ef5f 100644 (file)
@@ -21,8 +21,8 @@ import (
        "reflect"
 
        "github.com/gohugoio/hugo/common/maps"
+       "github.com/gohugoio/hugo/common/urls"
        "github.com/gohugoio/hugo/media"
-       _errors "github.com/pkg/errors"
 
        "github.com/gohugoio/hugo/langs"
 
@@ -67,8 +67,16 @@ var (
 
        // Assert that it implements the interface needed for related searches.
        _ related.Document = (*Page)(nil)
+
+       // Page supports ref and relref
+       _ urls.RefLinker = (*Page)(nil)
 )
 
+// Wraps a Page.
+type pageContainer interface {
+       page() *Page
+}
+
 const (
        KindPage = "page"
 
@@ -1863,79 +1871,6 @@ func (p *Page) GetPage(ref string) (*Page, error) {
        return p.s.getPageNew(p, ref)
 }
 
-type refArgs struct {
-       Path         string
-       Lang         string
-       OutputFormat string
-}
-
-func (p *Page) decodeRefArgs(args map[string]interface{}) (refArgs, *SiteInfo, error) {
-       var ra refArgs
-       err := mapstructure.WeakDecode(args, &ra)
-       if err != nil {
-               return ra, nil, nil
-       }
-       s := p.Site
-
-       if ra.Lang != "" && ra.Lang != p.Lang() {
-               // Find correct site
-               found := false
-               for _, ss := range p.s.owner.Sites {
-                       if ss.Lang() == ra.Lang {
-                               found = true
-                               s = &ss.Info
-                       }
-               }
-
-               if !found {
-                       p.s.siteRefLinker.logNotFound(ra.Path, fmt.Sprintf("no site found with lang %q", ra.Lang), p)
-                       return ra, nil, nil
-               }
-       }
-
-       return ra, s, nil
-}
-
-func (p *Page) Ref(argsm map[string]interface{}) (string, error) {
-       args, s, err := p.decodeRefArgs(argsm)
-       if err != nil {
-               return "", _errors.Wrap(err, "invalid arguments to Ref")
-       }
-
-       if s == nil {
-               return p.s.siteRefLinker.notFoundURL, nil
-       }
-
-       if args.Path == "" {
-               return "", nil
-       }
-
-       if args.OutputFormat != "" {
-               return s.Ref(args.Path, p, args.OutputFormat)
-       }
-       return s.Ref(args.Path, p)
-}
-
-func (p *Page) RelRef(argsm map[string]interface{}) (string, error) {
-       args, s, err := p.decodeRefArgs(argsm)
-       if err != nil {
-               return "", _errors.Wrap(err, "invalid arguments to Ref")
-       }
-
-       if s == nil {
-               return p.s.siteRefLinker.notFoundURL, nil
-       }
-
-       if args.Path == "" {
-               return "", nil
-       }
-
-       if args.OutputFormat != "" {
-               return s.RelRef(args.Path, p, args.OutputFormat)
-       }
-       return s.RelRef(args.Path, p)
-}
-
 func (p *Page) String() string {
        if sourceRef := p.absoluteSourceRef(); sourceRef != "" {
                return fmt.Sprintf("Page(%s)", sourceRef)
index b3e8668ef588676a0fcca1ac77030db8fd689321..dc043f8241a14b6e0e8bd98cd446ff2aa21c4114 100644 (file)
@@ -17,13 +17,12 @@ import (
        "bytes"
        "io"
 
-       "github.com/gohugoio/hugo/source"
-
        errors "github.com/pkg/errors"
 
        bp "github.com/gohugoio/hugo/bufferpool"
 
        "github.com/gohugoio/hugo/common/herrors"
+       "github.com/gohugoio/hugo/common/text"
        "github.com/gohugoio/hugo/parser/metadecoders"
        "github.com/gohugoio/hugo/parser/pageparser"
 )
@@ -206,13 +205,13 @@ func (p *Page) parseError(err error, input []byte, offset int) error {
 
 }
 
-func (p *Page) posFromInput(input []byte, offset int) source.Position {
+func (p *Page) posFromInput(input []byte, offset int) text.Position {
        lf := []byte("\n")
        input = input[:offset]
        lineNumber := bytes.Count(input, lf) + 1
        endOfLastLine := bytes.LastIndex(input, lf)
 
-       return source.Position{
+       return text.Position{
                Filename:     p.pathOrTitle(),
                LineNumber:   lineNumber,
                ColumnNumber: offset - endOfLastLine,
@@ -220,6 +219,6 @@ func (p *Page) posFromInput(input []byte, offset int) source.Position {
        }
 }
 
-func (p *Page) posFromPage(offset int) source.Position {
+func (p *Page) posFromPage(offset int) text.Position {
        return p.posFromInput(p.source.parsed.Input(), offset)
 }
diff --git a/hugolib/page_ref.go b/hugolib/page_ref.go
new file mode 100644 (file)
index 0000000..03248de
--- /dev/null
@@ -0,0 +1,100 @@
+// 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 hugolib
+
+import (
+       "fmt"
+
+       "github.com/gohugoio/hugo/common/text"
+
+       "github.com/bep/mapstructure"
+       "github.com/pkg/errors"
+)
+
+type refArgs struct {
+       Path         string
+       Lang         string
+       OutputFormat string
+}
+
+func (p *Page) decodeRefArgs(args map[string]interface{}) (refArgs, *Site, error) {
+       var ra refArgs
+       err := mapstructure.WeakDecode(args, &ra)
+       if err != nil {
+               return ra, nil, nil
+       }
+       s := p.s
+
+       if ra.Lang != "" && ra.Lang != p.Lang() {
+               // Find correct site
+               found := false
+               for _, ss := range p.s.owner.Sites {
+                       if ss.Lang() == ra.Lang {
+                               found = true
+                               s = ss
+                       }
+               }
+
+               if !found {
+                       p.s.siteRefLinker.logNotFound(ra.Path, fmt.Sprintf("no site found with lang %q", ra.Lang), p, text.Position{})
+                       return ra, nil, nil
+               }
+       }
+
+       return ra, s, nil
+}
+
+func (p *Page) Ref(argsm map[string]interface{}) (string, error) {
+       return p.ref(argsm, p)
+}
+
+func (p *Page) ref(argsm map[string]interface{}, source interface{}) (string, error) {
+       args, s, err := p.decodeRefArgs(argsm)
+       if err != nil {
+               return "", errors.Wrap(err, "invalid arguments to Ref")
+       }
+
+       if s == nil {
+               return p.s.siteRefLinker.notFoundURL, nil
+       }
+
+       if args.Path == "" {
+               return "", nil
+       }
+
+       return s.refLink(args.Path, source, false, args.OutputFormat)
+
+}
+
+func (p *Page) RelRef(argsm map[string]interface{}) (string, error) {
+       return p.relRef(argsm, p)
+}
+
+func (p *Page) relRef(argsm map[string]interface{}, source interface{}) (string, error) {
+       args, s, err := p.decodeRefArgs(argsm)
+       if err != nil {
+               return "", errors.Wrap(err, "invalid arguments to Ref")
+       }
+
+       if s == nil {
+               return p.s.siteRefLinker.notFoundURL, nil
+       }
+
+       if args.Path == "" {
+               return "", nil
+       }
+
+       return s.refLink(args.Path, source, true, args.OutputFormat)
+
+}
index 41d8d76c44cc041dbea882f6552c332497c37e10..1860a5e908967fa4e82be52e2857f840ddee7086 100644 (file)
@@ -19,8 +19,6 @@ import (
        "fmt"
        "html/template"
 
-       "github.com/gohugoio/hugo/source"
-
        "reflect"
 
        "regexp"
@@ -34,6 +32,8 @@ import (
        "sync"
 
        "github.com/gohugoio/hugo/common/maps"
+       "github.com/gohugoio/hugo/common/text"
+       "github.com/gohugoio/hugo/common/urls"
        "github.com/gohugoio/hugo/output"
 
        "github.com/gohugoio/hugo/media"
@@ -43,6 +43,12 @@ import (
        "github.com/gohugoio/hugo/tpl"
 )
 
+var (
+       _ urls.RefLinker  = (*ShortcodeWithPage)(nil)
+       _ pageContainer   = (*ShortcodeWithPage)(nil)
+       _ text.Positioner = (*ShortcodeWithPage)(nil)
+)
+
 // ShortcodeWithPage is the "." context in a shortcode template.
 type ShortcodeWithPage struct {
        Params        interface{}
@@ -58,14 +64,14 @@ type ShortcodeWithPage struct {
        // pos is the position in bytes in the source file. Used for error logging.
        posInit   sync.Once
        posOffset int
-       pos       source.Position
+       pos       text.Position
 
        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 {
+func (scp *ShortcodeWithPage) Position() text.Position {
        scp.posInit.Do(func() {
                scp.pos = scp.Page.posFromPage(scp.posOffset)
        })
@@ -77,14 +83,16 @@ func (scp *ShortcodeWithPage) Site() *SiteInfo {
        return scp.Page.Site
 }
 
-// Ref is a shortcut to the Ref method on Page.
+// Ref is a shortcut to the Ref method on Page. It passes itself as a context
+// to get better error messages.
 func (scp *ShortcodeWithPage) Ref(args map[string]interface{}) (string, error) {
-       return scp.Page.Ref(args)
+       return scp.Page.ref(args, scp)
 }
 
-// RelRef is a shortcut to the RelRef method on Page.
+// RelRef is a shortcut to the RelRef method on Page. It passes itself as a context
+// to get better error messages.
 func (scp *ShortcodeWithPage) RelRef(args map[string]interface{}) (string, error) {
-       return scp.Page.RelRef(args)
+       return scp.Page.relRef(args, scp)
 }
 
 // Scratch returns a scratch-pad scoped for this shortcode. This can be used
@@ -147,6 +155,10 @@ func (scp *ShortcodeWithPage) Get(key interface{}) interface{} {
 
 }
 
+func (scp *ShortcodeWithPage) page() *Page {
+       return scp.Page.Page
+}
+
 // Note - this value must not contain any markup syntax
 const shortcodePlaceholderPrefix = "HUGOSHORTCODE"
 
index 2ddecc2ffe8f4448a2fd7ed0713de42e9962f17d..30fdbead3b0a03fa9844d6c18c199da0518ae932 100644 (file)
@@ -1056,9 +1056,9 @@ String: {{ . | safeHTML }}
        assert.Equal(1, len(s.RegularPages))
 
        builder.AssertFileContent("public/page/index.html",
-               "File: content/page.md",
+               filepath.FromSlash("File: content/page.md"),
                "Line: 7", "Column: 4", "Offset: 40",
-               "String: content/page.md:7:4",
+               filepath.FromSlash("String: \"content/page.md:7:4\""),
        )
 
 }
index a4746d8757bc920aa8366ec58c43fbb11a54cff6..d0d84dc60bbe7bd493b70eaa0f74a8abe8f9b53c 100644 (file)
@@ -28,6 +28,8 @@ import (
        "strings"
        "time"
 
+       "github.com/gohugoio/hugo/common/text"
+
        "github.com/gohugoio/hugo/hugofs"
 
        "github.com/gohugoio/hugo/common/herrors"
@@ -493,16 +495,25 @@ func newSiteRefLinker(cfg config.Provider, s *Site) (siteRefLinker, error) {
        return siteRefLinker{s: s, errorLogger: logger, notFoundURL: notFoundURL}, nil
 }
 
-func (s siteRefLinker) logNotFound(ref, what string, p *Page) {
-       if p == nil {
+func (s siteRefLinker) logNotFound(ref, what string, p *Page, position text.Position) {
+       if position.IsValid() {
+               s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q: %s: %s", s.s.Lang(), ref, position.String(), what)
+       } else if p == nil {
                s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q: %s", s.s.Lang(), ref, what)
        } else {
                s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q from page %q: %s", s.s.Lang(), ref, p.pathOrTitle(), what)
        }
-
 }
 
-func (s *siteRefLinker) refLink(ref string, page *Page, relative bool, outputFormat string) (string, error) {
+func (s *siteRefLinker) refLink(ref string, source interface{}, relative bool, outputFormat string) (string, error) {
+
+       var page *Page
+       switch v := source.(type) {
+       case *Page:
+               page = v
+       case pageContainer:
+               page = v.page()
+       }
 
        var refURL *url.URL
        var err error
@@ -520,14 +531,21 @@ func (s *siteRefLinker) refLink(ref string, page *Page, relative bool, outputFor
 
        if refURL.Path != "" {
                target, err := s.s.getPageNew(page, refURL.Path)
+               var pos text.Position
+               if err != nil || target == nil {
+                       if p, ok := source.(text.Positioner); ok {
+                               pos = p.Position()
+
+                       }
+               }
 
                if err != nil {
-                       s.logNotFound(refURL.Path, err.Error(), page)
+                       s.logNotFound(refURL.Path, err.Error(), page, pos)
                        return s.notFoundURL, nil
                }
 
                if target == nil {
-                       s.logNotFound(refURL.Path, "page not found", page)
+                       s.logNotFound(refURL.Path, "page not found", page, pos)
                        return s.notFoundURL, nil
                }
 
@@ -537,7 +555,7 @@ func (s *siteRefLinker) refLink(ref string, page *Page, relative bool, outputFor
                        o := target.OutputFormats().Get(outputFormat)
 
                        if o == nil {
-                               s.logNotFound(refURL.Path, fmt.Sprintf("output format %q", outputFormat), page)
+                               s.logNotFound(refURL.Path, fmt.Sprintf("output format %q", outputFormat), page, pos)
                                return s.notFoundURL, nil
                        }
                        permalinker = o
diff --git a/source/position.go b/source/position.go
deleted file mode 100644 (file)
index 8c1ea3d..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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)
-
-}
index 913b20ed268bb8e46cc5ace4737455595fbbd6bf..3225814c02d7cb5797cfa88754d1cf36e2e9af0a 100644 (file)
@@ -165,14 +165,14 @@ func (t *TemplateAdapter) addFileContext(name string, inerr error) error {
        // Since this can be a composite of multiple template files (single.html + baseof.html etc.)
        // we potentially need to look in both -- and cannot rely on line number alone.
        lineMatcher := func(m herrors.LineMatcher) bool {
-               if m.FileError.LineNumber() != m.LineNumber {
+               if m.Position.LineNumber != m.LineNumber {
                        return false
                }
                if !hasMaster {
                        return true
                }
 
-               identifiers := t.extractIdentifiers(m.FileError.Error())
+               identifiers := t.extractIdentifiers(m.Error.Error())
 
                for _, id := range identifiers {
                        if strings.Contains(m.Line, id) {
index e2d94dc8bf63965119b1ad2b5943ccebac3b5116..defc0f313fe7bbf1db3bed735fe4b46107dfc2e4 100644 (file)
@@ -398,8 +398,8 @@ if (!doNotTrack) {
 </style>
 {{ end }}
 {{ end }}`},
-       {`shortcodes/ref.html`, `{{ ref .Page .Params }}`},
-       {`shortcodes/relref.html`, `{{ relref .Page .Params }}`},
+       {`shortcodes/ref.html`, `{{ ref . .Params }}`},
+       {`shortcodes/relref.html`, `{{ relref . .Params }}`},
        {`shortcodes/twitter.html`, `{{- $pc := .Page.Site.Config.Privacy.Twitter -}}
 {{- if not $pc.Disable -}}
 {{- if $pc.Simple -}}
index c4d9f1d16ec221673ddb47f8101260978cd4113a..cd9c3defc6200aeacc2219a550c92a9e954f44cb 100644 (file)
@@ -1 +1 @@
-{{ ref .Page .Params }}
\ No newline at end of file
+{{ ref . .Params }}
\ No newline at end of file
index d3a31ea4449defc8f794a9a473a71e9358bb868e..82005bd82b4592f7275a78509b694fc224e12e7d 100644 (file)
@@ -1 +1 @@
-{{ relref .Page .Params }}
\ No newline at end of file
+{{ relref . .Params }}
\ No newline at end of file
index 8f6f92f3db675d89200e5b3b8180e92a940bd98e..7abf45ba21844b7ba502d45e2bd8521049576f20 100644 (file)
@@ -20,10 +20,10 @@ import (
        "html/template"
        "net/url"
 
+       "github.com/gohugoio/hugo/common/urls"
+       "github.com/gohugoio/hugo/deps"
        _errors "github.com/pkg/errors"
        "github.com/russross/blackfriday"
-
-       "github.com/gohugoio/hugo/deps"
        "github.com/spf13/cast"
 )
 
@@ -91,14 +91,9 @@ func (ns *Namespace) Anchorize(a interface{}) (string, error) {
        return blackfriday.SanitizedAnchorName(s), nil
 }
 
-type reflinker interface {
-       Ref(args map[string]interface{}) (string, error)
-       RelRef(args map[string]interface{}) (string, error)
-}
-
 // Ref returns the absolute URL path to a given content item.
 func (ns *Namespace) Ref(in interface{}, args interface{}) (template.HTML, error) {
-       p, ok := in.(reflinker)
+       p, ok := in.(urls.RefLinker)
        if !ok {
                return "", errors.New("invalid Page received in Ref")
        }
@@ -112,7 +107,7 @@ func (ns *Namespace) Ref(in interface{}, args interface{}) (template.HTML, error
 
 // RelRef returns the relative URL path to a given content item.
 func (ns *Namespace) RelRef(in interface{}, args interface{}) (template.HTML, error) {
-       p, ok := in.(reflinker)
+       p, ok := in.(urls.RefLinker)
        if !ok {
                return "", errors.New("invalid Page received in RelRef")
        }