mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-29 22:29:56 +02:00
@@ -52,6 +52,16 @@ var NopLineMatcher = func(m LineMatcher) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// OffsetMatcher is a line matcher that matches by offset.
|
||||
var OffsetMatcher = func(m LineMatcher) int {
|
||||
if m.Offset+len(m.Line) >= m.Position.Offset {
|
||||
// We found the line, but return 0 to signal that we want to determine
|
||||
// the column from the error.
|
||||
return 0
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// ErrorContext contains contextual information about an error. This will
|
||||
// typically be the lines surrounding some problem in a file.
|
||||
type ErrorContext struct {
|
||||
|
@@ -19,6 +19,8 @@ import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/bep/godartsass"
|
||||
"github.com/bep/golibsass/libsass/libsasserrors"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
"github.com/pelletier/go-toml/v2"
|
||||
@@ -132,7 +134,22 @@ func (e fileError) Position() text.Position {
|
||||
}
|
||||
|
||||
func (e *fileError) Error() string {
|
||||
return fmt.Sprintf("%s: %s", e.position, e.cause)
|
||||
return fmt.Sprintf("%s: %s", e.position, e.causeString())
|
||||
}
|
||||
|
||||
func (e *fileError) causeString() string {
|
||||
if e.cause == nil {
|
||||
return ""
|
||||
}
|
||||
switch v := e.cause.(type) {
|
||||
// Avoid repeating the file info in the error message.
|
||||
case godartsass.SassError:
|
||||
return v.Message
|
||||
case libsasserrors.Error:
|
||||
return v.Message
|
||||
default:
|
||||
return v.Error()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *fileError) Unwrap() error {
|
||||
@@ -140,9 +157,17 @@ func (e *fileError) Unwrap() error {
|
||||
}
|
||||
|
||||
// NewFileError creates a new FileError that wraps err.
|
||||
// It will try to extract the filename and line number from err.
|
||||
func NewFileError(err error) FileError {
|
||||
// Filetype is used to determine the Chroma lexer to use.
|
||||
fileType, pos := extractFileTypePos(err)
|
||||
return &fileError{cause: err, fileType: fileType, position: pos}
|
||||
}
|
||||
|
||||
// NewFileErrorFromName creates a new FileError that wraps err.
|
||||
// The value for name should identify the file, the best
|
||||
// being the full filename to the file on disk.
|
||||
func NewFileError(err error, name string) FileError {
|
||||
func NewFileErrorFromName(err error, name string) FileError {
|
||||
// Filetype is used to determine the Chroma lexer to use.
|
||||
fileType, pos := extractFileTypePos(err)
|
||||
pos.Filename = name
|
||||
@@ -165,6 +190,23 @@ func NewFileErrorFromPos(err error, pos text.Position) FileError {
|
||||
|
||||
}
|
||||
|
||||
func NewFileErrorFromFileInErr(err error, fs afero.Fs, linematcher LineMatcherFn) FileError {
|
||||
fe := NewFileError(err)
|
||||
pos := fe.Position()
|
||||
if pos.Filename == "" {
|
||||
return fe
|
||||
}
|
||||
|
||||
f, realFilename, err2 := openFile(pos.Filename, fs)
|
||||
if err2 != nil {
|
||||
return fe
|
||||
}
|
||||
|
||||
pos.Filename = realFilename
|
||||
defer f.Close()
|
||||
return fe.UpdateContent(f, linematcher)
|
||||
}
|
||||
|
||||
func NewFileErrorFromFileInPos(err error, pos text.Position, fs afero.Fs, linematcher LineMatcherFn) FileError {
|
||||
if err == nil {
|
||||
panic("err is nil")
|
||||
@@ -185,10 +227,10 @@ func NewFileErrorFromFile(err error, filename string, fs afero.Fs, linematcher L
|
||||
}
|
||||
f, realFilename, err2 := openFile(filename, fs)
|
||||
if err2 != nil {
|
||||
return NewFileError(err, realFilename)
|
||||
return NewFileErrorFromName(err, realFilename)
|
||||
}
|
||||
defer f.Close()
|
||||
return NewFileError(err, realFilename).UpdateContent(f, linematcher)
|
||||
return NewFileErrorFromName(err, realFilename).UpdateContent(f, linematcher)
|
||||
}
|
||||
|
||||
func openFile(filename string, fs afero.Fs) (afero.File, string, error) {
|
||||
@@ -223,8 +265,15 @@ func Cause(err error) error {
|
||||
|
||||
func extractFileTypePos(err error) (string, text.Position) {
|
||||
err = Cause(err)
|
||||
|
||||
var fileType string
|
||||
|
||||
// LibSass, DartSass
|
||||
if pos := extractPosition(err); pos.LineNumber > 0 || pos.Offset > 0 {
|
||||
_, fileType = paths.FileAndExtNoDelimiter(pos.Filename)
|
||||
return fileType, pos
|
||||
}
|
||||
|
||||
// Default to line 1 col 1 if we don't find any better.
|
||||
pos := text.Position{
|
||||
Offset: -1,
|
||||
@@ -259,6 +308,10 @@ func extractFileTypePos(err error) (string, text.Position) {
|
||||
}
|
||||
}
|
||||
|
||||
if fileType == "" && pos.Filename != "" {
|
||||
_, fileType = paths.FileAndExtNoDelimiter(pos.Filename)
|
||||
}
|
||||
|
||||
return fileType, pos
|
||||
}
|
||||
|
||||
@@ -322,3 +375,20 @@ func exctractLineNumberAndColumnNumber(e error) (int, int) {
|
||||
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
func extractPosition(e error) (pos text.Position) {
|
||||
switch v := e.(type) {
|
||||
case godartsass.SassError:
|
||||
span := v.Span
|
||||
start := span.Start
|
||||
filename, _ := paths.UrlToFilename(span.Url)
|
||||
pos.Filename = filename
|
||||
pos.Offset = start.Offset
|
||||
pos.ColumnNumber = start.Column
|
||||
case libsasserrors.Error:
|
||||
pos.Filename = v.File
|
||||
pos.LineNumber = v.Line
|
||||
pos.ColumnNumber = v.Column
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ func TestNewFileError(t *testing.T) {
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
fe := NewFileError(errors.New("bar"), "foo.html")
|
||||
fe := NewFileErrorFromName(errors.New("bar"), "foo.html")
|
||||
c.Assert(fe.Error(), qt.Equals, `"foo.html:1:1": bar`)
|
||||
|
||||
lines := ""
|
||||
@@ -70,7 +70,7 @@ func TestNewFileErrorExtractFromMessage(t *testing.T) {
|
||||
{errors.New(`execute of template failed: template: index.html:2:5: executing "index.html" at <partial "foo.html" .>: error calling partial: "/layouts/partials/foo.html:3:6": execute of template failed: template: partials/foo.html:3:6: executing "partials/foo.html" at <.ThisDoesNotExist>: can't evaluate field ThisDoesNotExist in type *hugolib.pageStat`), 0, 2, 5},
|
||||
} {
|
||||
|
||||
got := NewFileError(test.in, "test.txt")
|
||||
got := NewFileErrorFromName(test.in, "test.txt")
|
||||
|
||||
errMsg := qt.Commentf("[%d][%T]", i, got)
|
||||
|
||||
|
@@ -17,6 +17,7 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -152,3 +153,29 @@ func Uglify(in string) string {
|
||||
// /section/name.html -> /section/name.html
|
||||
return path.Clean(in)
|
||||
}
|
||||
|
||||
// UrlToFilename converts the URL s to a filename.
|
||||
// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
|
||||
func UrlToFilename(s string) (string, bool) {
|
||||
u, err := url.ParseRequestURI(s)
|
||||
|
||||
if err != nil {
|
||||
return filepath.FromSlash(s), false
|
||||
}
|
||||
|
||||
p := u.Path
|
||||
|
||||
if p == "" {
|
||||
p, _ = url.QueryUnescape(u.Opaque)
|
||||
return filepath.FromSlash(p), true
|
||||
}
|
||||
|
||||
p = filepath.FromSlash(p)
|
||||
|
||||
if u.Host != "" {
|
||||
// C:\data\file.txt
|
||||
p = strings.ToUpper(u.Host) + ":" + p
|
||||
}
|
||||
|
||||
return p, true
|
||||
}
|
||||
|
Reference in New Issue
Block a user