github.com/neohugo/neohugo@v0.123.8/resources/resource_transformers/tocss/dartsass/transform.go (about)

     1  // Copyright 2024 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/neohugo/neohugo/common/neohugo"
    24  	"github.com/neohugo/neohugo/common/paths"
    25  	"github.com/neohugo/neohugo/htesting"
    26  	"github.com/neohugo/neohugo/identity"
    27  	"github.com/neohugo/neohugo/media"
    28  	"github.com/neohugo/neohugo/resources"
    29  
    30  	"github.com/neohugo/neohugo/resources/internal"
    31  	"github.com/neohugo/neohugo/resources/resource_transformers/tocss/internal/sass"
    32  
    33  	"github.com/spf13/afero"
    34  
    35  	"github.com/neohugo/neohugo/hugofs"
    36  
    37  	godartsassv1 "github.com/bep/godartsass"
    38  	"github.com/bep/godartsass/v2"
    39  )
    40  
    41  // Supports returns whether dart-sass-embedded is found in $PATH.
    42  func Supports() bool {
    43  	if htesting.SupportsAll() {
    44  		return true
    45  	}
    46  	return neohugo.DartSassBinaryName != ""
    47  }
    48  
    49  type transform struct {
    50  	optsm map[string]any
    51  	c     *Client
    52  }
    53  
    54  func (t *transform) Key() internal.ResourceTransformationKey {
    55  	return internal.NewResourceTransformationKey(transformationName, t.optsm)
    56  }
    57  
    58  func (t *transform) Transform(ctx *resources.ResourceTransformationCtx) error {
    59  	ctx.OutMediaType = media.Builtin.CSSType
    60  
    61  	opts, err := decodeOptions(t.optsm)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	if opts.TargetPath != "" {
    67  		ctx.OutPath = opts.TargetPath
    68  	} else {
    69  		ctx.ReplaceOutPathExtension(".css")
    70  	}
    71  
    72  	baseDir := path.Dir(ctx.SourcePath)
    73  	filename := dartSassStdinPrefix
    74  
    75  	if ctx.SourcePath != "" {
    76  		filename += t.c.sfs.RealFilename(ctx.SourcePath)
    77  	}
    78  
    79  	args := godartsass.Args{
    80  		URL:          filename,
    81  		IncludePaths: t.c.sfs.RealDirs(baseDir),
    82  		ImportResolver: importResolver{
    83  			baseDir:           baseDir,
    84  			c:                 t.c,
    85  			dependencyManager: ctx.DependencyManager,
    86  
    87  			varsStylesheet: godartsass.Import{Content: sass.CreateVarsStyleSheet(opts.Vars)},
    88  		},
    89  		OutputStyle:             godartsass.ParseOutputStyle(opts.OutputStyle),
    90  		EnableSourceMap:         opts.EnableSourceMap,
    91  		SourceMapIncludeSources: opts.SourceMapIncludeSources,
    92  	}
    93  
    94  	// Append any workDir relative include paths
    95  	for _, ip := range opts.IncludePaths {
    96  		info, err := t.c.workFs.Stat(filepath.Clean(ip))
    97  		if err == nil {
    98  			filename := info.(hugofs.FileMetaInfo).Meta().Filename
    99  			args.IncludePaths = append(args.IncludePaths, filename)
   100  		}
   101  	}
   102  
   103  	if ctx.InMediaType.SubType == media.Builtin.SASSType.SubType {
   104  		args.SourceSyntax = godartsass.SourceSyntaxSASS
   105  	}
   106  
   107  	res, err := t.c.toCSS(args, ctx.From)
   108  	if err != nil {
   109  		return err
   110  	}
   111  
   112  	out := res.CSS
   113  
   114  	_, err = io.WriteString(ctx.To, out)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	if opts.EnableSourceMap && res.SourceMap != "" {
   120  		if err := ctx.PublishSourceMap(res.SourceMap); err != nil {
   121  			return err
   122  		}
   123  		_, err = fmt.Fprintf(ctx.To, "\n\n/*# sourceMappingURL=%s */", path.Base(ctx.OutPath)+".map")
   124  	}
   125  
   126  	return err
   127  }
   128  
   129  type importResolver struct {
   130  	baseDir           string
   131  	c                 *Client
   132  	dependencyManager identity.Manager
   133  	varsStylesheet    godartsass.Import
   134  }
   135  
   136  func (t importResolver) CanonicalizeURL(url string) (string, error) {
   137  	if url == sass.HugoVarsNamespace {
   138  		return url, nil
   139  	}
   140  
   141  	filePath, isURL := paths.UrlToFilename(url)
   142  	var prevDir string
   143  	var pathDir string
   144  	if isURL {
   145  		var found bool
   146  		prevDir, found = t.c.sfs.MakePathRelative(filepath.Dir(filePath), true)
   147  
   148  		if !found {
   149  			// Not a member of this filesystem, let Dart Sass handle it.
   150  			return "", nil
   151  		}
   152  	} else {
   153  		prevDir = t.baseDir
   154  		pathDir = path.Dir(url)
   155  	}
   156  
   157  	basePath := filepath.Join(prevDir, pathDir)
   158  	name := filepath.Base(filePath)
   159  
   160  	// Pick the first match.
   161  	var namePatterns []string
   162  	if strings.Contains(name, ".") {
   163  		namePatterns = []string{"_%s", "%s"}
   164  	} else if strings.HasPrefix(name, "_") {
   165  		namePatterns = []string{"_%s.scss", "_%s.sass", "_%s.css"}
   166  	} else {
   167  		namePatterns = []string{"_%s.scss", "%s.scss", "_%s.sass", "%s.sass", "_%s.css", "%s.css"}
   168  	}
   169  
   170  	name = strings.TrimPrefix(name, "_")
   171  
   172  	for _, namePattern := range namePatterns {
   173  		filenameToCheck := filepath.Join(basePath, fmt.Sprintf(namePattern, name))
   174  		fi, err := t.c.sfs.Fs.Stat(filenameToCheck)
   175  		if err == nil {
   176  			if fim, ok := fi.(hugofs.FileMetaInfo); ok {
   177  				t.dependencyManager.AddIdentity(identity.CleanStringIdentity(filenameToCheck))
   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) (godartsass.Import, 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  
   194  	sourceSyntax := godartsass.SourceSyntaxSCSS
   195  	if strings.HasSuffix(filename, ".sass") {
   196  		sourceSyntax = godartsass.SourceSyntaxSASS
   197  	} else if strings.HasSuffix(filename, ".css") {
   198  		sourceSyntax = godartsass.SourceSyntaxCSS
   199  	}
   200  
   201  	return godartsass.Import{Content: string(b), SourceSyntax: sourceSyntax}, err
   202  }
   203  
   204  type importResolverV1 struct {
   205  	godartsass.ImportResolver
   206  }
   207  
   208  func (t importResolverV1) Load(url string) (godartsassv1.Import, error) {
   209  	res, err := t.ImportResolver.Load(url)
   210  	return godartsassv1.Import{Content: res.Content, SourceSyntax: godartsassv1.SourceSyntax(res.SourceSyntax)}, err
   211  }