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 }