summaryrefslogtreecommitdiffstats
path: root/resources/resource_transformers/tocss/dartsass/transform.go
diff options
context:
space:
mode:
Diffstat (limited to 'resources/resource_transformers/tocss/dartsass/transform.go')
-rw-r--r--resources/resource_transformers/tocss/dartsass/transform.go222
1 files changed, 222 insertions, 0 deletions
diff --git a/resources/resource_transformers/tocss/dartsass/transform.go b/resources/resource_transformers/tocss/dartsass/transform.go
new file mode 100644
index 000000000..4cbb35fb9
--- /dev/null
+++ b/resources/resource_transformers/tocss/dartsass/transform.go
@@ -0,0 +1,222 @@
+// Copyright 2020 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 dartsass
+
+import (
+ "fmt"
+ "io"
+ "net/url"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/cli/safeexec"
+
+ "github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/htesting"
+ "github.com/gohugoio/hugo/media"
+
+ "github.com/gohugoio/hugo/resources"
+
+ "github.com/gohugoio/hugo/resources/internal"
+
+ "github.com/spf13/afero"
+
+ "github.com/gohugoio/hugo/hugofs"
+
+ "github.com/bep/godartsass"
+)
+
+// See https://github.com/sass/dart-sass-embedded/issues/24
+const stdinPlaceholder = "HUGOSTDIN"
+
+// Supports returns whether dart-sass-embedded is found in $PATH.
+func Supports() bool {
+ if htesting.SupportsAll() {
+ return true
+ }
+ p, err := safeexec.LookPath("dart-sass-embedded")
+ return err == nil && p != ""
+}
+
+type transform struct {
+ optsm map[string]interface{}
+ c *Client
+}
+
+func (t *transform) Key() internal.ResourceTransformationKey {
+ return internal.NewResourceTransformationKey(transformationName, t.optsm)
+}
+
+func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
+ ctx.OutMediaType = media.CSSType
+
+ opts, err := decodeOptions(t.optsm)
+ if err != nil {
+ return err
+ }
+
+ if opts.TargetPath != "" {
+ ctx.OutPath = opts.TargetPath
+ } else {
+ ctx.ReplaceOutPathExtension(".css")
+ }
+
+ baseDir := path.Dir(ctx.SourcePath)
+
+ args := godartsass.Args{
+ URL: stdinPlaceholder,
+ IncludePaths: t.c.sfs.RealDirs(baseDir),
+ ImportResolver: importResolver{
+ baseDir: baseDir,
+ c: t.c,
+ },
+ EnableSourceMap: opts.EnableSourceMap,
+ }
+
+ // Append any workDir relative include paths
+ for _, ip := range opts.IncludePaths {
+ info, err := t.c.workFs.Stat(filepath.Clean(ip))
+ if err == nil {
+ filename := info.(hugofs.FileMetaInfo).Meta().Filename()
+ args.IncludePaths = append(args.IncludePaths, filename)
+ }
+ }
+
+ if ctx.InMediaType.SubType == media.SASSType.SubType {
+ args.SourceSyntax = godartsass.SourceSyntaxSASS
+ }
+
+ res, err := t.c.toCSS(args, ctx.From)
+ 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 filename == stdinPlaceholder {
+ if ctx.SourcePath == "" {
+ return sassErr
+ }
+ filename = t.c.sfs.RealFilename(ctx.SourcePath)
+ }
+
+ offsetMatcher := func(m herrors.LineMatcher) bool {
+ return m.Offset+len(m.Line) >= start.Offset && strings.Contains(m.Line, context)
+ }
+
+ ferr, ok := herrors.WithFileContextForFile(
+ herrors.NewFileError("scss", -1, -1, start.Column, sassErr),
+ filename,
+ filename,
+ hugofs.Os,
+ offsetMatcher)
+
+ if !ok {
+ return sassErr
+ }
+
+ return ferr
+ }
+ return err
+ }
+
+ out := res.CSS
+
+ _, err = io.WriteString(ctx.To, out)
+ if err != nil {
+ return err
+ }
+
+ if opts.EnableSourceMap && res.SourceMap != "" {
+ if err := ctx.PublishSourceMap(res.SourceMap); err != nil {
+ return err
+ }
+ _, err = fmt.Fprintf(ctx.To, "\n\n/*# sourceMappingURL=%s */", path.Base(ctx.OutPath)+".map")
+ }
+
+ return err
+}
+
+type importResolver struct {
+ baseDir string
+ c *Client
+}
+
+func (t importResolver) CanonicalizeURL(url string) (string, error) {
+ filePath, isURL := urlToFilename(url)
+ var prevDir string
+ var pathDir string
+ if isURL {
+ var found bool
+ prevDir, found = t.c.sfs.MakePathRelative(filepath.Dir(filePath))
+
+ if !found {
+ // Not a member of this filesystem, let Dart Sass handle it.
+ return "", nil
+ }
+ } else {
+ prevDir = t.baseDir
+ pathDir = path.Dir(url)
+ }
+
+ basePath := filepath.Join(prevDir, pathDir)
+ name := filepath.Base(filePath)
+
+ // Pick the first match.
+ var namePatterns []string
+ if strings.Contains(name, ".") {
+ namePatterns = []string{"_%s", "%s"}
+ } else if strings.HasPrefix(name, "_") {
+ namePatterns = []string{"_%s.scss", "_%s.sass"}
+ } else {
+ namePatterns = []string{"_%s.scss", "%s.scss", "_%s.sass", "%s.sass"}
+ }
+
+ name = strings.TrimPrefix(name, "_")
+
+ for _, namePattern := range namePatterns {
+ filenameToCheck := filepath.Join(basePath, fmt.Sprintf(namePattern, name))
+ fi, err := t.c.sfs.Fs.Stat(filenameToCheck)
+ if err == nil {
+ if fim, ok := fi.(hugofs.FileMetaInfo); ok {
+ return "file://" + filepath.ToSlash(fim.Meta().Filename()), nil
+ }
+ }
+ }
+
+ // Not found, let Dart Dass handle it
+ return "", nil
+}
+
+func (t importResolver) Load(url string) (string, error) {
+ filename, _ := urlToFilename(url)
+ b, err := afero.ReadFile(hugofs.Os, filename)
+ 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
+}