From ce8a09a4c0661dece931ab1173e4f09e8e04aa38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 2 Jan 2019 11:58:32 +0100 Subject: resources: Move resource interfaces into its own package --- .../resource_transformers/tocss/scss/client.go | 111 +++++++++++++ .../resource_transformers/tocss/scss/tocss.go | 173 +++++++++++++++++++++ .../tocss/scss/tocss_notavailable.go | 30 ++++ 3 files changed, 314 insertions(+) create mode 100644 resources/resource_transformers/tocss/scss/client.go create mode 100644 resources/resource_transformers/tocss/scss/tocss.go create mode 100644 resources/resource_transformers/tocss/scss/tocss_notavailable.go (limited to 'resources/resource_transformers/tocss') diff --git a/resources/resource_transformers/tocss/scss/client.go b/resources/resource_transformers/tocss/scss/client.go new file mode 100644 index 000000000..41ff67433 --- /dev/null +++ b/resources/resource_transformers/tocss/scss/client.go @@ -0,0 +1,111 @@ +// Copyright 2018 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 scss + +import ( + "github.com/bep/go-tocss/scss" + "github.com/gohugoio/hugo/helpers" + "github.com/gohugoio/hugo/hugolib/filesystems" + "github.com/gohugoio/hugo/resources" + "github.com/gohugoio/hugo/resources/resource" + + "github.com/mitchellh/mapstructure" +) + +type Client struct { + rs *resources.Spec + sfs *filesystems.SourceFilesystem + workFs *filesystems.SourceFilesystem +} + +func New(fs *filesystems.SourceFilesystem, rs *resources.Spec) (*Client, error) { + return &Client{sfs: fs, workFs: rs.BaseFs.Work, rs: rs}, nil +} + +type Options struct { + + // Hugo, will by default, just replace the extension of the source + // to .css, e.g. "scss/main.scss" becomes "scss/main.css". You can + // control this by setting this, e.g. "styles/main.css" will create + // a Resource with that as a base for RelPermalink etc. + TargetPath string + + // Hugo automatically adds the entry directories (where the main.scss lives) + // for project and themes to the list of include paths sent to LibSASS. + // Any paths set in this setting will be appended. Note that these will be + // treated as relative to the working dir, i.e. no include paths outside the + // project/themes. + IncludePaths []string + + // Default is nested. + // One of nested, expanded, compact, compressed. + OutputStyle string + + // Precision of floating point math. + Precision int + + // When enabled, Hugo will generate a source map. + EnableSourceMap bool +} + +type options struct { + // The options we receive from the end user. + from Options + + // The options we send to the SCSS library. + to scss.Options +} + +func (c *Client) ToCSS(res resource.Resource, opts Options) (resource.Resource, error) { + internalOptions := options{ + from: opts, + } + + // Transfer values from client. + internalOptions.to.Precision = opts.Precision + internalOptions.to.OutputStyle = scss.OutputStyleFromString(opts.OutputStyle) + + if internalOptions.to.Precision == 0 { + // bootstrap-sass requires 8 digits precision. The libsass default is 5. + // https://github.com/twbs/bootstrap-sass/blob/master/README.md#sass-number-precision + internalOptions.to.Precision = 8 + } + + return c.rs.Transform( + res, + &toCSSTransformation{c: c, options: internalOptions}, + ) +} + +type toCSSTransformation struct { + c *Client + options options +} + +func (t *toCSSTransformation) Key() resources.ResourceTransformationKey { + return resources.NewResourceTransformationKey("tocss", t.options.from) +} + +func DecodeOptions(m map[string]interface{}) (opts Options, err error) { + if m == nil { + return + } + err = mapstructure.WeakDecode(m, &opts) + + if opts.TargetPath != "" { + opts.TargetPath = helpers.ToSlashTrimLeading(opts.TargetPath) + } + + return +} diff --git a/resources/resource_transformers/tocss/scss/tocss.go b/resources/resource_transformers/tocss/scss/tocss.go new file mode 100644 index 000000000..17c32ea8e --- /dev/null +++ b/resources/resource_transformers/tocss/scss/tocss.go @@ -0,0 +1,173 @@ +// Copyright 2018 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. + +// +build extended + +package scss + +import ( + "fmt" + "io" + "path" + "path/filepath" + "strings" + + "github.com/bep/go-tocss/scss" + "github.com/bep/go-tocss/scss/libsass" + "github.com/bep/go-tocss/tocss" + "github.com/gohugoio/hugo/helpers" + "github.com/gohugoio/hugo/hugofs" + "github.com/gohugoio/hugo/media" + "github.com/gohugoio/hugo/resources" + "github.com/pkg/errors" +) + +// Used in tests. This feature requires Hugo to be built with the extended tag. +func Supports() bool { + return true +} + +func (t *toCSSTransformation) Transform(ctx *resources.ResourceTransformationCtx) error { + ctx.OutMediaType = media.CSSType + + var outName string + if t.options.from.TargetPath != "" { + ctx.OutPath = t.options.from.TargetPath + } else { + ctx.ReplaceOutPathExtension(".css") + } + + outName = path.Base(ctx.OutPath) + + options := t.options + baseDir := path.Dir(ctx.SourcePath) + options.to.IncludePaths = t.c.sfs.RealDirs(baseDir) + + // Append any workDir relative include paths + for _, ip := range options.from.IncludePaths { + options.to.IncludePaths = append(options.to.IncludePaths, t.c.workFs.RealDirs(filepath.Clean(ip))...) + } + + // To allow for overrides of SCSS files anywhere in the project/theme hierarchy, we need + // to help libsass revolve the filename by looking in the composite filesystem first. + // We add the entry directories for both project and themes to the include paths list, but + // that only work for overrides on the top level. + options.to.ImportResolver = func(url string, prev string) (newUrl string, body string, resolved bool) { + // We get URL paths from LibSASS, but we need file paths. + url = filepath.FromSlash(url) + prev = filepath.FromSlash(prev) + + var basePath string + urlDir := filepath.Dir(url) + var prevDir string + if prev == "stdin" { + prevDir = baseDir + } else { + prevDir = t.c.sfs.MakePathRelative(filepath.Dir(prev)) + if prevDir == "" { + // Not a member of this filesystem. Let LibSASS handle it. + return "", "", false + } + } + + basePath = filepath.Join(prevDir, urlDir) + name := filepath.Base(url) + + // Libsass throws an error in cases where you have several possible candidates. + // We make this simpler and 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 fir, ok := fi.(hugofs.RealFilenameInfo); ok { + return fir.RealFilename(), "", true + } + } + } + + // Not found, let LibSASS handle it + return "", "", false + } + + if ctx.InMediaType.SubType == media.SASSType.SubType { + options.to.SassSyntax = true + } + + if options.from.EnableSourceMap { + + options.to.SourceMapFilename = outName + ".map" + options.to.SourceMapRoot = t.c.rs.WorkingDir + + // Setting this to the relative input filename will get the source map + // more correct for the main entry path (main.scss typically), but + // it will mess up the import mappings. As a workaround, we do a replacement + // in the source map itself (see below). + //options.InputPath = inputPath + options.to.OutputPath = outName + options.to.SourceMapContents = true + options.to.OmitSourceMapURL = false + options.to.EnableEmbeddedSourceMap = false + } + + res, err := t.c.toCSS(options.to, ctx.To, ctx.From) + if err != nil { + return err + } + + if options.from.EnableSourceMap && res.SourceMapContent != "" { + sourcePath := t.c.sfs.RealFilename(ctx.SourcePath) + + if strings.HasPrefix(sourcePath, t.c.rs.WorkingDir) { + sourcePath = strings.TrimPrefix(sourcePath, t.c.rs.WorkingDir+helpers.FilePathSeparator) + } + + // This needs to be Unix-style slashes, even on Windows. + // See https://github.com/gohugoio/hugo/issues/4968 + sourcePath = filepath.ToSlash(sourcePath) + + // This is a workaround for what looks like a bug in Libsass. But + // getting this resolution correct in tools like Chrome Workspaces + // is important enough to go this extra mile. + mapContent := strings.Replace(res.SourceMapContent, `stdin",`, fmt.Sprintf("%s\",", sourcePath), 1) + + return ctx.PublishSourceMap(mapContent) + } + return nil +} + +func (c *Client) toCSS(options scss.Options, dst io.Writer, src io.Reader) (tocss.Result, error) { + var res tocss.Result + + transpiler, err := libsass.New(options) + if err != nil { + return res, err + } + + res, err = transpiler.Execute(dst, src) + if err != nil { + return res, errors.Wrap(err, "SCSS processing failed") + } + + return res, nil +} diff --git a/resources/resource_transformers/tocss/scss/tocss_notavailable.go b/resources/resource_transformers/tocss/scss/tocss_notavailable.go new file mode 100644 index 000000000..ad6b42b98 --- /dev/null +++ b/resources/resource_transformers/tocss/scss/tocss_notavailable.go @@ -0,0 +1,30 @@ +// Copyright 2018 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. + +// +build !extended + +package scss + +import ( + "github.com/gohugoio/hugo/common/herrors" + "github.com/gohugoio/hugo/resources" +) + +// Used in tests. +func Supports() bool { + return false +} + +func (t *toCSSTransformation) Transform(ctx *resources.ResourceTransformationCtx) error { + return herrors.ErrFeatureNotAvailable +} -- cgit v1.2.3