github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/resources/resource_transformers/tocss/dartsass/transform.go (about)

     1  // Copyright 2022 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package dartsass
    15  
    16  import (
    17  	"fmt"
    18  	"io"
    19  	"path"
    20  	"path/filepath"
    21  	"strings"
    22  
    23  	"github.com/gohugoio/hugo/common/hexec"
    24  	"github.com/gohugoio/hugo/common/paths"
    25  	"github.com/gohugoio/hugo/htesting"
    26  	"github.com/gohugoio/hugo/media"
    27  
    28  	"github.com/gohugoio/hugo/resources"
    29  
    30  	"github.com/gohugoio/hugo/resources/internal"
    31  	"github.com/gohugoio/hugo/resources/resource_transformers/tocss/internal/sass"
    32  
    33  	"github.com/spf13/afero"
    34  
    35  	"github.com/gohugoio/hugo/hugofs"
    36  
    37  	"github.com/bep/godartsass"
    38  )
    39  
    40  const (
    41  	dartSassEmbeddedBinaryName = "dart-sass-embedded"
    42  )
    43  
    44  // Supports returns whether dart-sass-embedded is found in $PATH.
    45  func Supports() bool {
    46  	if htesting.SupportsAll() {
    47  		return true
    48  	}
    49  	return hexec.InPath(dartSassEmbeddedBinaryName)
    50  }
    51  
    52  type transform struct {
    53  	optsm map[string]any
    54  	c     *Client
    55  }
    56  
    57  func (t *transform) Key() internal.ResourceTransformationKey {
    58  	return internal.NewResourceTransformationKey(transformationName, t.optsm)
    59  }
    60  
    61  func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
    62  	ctx.OutMediaType = media.CSSType
    63  
    64  	opts, err := decodeOptions(t.optsm)
    65  	if err != nil {
    66  		return err
    67  	}
    68  
    69  	if opts.TargetPath != "" {
    70  		ctx.OutPath = opts.TargetPath
    71  	} else {
    72  		ctx.ReplaceOutPathExtension(".css")
    73  	}
    74  
    75  	baseDir := path.Dir(ctx.SourcePath)
    76  	filename := dartSassStdinPrefix
    77  
    78  	if ctx.SourcePath != "" {
    79  		filename += t.c.sfs.RealFilename(ctx.SourcePath)
    80  	}
    81  
    82  	args := godartsass.Args{
    83  		URL:          filename,
    84  		IncludePaths: t.c.sfs.RealDirs(baseDir),
    85  		ImportResolver: importResolver{
    86  			baseDir: baseDir,
    87  			c:       t.c,
    88  
    89  			varsStylesheet: sass.CreateVarsStyleSheet(opts.Vars),
    90  		},
    91  		OutputStyle:             godartsass.ParseOutputStyle(opts.OutputStyle),
    92  		EnableSourceMap:         opts.EnableSourceMap,
    93  		SourceMapIncludeSources: opts.SourceMapIncludeSources,
    94  	}
    95  
    96  	// Append any workDir relative include paths
    97  	for _, ip := range opts.IncludePaths {
    98  		info, err := t.c.workFs.Stat(filepath.Clean(ip))
    99  		if err == nil {
   100  			filename := info.(hugofs.FileMetaInfo).Meta().Filename
   101  			args.IncludePaths = append(args.IncludePaths, filename)
   102  		}
   103  	}
   104  
   105  	if ctx.InMediaType.SubType == media.SASSType.SubType {
   106  		args.SourceSyntax = godartsass.SourceSyntaxSASS
   107  	}
   108  
   109  	res, err := t.c.toCSS(args, ctx.From)
   110  	if err != nil {
   111  		return err
   112  	}
   113  
   114  	out := res.CSS
   115  
   116  	_, err = io.WriteString(ctx.To, out)
   117  	if err != nil {
   118  		return err
   119  	}
   120  
   121  	if opts.EnableSourceMap && res.SourceMap != "" {
   122  		if err := ctx.PublishSourceMap(res.SourceMap); err != nil {
   123  			return err
   124  		}
   125  		_, err = fmt.Fprintf(ctx.To, "\n\n/*# sourceMappingURL=%s */", path.Base(ctx.OutPath)+".map")
   126  	}
   127  
   128  	return err
   129  }
   130  
   131  type importResolver struct {
   132  	baseDir string
   133  	c       *Client
   134  
   135  	varsStylesheet string
   136  }
   137  
   138  func (t importResolver) CanonicalizeURL(url string) (string, error) {
   139  	if url == sass.HugoVarsNamespace {
   140  		return url, nil
   141  	}
   142  	filePath, isURL := paths.UrlToFilename(url)
   143  	var prevDir string
   144  	var pathDir string
   145  	if isURL {
   146  		var found bool
   147  		prevDir, found = t.c.sfs.MakePathRelative(filepath.Dir(filePath))
   148  
   149  		if !found {
   150  			// Not a member of this filesystem, let Dart Sass handle it.
   151  			return "", nil
   152  		}
   153  	} else {
   154  		prevDir = t.baseDir
   155  		pathDir = path.Dir(url)
   156  	}
   157  
   158  	basePath := filepath.Join(prevDir, pathDir)
   159  	name := filepath.Base(filePath)
   160  
   161  	// Pick the first match.
   162  	var namePatterns []string
   163  	if strings.Contains(name, ".") {
   164  		namePatterns = []string{"_%s", "%s"}
   165  	} else if strings.HasPrefix(name, "_") {
   166  		namePatterns = []string{"_%s.scss", "_%s.sass", "_%s.css"}
   167  	} else {
   168  		namePatterns = []string{"_%s.scss", "%s.scss", "_%s.sass", "%s.sass", "_%s.css", "%s.css"}
   169  	}
   170  
   171  	name = strings.TrimPrefix(name, "_")
   172  
   173  	for _, namePattern := range namePatterns {
   174  		filenameToCheck := filepath.Join(basePath, fmt.Sprintf(namePattern, name))
   175  		fi, err := t.c.sfs.Fs.Stat(filenameToCheck)
   176  		if err == nil {
   177  			if fim, ok := fi.(hugofs.FileMetaInfo); ok {
   178  				return "file://" + filepath.ToSlash(fim.Meta().Filename), nil
   179  			}
   180  		}
   181  	}
   182  
   183  	// Not found, let Dart Dass handle it
   184  	return "", nil
   185  }
   186  
   187  func (t importResolver) Load(url string) (string, error) {
   188  	if url == sass.HugoVarsNamespace {
   189  		return t.varsStylesheet, nil
   190  	}
   191  	filename, _ := paths.UrlToFilename(url)
   192  	b, err := afero.ReadFile(hugofs.Os, filename)
   193  	return string(b), err
   194  }