mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
parent
4b189d8fd9
commit
fc9f315d86
24 changed files with 306 additions and 69 deletions
|
@ -482,6 +482,7 @@ func removeErrorPrefixFromLog(content string) string {
|
||||||
var logReplacer = strings.NewReplacer(
|
var logReplacer = strings.NewReplacer(
|
||||||
"can't", "can’t", // Chroma lexer does'nt do well with "can't"
|
"can't", "can’t", // Chroma lexer does'nt do well with "can't"
|
||||||
"*hugolib.pageState", "page.Page", // Page is the public interface.
|
"*hugolib.pageState", "page.Page", // Page is the public interface.
|
||||||
|
"Rebuild failed:", "",
|
||||||
)
|
)
|
||||||
|
|
||||||
func cleanErrorLog(content string) string {
|
func cleanErrorLog(content string) string {
|
||||||
|
|
|
@ -52,6 +52,16 @@ var NopLineMatcher = func(m LineMatcher) int {
|
||||||
return 1
|
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
|
// ErrorContext contains contextual information about an error. This will
|
||||||
// typically be the lines surrounding some problem in a file.
|
// typically be the lines surrounding some problem in a file.
|
||||||
type ErrorContext struct {
|
type ErrorContext struct {
|
||||||
|
|
|
@ -19,6 +19,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/bep/godartsass"
|
||||||
|
"github.com/bep/golibsass/libsass/libsasserrors"
|
||||||
"github.com/gohugoio/hugo/common/paths"
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
"github.com/gohugoio/hugo/common/text"
|
"github.com/gohugoio/hugo/common/text"
|
||||||
"github.com/pelletier/go-toml/v2"
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
@ -132,7 +134,22 @@ func (e fileError) Position() text.Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *fileError) Error() string {
|
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 {
|
func (e *fileError) Unwrap() error {
|
||||||
|
@ -140,9 +157,17 @@ func (e *fileError) Unwrap() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileError creates a new FileError that wraps err.
|
// 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
|
// The value for name should identify the file, the best
|
||||||
// being the full filename to the file on disk.
|
// 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 is used to determine the Chroma lexer to use.
|
||||||
fileType, pos := extractFileTypePos(err)
|
fileType, pos := extractFileTypePos(err)
|
||||||
pos.Filename = name
|
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 {
|
func NewFileErrorFromFileInPos(err error, pos text.Position, fs afero.Fs, linematcher LineMatcherFn) FileError {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
panic("err is 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)
|
f, realFilename, err2 := openFile(filename, fs)
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return NewFileError(err, realFilename)
|
return NewFileErrorFromName(err, realFilename)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
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) {
|
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) {
|
func extractFileTypePos(err error) (string, text.Position) {
|
||||||
err = Cause(err)
|
err = Cause(err)
|
||||||
|
|
||||||
var fileType string
|
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.
|
// Default to line 1 col 1 if we don't find any better.
|
||||||
pos := text.Position{
|
pos := text.Position{
|
||||||
Offset: -1,
|
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
|
return fileType, pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,3 +375,20 @@ func exctractLineNumberAndColumnNumber(e error) (int, int) {
|
||||||
|
|
||||||
return -1, -1
|
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)
|
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`)
|
c.Assert(fe.Error(), qt.Equals, `"foo.html:1:1": bar`)
|
||||||
|
|
||||||
lines := ""
|
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},
|
{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)
|
errMsg := qt.Commentf("[%d][%T]", i, got)
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -152,3 +153,29 @@ func Uglify(in string) string {
|
||||||
// /section/name.html -> /section/name.html
|
// /section/name.html -> /section/name.html
|
||||||
return path.Clean(in)
|
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
|
||||||
|
}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -11,7 +11,7 @@ require (
|
||||||
github.com/bep/gitmap v1.1.2
|
github.com/bep/gitmap v1.1.2
|
||||||
github.com/bep/goat v0.5.0
|
github.com/bep/goat v0.5.0
|
||||||
github.com/bep/godartsass v0.14.0
|
github.com/bep/godartsass v0.14.0
|
||||||
github.com/bep/golibsass v1.0.0
|
github.com/bep/golibsass v1.1.0
|
||||||
github.com/bep/gowebp v0.1.0
|
github.com/bep/gowebp v0.1.0
|
||||||
github.com/bep/overlayfs v0.6.0
|
github.com/bep/overlayfs v0.6.0
|
||||||
github.com/bep/tmc v0.5.1
|
github.com/bep/tmc v0.5.1
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -175,6 +175,8 @@ github.com/bep/godartsass v0.14.0 h1:pPb6XkpyDEppS+wK0veh7OXDQc4xzOJI9Qcjb743UeQ
|
||||||
github.com/bep/godartsass v0.14.0/go.mod h1:6LvK9RftsXMxGfsA0LDV12AGc4Jylnu6NgHL+Q5/pE8=
|
github.com/bep/godartsass v0.14.0/go.mod h1:6LvK9RftsXMxGfsA0LDV12AGc4Jylnu6NgHL+Q5/pE8=
|
||||||
github.com/bep/golibsass v1.0.0 h1:gNguBMSDi5yZEZzVZP70YpuFQE3qogJIGUlrVILTmOw=
|
github.com/bep/golibsass v1.0.0 h1:gNguBMSDi5yZEZzVZP70YpuFQE3qogJIGUlrVILTmOw=
|
||||||
github.com/bep/golibsass v1.0.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
|
github.com/bep/golibsass v1.0.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
|
||||||
|
github.com/bep/golibsass v1.1.0 h1:pjtXr00IJZZaOdfryNa9wARTB3Q0BmxC3/V1KNcgyTw=
|
||||||
|
github.com/bep/golibsass v1.1.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
|
||||||
github.com/bep/gowebp v0.1.0 h1:4/iQpfnxHyXs3x/aTxMMdOpLEQQhFmF6G7EieWPTQyo=
|
github.com/bep/gowebp v0.1.0 h1:4/iQpfnxHyXs3x/aTxMMdOpLEQQhFmF6G7EieWPTQyo=
|
||||||
github.com/bep/gowebp v0.1.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
github.com/bep/gowebp v0.1.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||||
github.com/bep/overlayfs v0.6.0 h1:sgLcq/qtIzbaQNl2TldGXOkHvqeZB025sPvHOQL+DYo=
|
github.com/bep/overlayfs v0.6.0 h1:sgLcq/qtIzbaQNl2TldGXOkHvqeZB025sPvHOQL+DYo=
|
||||||
|
|
|
@ -168,8 +168,9 @@ func (s *IntegrationTestBuilder) destinationExists(filename string) bool {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestBuilder) AssertIsFileError(err error) {
|
func (s *IntegrationTestBuilder) AssertIsFileError(err error) herrors.FileError {
|
||||||
s.Assert(err, qt.ErrorAs, new(herrors.FileError))
|
s.Assert(err, qt.ErrorAs, new(herrors.FileError))
|
||||||
|
return herrors.UnwrapFileError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *IntegrationTestBuilder) AssertRenderCountContent(count int) {
|
func (s *IntegrationTestBuilder) AssertRenderCountContent(count int) {
|
||||||
|
|
|
@ -788,7 +788,7 @@ func (p *pageState) outputFormat() (f output.Format) {
|
||||||
|
|
||||||
func (p *pageState) parseError(err error, input []byte, offset int) error {
|
func (p *pageState) parseError(err error, input []byte, offset int) error {
|
||||||
pos := p.posFromInput(input, offset)
|
pos := p.posFromInput(input, offset)
|
||||||
return herrors.NewFileError(err, p.File().Filename()).UpdatePosition(pos)
|
return herrors.NewFileErrorFromName(err, p.File().Filename()).UpdatePosition(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pageState) pathOrTitle() string {
|
func (p *pageState) pathOrTitle() string {
|
||||||
|
|
|
@ -298,7 +298,7 @@ func renderShortcode(
|
||||||
var err error
|
var err error
|
||||||
tmpl, err = s.TextTmpl().Parse(templName, templStr)
|
tmpl, err = s.TextTmpl().Parse(templName, templStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fe := herrors.NewFileError(err, p.File().Filename())
|
fe := herrors.NewFileErrorFromName(err, p.File().Filename())
|
||||||
pos := fe.Position()
|
pos := fe.Position()
|
||||||
pos.LineNumber += p.posOffset(sc.pos).LineNumber
|
pos.LineNumber += p.posOffset(sc.pos).LineNumber
|
||||||
fe = fe.UpdatePosition(pos)
|
fe = fe.UpdatePosition(pos)
|
||||||
|
@ -391,7 +391,7 @@ func renderShortcode(
|
||||||
result, err := renderShortcodeWithPage(s.Tmpl(), tmpl, data)
|
result, err := renderShortcodeWithPage(s.Tmpl(), tmpl, data)
|
||||||
|
|
||||||
if err != nil && sc.isInline {
|
if err != nil && sc.isInline {
|
||||||
fe := herrors.NewFileError(err, p.File().Filename())
|
fe := herrors.NewFileErrorFromName(err, p.File().Filename())
|
||||||
pos := fe.Position()
|
pos := fe.Position()
|
||||||
pos.LineNumber += p.posOffset(sc.pos).LineNumber
|
pos.LineNumber += p.posOffset(sc.pos).LineNumber
|
||||||
fe = fe.UpdatePosition(pos)
|
fe = fe.UpdatePosition(pos)
|
||||||
|
|
|
@ -138,6 +138,6 @@ func errWithFileContext(inerr error, r source.File) error {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
return herrors.NewFileError(inerr, realFilename).UpdateContent(f, nil)
|
return herrors.NewFileErrorFromName(inerr, realFilename).UpdateContent(f, nil)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -260,7 +260,7 @@ func (d Decoder) unmarshalORG(data []byte, v any) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func toFileError(f Format, data []byte, err error) error {
|
func toFileError(f Format, data []byte, err error) error {
|
||||||
return herrors.NewFileError(err, fmt.Sprintf("_stream.%s", f)).UpdateContent(bytes.NewReader(data), nil)
|
return herrors.NewFileErrorFromName(err, fmt.Sprintf("_stream.%s", f)).UpdateContent(bytes.NewReader(data), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stringifyMapKeys recurses into in and changes all instances of
|
// stringifyMapKeys recurses into in and changes all instances of
|
||||||
|
|
|
@ -165,7 +165,7 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fe := herrors.
|
fe := herrors.
|
||||||
NewFileError(errors.New(errorMessage), path).
|
NewFileErrorFromName(errors.New(errorMessage), path).
|
||||||
UpdatePosition(text.Position{Offset: -1, LineNumber: loc.Line, ColumnNumber: loc.Column}).
|
UpdatePosition(text.Position{Offset: -1, LineNumber: loc.Line, ColumnNumber: loc.Column}).
|
||||||
UpdateContent(f, nil)
|
UpdateContent(f, nil)
|
||||||
|
|
||||||
|
|
|
@ -183,6 +183,6 @@ func TestTransformPostCSSImporSkipInlineImportsNotFound(t *testing.T) {
|
||||||
TxtarString: files,
|
TxtarString: files,
|
||||||
}).Build()
|
}).Build()
|
||||||
|
|
||||||
s.AssertFileContent("public/css/styles.css", filepath.FromSlash(`@import "components/doesnotexist.css";`))
|
s.AssertFileContent("public/css/styles.css", `@import "components/doesnotexist.css";`)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -314,7 +314,7 @@ func (imp *importResolver) importRecursive(
|
||||||
LineNumber: offset + 1,
|
LineNumber: offset + 1,
|
||||||
ColumnNumber: column + 1,
|
ColumnNumber: column + 1,
|
||||||
}
|
}
|
||||||
return 0, "", herrors.NewFileErrorFromFileInPos(fmt.Errorf("failed to resolve CSS @import %q", filename), pos, imp.fs, nil)
|
return 0, "", herrors.NewFileErrorFromFileInPos(fmt.Errorf("failed to resolve CSS @import \"%s\"", filename), pos, imp.fs, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
i--
|
i--
|
||||||
|
@ -421,7 +421,7 @@ func (imp *importResolver) toFileError(output string) error {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
ferr := herrors.NewFileError(inErr, realFilename)
|
ferr := herrors.NewFileErrorFromName(inErr, realFilename)
|
||||||
pos := ferr.Position()
|
pos := ferr.Position()
|
||||||
pos.LineNumber = file.Offset + 1
|
pos.LineNumber = file.Offset + 1
|
||||||
return ferr.UpdatePosition(pos).UpdateContent(f, nil)
|
return ferr.UpdatePosition(pos).UpdateContent(f, nil)
|
||||||
|
|
|
@ -20,7 +20,9 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/herrors"
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
|
"github.com/gohugoio/hugo/hugofs"
|
||||||
"github.com/gohugoio/hugo/hugolib/filesystems"
|
"github.com/gohugoio/hugo/hugolib/filesystems"
|
||||||
"github.com/gohugoio/hugo/resources"
|
"github.com/gohugoio/hugo/resources"
|
||||||
"github.com/gohugoio/hugo/resources/resource"
|
"github.com/gohugoio/hugo/resources/resource"
|
||||||
|
@ -33,6 +35,10 @@ import (
|
||||||
// used as part of the cache key.
|
// used as part of the cache key.
|
||||||
const transformationName = "tocss-dart"
|
const transformationName = "tocss-dart"
|
||||||
|
|
||||||
|
// See https://github.com/sass/dart-sass-embedded/issues/24
|
||||||
|
// Note: This prefix must be all lower case.
|
||||||
|
const dartSassStdinPrefix = "hugostdin:"
|
||||||
|
|
||||||
func New(fs *filesystems.SourceFilesystem, rs *resources.Spec) (*Client, error) {
|
func New(fs *filesystems.SourceFilesystem, rs *resources.Spec) (*Client, error) {
|
||||||
if !Supports() {
|
if !Supports() {
|
||||||
return &Client{dartSassNotAvailable: true}, nil
|
return &Client{dartSassNotAvailable: true}, nil
|
||||||
|
@ -44,7 +50,7 @@ func New(fs *filesystems.SourceFilesystem, rs *resources.Spec) (*Client, error)
|
||||||
|
|
||||||
transpiler, err := godartsass.Start(godartsass.Options{
|
transpiler, err := godartsass.Start(godartsass.Options{
|
||||||
LogEventHandler: func(event godartsass.LogEvent) {
|
LogEventHandler: func(event godartsass.LogEvent) {
|
||||||
message := strings.ReplaceAll(event.Message, stdinPrefix, "")
|
message := strings.ReplaceAll(event.Message, dartSassStdinPrefix, "")
|
||||||
switch event.Type {
|
switch event.Type {
|
||||||
case godartsass.LogEventTypeDebug:
|
case godartsass.LogEventTypeDebug:
|
||||||
// Log as Info for now, we may adjust this if it gets too chatty.
|
// Log as Info for now, we may adjust this if it gets too chatty.
|
||||||
|
@ -94,7 +100,7 @@ func (c *Client) toCSS(args godartsass.Args, src io.Reader) (godartsass.Result,
|
||||||
if err.Error() == "unexpected EOF" {
|
if err.Error() == "unexpected EOF" {
|
||||||
return res, fmt.Errorf("got unexpected EOF when executing %q. The user running hugo must have read and execute permissions on this program. With execute permissions only, this error is thrown.", dartSassEmbeddedBinaryName)
|
return res, fmt.Errorf("got unexpected EOF when executing %q. The user running hugo must have read and execute permissions on this program. With execute permissions only, this error is thrown.", dartSassEmbeddedBinaryName)
|
||||||
}
|
}
|
||||||
return res, err
|
return res, herrors.NewFileErrorFromFileInErr(err, hugofs.Os, herrors.OffsetMatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, err
|
return res, err
|
||||||
|
|
|
@ -14,8 +14,10 @@
|
||||||
package dartsass_test
|
package dartsass_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
qt "github.com/frankban/quicktest"
|
||||||
"github.com/gohugoio/hugo/hugolib"
|
"github.com/gohugoio/hugo/hugolib"
|
||||||
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
|
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
|
||||||
jww "github.com/spf13/jwalterweatherman"
|
jww "github.com/spf13/jwalterweatherman"
|
||||||
|
@ -196,3 +198,76 @@ T1: {{ $r.Content }}
|
||||||
b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:1:0: bar`)
|
b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:1:0: bar`)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTransformErrors(t *testing.T) {
|
||||||
|
if !dartsass.Supports() {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
const filesTemplate = `
|
||||||
|
-- config.toml --
|
||||||
|
-- assets/scss/components/_foo.scss --
|
||||||
|
/* comment line 1 */
|
||||||
|
$foocolor: #ccc;
|
||||||
|
|
||||||
|
foo {
|
||||||
|
color: $foocolor;
|
||||||
|
}
|
||||||
|
-- assets/scss/main.scss --
|
||||||
|
/* comment line 1 */
|
||||||
|
/* comment line 2 */
|
||||||
|
@import "components/foo";
|
||||||
|
/* comment line 4 */
|
||||||
|
|
||||||
|
$maincolor: #eee;
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: $maincolor;
|
||||||
|
}
|
||||||
|
|
||||||
|
-- layouts/index.html --
|
||||||
|
{{ $cssOpts := dict "transpiler" "dartsass" }}
|
||||||
|
{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }}
|
||||||
|
T1: {{ $r.Content }}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
c.Run("error in main", func(c *qt.C) {
|
||||||
|
b, err := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: c,
|
||||||
|
TxtarString: strings.Replace(filesTemplate, "$maincolor: #eee;", "$maincolor #eee;", 1),
|
||||||
|
NeedsOsFS: true,
|
||||||
|
}).BuildE()
|
||||||
|
|
||||||
|
b.Assert(err, qt.IsNotNil)
|
||||||
|
b.Assert(err.Error(), qt.Contains, `main.scss:8:13":`)
|
||||||
|
b.Assert(err.Error(), qt.Contains, `: expected ":".`)
|
||||||
|
fe := b.AssertIsFileError(err)
|
||||||
|
b.Assert(fe.ErrorContext(), qt.IsNotNil)
|
||||||
|
b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{" $maincolor #eee;", "", "body {", "\tcolor: $maincolor;", "}"})
|
||||||
|
b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss")
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
c.Run("error in import", func(c *qt.C) {
|
||||||
|
b, err := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: c,
|
||||||
|
TxtarString: strings.Replace(filesTemplate, "$foocolor: #ccc;", "$foocolor #ccc;", 1),
|
||||||
|
NeedsOsFS: true,
|
||||||
|
}).BuildE()
|
||||||
|
|
||||||
|
b.Assert(err, qt.IsNotNil)
|
||||||
|
b.Assert(err.Error(), qt.Contains, `_foo.scss:2:10":`)
|
||||||
|
b.Assert(err.Error(), qt.Contains, `: expected ":".`)
|
||||||
|
fe := b.AssertIsFileError(err)
|
||||||
|
b.Assert(fe.ErrorContext(), qt.IsNotNil)
|
||||||
|
b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"/* comment line 1 */", "$foocolor #ccc;", "", "foo {"})
|
||||||
|
b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss")
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -16,13 +16,12 @@ package dartsass
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/herrors"
|
|
||||||
"github.com/gohugoio/hugo/common/hexec"
|
"github.com/gohugoio/hugo/common/hexec"
|
||||||
|
"github.com/gohugoio/hugo/common/paths"
|
||||||
"github.com/gohugoio/hugo/htesting"
|
"github.com/gohugoio/hugo/htesting"
|
||||||
"github.com/gohugoio/hugo/media"
|
"github.com/gohugoio/hugo/media"
|
||||||
|
|
||||||
|
@ -38,9 +37,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// See https://github.com/sass/dart-sass-embedded/issues/24
|
|
||||||
// Note: This prefix must be all lower case.
|
|
||||||
stdinPrefix = "hugostdin:"
|
|
||||||
dartSassEmbeddedBinaryName = "dart-sass-embedded"
|
dartSassEmbeddedBinaryName = "dart-sass-embedded"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -76,7 +72,7 @@ func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
baseDir := path.Dir(ctx.SourcePath)
|
baseDir := path.Dir(ctx.SourcePath)
|
||||||
filename := stdinPrefix
|
filename := dartSassStdinPrefix
|
||||||
|
|
||||||
if ctx.SourcePath != "" {
|
if ctx.SourcePath != "" {
|
||||||
filename += t.c.sfs.RealFilename(ctx.SourcePath)
|
filename += t.c.sfs.RealFilename(ctx.SourcePath)
|
||||||
|
@ -108,26 +104,6 @@ func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
|
||||||
|
|
||||||
res, err := t.c.toCSS(args, ctx.From)
|
res, err := t.c.toCSS(args, ctx.From)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if sassErr, ok := err.(godartsass.SassError); ok {
|
|
||||||
start := sassErr.Span.Start
|
|
||||||
context := strings.TrimSpace(sassErr.Span.Context)
|
|
||||||
filename, _ := urlToFilename(sassErr.Span.Url)
|
|
||||||
if strings.HasPrefix(filename, stdinPrefix) {
|
|
||||||
filename = filename[len(stdinPrefix):]
|
|
||||||
}
|
|
||||||
|
|
||||||
offsetMatcher := func(m herrors.LineMatcher) int {
|
|
||||||
if m.Offset+len(m.Line) >= start.Offset && strings.Contains(m.Line, context) {
|
|
||||||
// We found the line, but return 0 to signal that we want to determine
|
|
||||||
// the column from the error.
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
return herrors.NewFileErrorFromFile(sassErr, filename, hugofs.Os, offsetMatcher)
|
|
||||||
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +130,7 @@ type importResolver struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t importResolver) CanonicalizeURL(url string) (string, error) {
|
func (t importResolver) CanonicalizeURL(url string) (string, error) {
|
||||||
filePath, isURL := urlToFilename(url)
|
filePath, isURL := paths.UrlToFilename(url)
|
||||||
var prevDir string
|
var prevDir string
|
||||||
var pathDir string
|
var pathDir string
|
||||||
if isURL {
|
if isURL {
|
||||||
|
@ -200,23 +176,7 @@ func (t importResolver) CanonicalizeURL(url string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t importResolver) Load(url string) (string, error) {
|
func (t importResolver) Load(url string) (string, error) {
|
||||||
filename, _ := urlToFilename(url)
|
filename, _ := paths.UrlToFilename(url)
|
||||||
b, err := afero.ReadFile(hugofs.Os, filename)
|
b, err := afero.ReadFile(hugofs.Os, filename)
|
||||||
return string(b), err
|
return string(b), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(bep) add tests
|
|
||||||
func urlToFilename(urls string) (string, bool) {
|
|
||||||
u, err := url.ParseRequestURI(urls)
|
|
||||||
if err != nil {
|
|
||||||
return filepath.FromSlash(urls), false
|
|
||||||
}
|
|
||||||
p := filepath.FromSlash(u.Path)
|
|
||||||
|
|
||||||
if u.Host != "" {
|
|
||||||
// C:\data\file.txt
|
|
||||||
p = strings.ToUpper(u.Host) + ":" + p
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, true
|
|
||||||
}
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ func (c *Client) ToCSS(res resources.ResourceTransformer, opts Options) (resourc
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.Transform(&toCSSTransformation{c: c, options: internalOptions})
|
return res.Transform(&toCSSTransformation{c: c, options: internalOptions})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type toCSSTransformation struct {
|
type toCSSTransformation struct {
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
package scss_test
|
package scss_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
|
@ -133,7 +135,7 @@ moo {
|
||||||
-- config.toml --
|
-- config.toml --
|
||||||
theme = 'mytheme'
|
theme = 'mytheme'
|
||||||
-- layouts/index.html --
|
-- layouts/index.html --
|
||||||
{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo" ) "transpiler" "dartsass" ) }}
|
{{ $cssOpts := (dict "includePaths" (slice "node_modules/foo" ) ) }}
|
||||||
{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }}
|
{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }}
|
||||||
T1: {{ $r.Content }}
|
T1: {{ $r.Content }}
|
||||||
-- themes/mytheme/assets/scss/components/_boo.scss --
|
-- themes/mytheme/assets/scss/components/_boo.scss --
|
||||||
|
@ -171,3 +173,75 @@ zoo {
|
||||||
|
|
||||||
b.AssertFileContent("public/index.html", `T1: moo{color:#ccc}boo{color:green}zoo{color:pink}`)
|
b.AssertFileContent("public/index.html", `T1: moo{color:#ccc}boo{color:green}zoo{color:pink}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTransformErrors(t *testing.T) {
|
||||||
|
if !scss.Supports() {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
const filesTemplate = `
|
||||||
|
-- config.toml --
|
||||||
|
theme = 'mytheme'
|
||||||
|
-- assets/scss/components/_foo.scss --
|
||||||
|
/* comment line 1 */
|
||||||
|
$foocolor: #ccc;
|
||||||
|
|
||||||
|
foo {
|
||||||
|
color: $foocolor;
|
||||||
|
}
|
||||||
|
-- themes/mytheme/assets/scss/main.scss --
|
||||||
|
/* comment line 1 */
|
||||||
|
/* comment line 2 */
|
||||||
|
@import "components/foo";
|
||||||
|
/* comment line 4 */
|
||||||
|
|
||||||
|
$maincolor: #eee;
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: $maincolor;
|
||||||
|
}
|
||||||
|
|
||||||
|
-- layouts/index.html --
|
||||||
|
{{ $cssOpts := dict }}
|
||||||
|
{{ $r := resources.Get "scss/main.scss" | toCSS $cssOpts | minify }}
|
||||||
|
T1: {{ $r.Content }}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
c.Run("error in main", func(c *qt.C) {
|
||||||
|
b, err := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: c,
|
||||||
|
TxtarString: strings.Replace(filesTemplate, "$maincolor: #eee;", "$maincolor #eee;", 1),
|
||||||
|
NeedsOsFS: true,
|
||||||
|
}).BuildE()
|
||||||
|
|
||||||
|
b.Assert(err, qt.IsNotNil)
|
||||||
|
b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`themes/mytheme/assets/scss/main.scss:6:1": expected ':' after $maincolor in assignment statement`))
|
||||||
|
fe := b.AssertIsFileError(err)
|
||||||
|
b.Assert(fe.ErrorContext(), qt.IsNotNil)
|
||||||
|
b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"/* comment line 4 */", "", "$maincolor #eee;", "", "body {"})
|
||||||
|
b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss")
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
c.Run("error in import", func(c *qt.C) {
|
||||||
|
b, err := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: c,
|
||||||
|
TxtarString: strings.Replace(filesTemplate, "$foocolor: #ccc;", "$foocolor #ccc;", 1),
|
||||||
|
NeedsOsFS: true,
|
||||||
|
}).BuildE()
|
||||||
|
|
||||||
|
b.Assert(err, qt.IsNotNil)
|
||||||
|
b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`assets/scss/components/_foo.scss:2:1": expected ':' after $foocolor in assignment statement`))
|
||||||
|
fe := b.AssertIsFileError(err)
|
||||||
|
b.Assert(fe.ErrorContext(), qt.IsNotNil)
|
||||||
|
b.Assert(fe.ErrorContext().Lines, qt.DeepEquals, []string{"/* comment line 1 */", "$foocolor #ccc;", "", "foo {"})
|
||||||
|
b.Assert(fe.ErrorContext().ChromaLexer, qt.Equals, "scss")
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -20,10 +20,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bep/golibsass/libsass"
|
"github.com/bep/golibsass/libsass"
|
||||||
|
"github.com/bep/golibsass/libsass/libsasserrors"
|
||||||
|
"github.com/gohugoio/hugo/common/herrors"
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
"github.com/gohugoio/hugo/hugofs"
|
"github.com/gohugoio/hugo/hugofs"
|
||||||
"github.com/gohugoio/hugo/media"
|
"github.com/gohugoio/hugo/media"
|
||||||
|
@ -136,7 +139,14 @@ func (t *toCSSTransformation) Transform(ctx *resources.ResourceTransformationCtx
|
||||||
|
|
||||||
res, err := t.c.toCSS(options.to, ctx.To, ctx.From)
|
res, err := t.c.toCSS(options.to, ctx.To, ctx.From)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
if sasserr, ok := err.(libsasserrors.Error); ok {
|
||||||
|
if sasserr.File == "stdin" && ctx.SourcePath != "" {
|
||||||
|
sasserr.File = t.c.sfs.RealFilename(ctx.SourcePath)
|
||||||
|
err = sasserr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return herrors.NewFileErrorFromFileInErr(err, hugofs.Os, nil)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.from.EnableSourceMap && res.SourceMapContent != "" {
|
if options.from.EnableSourceMap && res.SourceMapContent != "" {
|
||||||
|
@ -180,7 +190,7 @@ func (c *Client) toCSS(options libsass.Options, dst io.Writer, src io.Reader) (l
|
||||||
|
|
||||||
res, err = transpiler.Execute(in)
|
res, err = transpiler.Execute(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, fmt.Errorf("SCSS processing failed: %w", err)
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
out := res.CSS
|
out := res.CSS
|
||||||
|
|
|
@ -554,7 +554,7 @@ func (t *templateHandler) addFileContext(templ tpl.Template, inerr error) error
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
fe := herrors.NewFileError(inErr, info.realFilename)
|
fe := herrors.NewFileErrorFromName(inErr, info.realFilename)
|
||||||
fe.UpdateContent(f, lineMatcher)
|
fe.UpdateContent(f, lineMatcher)
|
||||||
|
|
||||||
if !fe.ErrorContext().Position.IsValid() {
|
if !fe.ErrorContext().Position.IsValid() {
|
||||||
|
|
|
@ -53,7 +53,7 @@ func (t templateInfo) resolveType() templateType {
|
||||||
|
|
||||||
func (info templateInfo) errWithFileContext(what string, err error) error {
|
func (info templateInfo) errWithFileContext(what string, err error) error {
|
||||||
err = fmt.Errorf(what+": %w", err)
|
err = fmt.Errorf(what+": %w", err)
|
||||||
fe := herrors.NewFileError(err, info.realFilename)
|
fe := herrors.NewFileErrorFromName(err, info.realFilename)
|
||||||
f, err := info.fs.Open(info.filename)
|
f, err := info.fs.Open(info.filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -115,7 +115,7 @@ func (c *Chain) Apply(to io.Writer, from io.Reader) error {
|
||||||
_, _ = io.Copy(tempfile, fb.from)
|
_, _ = io.Copy(tempfile, fb.from)
|
||||||
return herrors.NewFileErrorFromFile(err, filename, hugofs.Os, nil)
|
return herrors.NewFileErrorFromFile(err, filename, hugofs.Os, nil)
|
||||||
}
|
}
|
||||||
return herrors.NewFileError(err, filename).UpdateContent(fb.from, nil)
|
return herrors.NewFileErrorFromName(err, filename).UpdateContent(fb.from, nil)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue