resource: Move resource processors into sub-packages
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 2 Jan 2019 09:36:12 +0000 (10:36 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Wed, 2 Jan 2019 13:25:37 +0000 (14:25 +0100)
20 files changed:
hugolib/resource_chain_test.go
resource/bundler/bundler.go [deleted file]
resource/create/create.go [deleted file]
resource/integrity/integrity.go [deleted file]
resource/minifier/minify.go [deleted file]
resource/postcss/postcss.go [deleted file]
resource/resource_factories/bundler/bundler.go [new file with mode: 0644]
resource/resource_factories/create/create.go [new file with mode: 0644]
resource/resource_transformers/integrity/integrity.go [new file with mode: 0644]
resource/resource_transformers/minifier/minify.go [new file with mode: 0644]
resource/resource_transformers/postcss/postcss.go [new file with mode: 0644]
resource/resource_transformers/templates/execute_as_template.go [new file with mode: 0644]
resource/resource_transformers/tocss/scss/client.go [new file with mode: 0644]
resource/resource_transformers/tocss/scss/tocss.go [new file with mode: 0644]
resource/resource_transformers/tocss/scss/tocss_notavailable.go [new file with mode: 0644]
resource/templates/execute_as_template.go [deleted file]
resource/tocss/scss/client.go [deleted file]
resource/tocss/scss/tocss.go [deleted file]
resource/tocss/scss/tocss_notavailable.go [deleted file]
tpl/resources/resources.go

index 551ac017704828d5de2dee9bd7bd65be74b638a0..0fe9c70c1f7a8d45c22b3761f6607fb17b62b123 100644 (file)
@@ -25,7 +25,7 @@ import (
        "github.com/gohugoio/hugo/hugofs"
 
        "github.com/gohugoio/hugo/common/loggers"
-       "github.com/gohugoio/hugo/resource/tocss/scss"
+       "github.com/gohugoio/hugo/resource/resource_transformers/tocss/scss"
 )
 
 func TestSCSSWithIncludePaths(t *testing.T) {
diff --git a/resource/bundler/bundler.go b/resource/bundler/bundler.go
deleted file mode 100644 (file)
index 70b8ee5..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-// 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 bundler contains functions for concatenation etc. of Resource objects.
-package bundler
-
-import (
-       "fmt"
-       "io"
-       "path/filepath"
-
-       "github.com/gohugoio/hugo/common/hugio"
-       "github.com/gohugoio/hugo/media"
-       "github.com/gohugoio/hugo/resource"
-)
-
-// Client contains methods perform concatenation and other bundling related
-// tasks to Resource objects.
-type Client struct {
-       rs *resource.Spec
-}
-
-// New creates a new Client with the given specification.
-func New(rs *resource.Spec) *Client {
-       return &Client{rs: rs}
-}
-
-type multiReadSeekCloser struct {
-       mr      io.Reader
-       sources []hugio.ReadSeekCloser
-}
-
-func (r *multiReadSeekCloser) Read(p []byte) (n int, err error) {
-       return r.mr.Read(p)
-}
-
-func (r *multiReadSeekCloser) Seek(offset int64, whence int) (newOffset int64, err error) {
-       for _, s := range r.sources {
-               newOffset, err = s.Seek(offset, whence)
-               if err != nil {
-                       return
-               }
-       }
-       return
-}
-
-func (r *multiReadSeekCloser) Close() error {
-       for _, s := range r.sources {
-               s.Close()
-       }
-       return nil
-}
-
-// Concat concatenates the list of Resource objects.
-func (c *Client) Concat(targetPath string, resources resource.Resources) (resource.Resource, error) {
-       // The CACHE_OTHER will make sure this will be re-created and published on rebuilds.
-       return c.rs.ResourceCache.GetOrCreate(resource.CACHE_OTHER, targetPath, func() (resource.Resource, error) {
-               var resolvedm media.Type
-
-               // The given set of resources must be of the same Media Type.
-               // We may improve on that in the future, but then we need to know more.
-               for i, r := range resources {
-                       if i > 0 && r.MediaType().Type() != resolvedm.Type() {
-                               return nil, fmt.Errorf("resources in Concat must be of the same Media Type, got %q and %q", r.MediaType().Type(), resolvedm.Type())
-                       }
-                       resolvedm = r.MediaType()
-               }
-
-               concatr := func() (hugio.ReadSeekCloser, error) {
-                       var rcsources []hugio.ReadSeekCloser
-                       for _, s := range resources {
-                               rcr, ok := s.(resource.ReadSeekCloserResource)
-                               if !ok {
-                                       return nil, fmt.Errorf("resource %T does not implement resource.ReadSeekerCloserResource", s)
-                               }
-                               rc, err := rcr.ReadSeekCloser()
-                               if err != nil {
-                                       // Close the already opened.
-                                       for _, rcs := range rcsources {
-                                               rcs.Close()
-                                       }
-                                       return nil, err
-                               }
-                               rcsources = append(rcsources, rc)
-                       }
-
-                       readers := make([]io.Reader, len(rcsources))
-                       for i := 0; i < len(rcsources); i++ {
-                               readers[i] = rcsources[i]
-                       }
-
-                       mr := io.MultiReader(readers...)
-
-                       return &multiReadSeekCloser{mr: mr, sources: rcsources}, nil
-               }
-
-               composite, err := c.rs.NewForFs(
-                       c.rs.FileCaches.AssetsCache().Fs,
-                       resource.ResourceSourceDescriptor{
-                               LazyPublish:        true,
-                               OpenReadSeekCloser: concatr,
-                               RelTargetFilename:  filepath.Clean(targetPath)})
-
-               if err != nil {
-                       return nil, err
-               }
-
-               return composite, nil
-       })
-
-}
diff --git a/resource/create/create.go b/resource/create/create.go
deleted file mode 100644 (file)
index db23930..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-// 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 create contains functions for to create Resource objects. This will
-// typically non-files.
-package create
-
-import (
-       "path/filepath"
-
-       "github.com/spf13/afero"
-
-       "github.com/gohugoio/hugo/common/hugio"
-       "github.com/gohugoio/hugo/resource"
-)
-
-// Client contains methods to create Resource objects.
-// tasks to Resource objects.
-type Client struct {
-       rs *resource.Spec
-}
-
-// New creates a new Client with the given specification.
-func New(rs *resource.Spec) *Client {
-       return &Client{rs: rs}
-}
-
-// Get creates a new Resource by opening the given filename in the given filesystem.
-func (c *Client) Get(fs afero.Fs, filename string) (resource.Resource, error) {
-       filename = filepath.Clean(filename)
-       return c.rs.ResourceCache.GetOrCreate(resource.ResourceKeyPartition(filename), filename, func() (resource.Resource, error) {
-               return c.rs.NewForFs(fs,
-                       resource.ResourceSourceDescriptor{
-                               LazyPublish:    true,
-                               SourceFilename: filename})
-       })
-
-}
-
-// FromString creates a new Resource from a string with the given relative target path.
-func (c *Client) FromString(targetPath, content string) (resource.Resource, error) {
-       return c.rs.ResourceCache.GetOrCreate(resource.CACHE_OTHER, targetPath, func() (resource.Resource, error) {
-               return c.rs.NewForFs(
-                       c.rs.FileCaches.AssetsCache().Fs,
-                       resource.ResourceSourceDescriptor{
-                               LazyPublish: true,
-                               OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
-                                       return hugio.NewReadSeekerNoOpCloserFromString(content), nil
-                               },
-                               RelTargetFilename: filepath.Clean(targetPath)})
-
-       })
-
-}
diff --git a/resource/integrity/integrity.go b/resource/integrity/integrity.go
deleted file mode 100644 (file)
index 535c06a..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-// 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 integrity
-
-import (
-       "crypto/md5"
-       "crypto/sha256"
-       "crypto/sha512"
-       "encoding/base64"
-       "encoding/hex"
-       "fmt"
-       "hash"
-       "html/template"
-       "io"
-
-       "github.com/gohugoio/hugo/resource"
-)
-
-const defaultHashAlgo = "sha256"
-
-// Client contains methods to fingerprint (cachebusting) and other integrity-related
-// methods.
-type Client struct {
-       rs *resource.Spec
-}
-
-// New creates a new Client with the given specification.
-func New(rs *resource.Spec) *Client {
-       return &Client{rs: rs}
-}
-
-type fingerprintTransformation struct {
-       algo string
-}
-
-func (t *fingerprintTransformation) Key() resource.ResourceTransformationKey {
-       return resource.NewResourceTransformationKey("fingerprint", t.algo)
-}
-
-// Transform creates a MD5 hash of the Resource content and inserts that hash before
-// the extension in the filename.
-func (t *fingerprintTransformation) Transform(ctx *resource.ResourceTransformationCtx) error {
-       algo := t.algo
-
-       var h hash.Hash
-
-       switch algo {
-       case "md5":
-               h = md5.New()
-       case "sha256":
-               h = sha256.New()
-       case "sha512":
-               h = sha512.New()
-       default:
-               return fmt.Errorf("unsupported crypto algo: %q, use either md5, sha256 or sha512", algo)
-       }
-
-       io.Copy(io.MultiWriter(h, ctx.To), ctx.From)
-       d, err := digest(h)
-       if err != nil {
-               return err
-       }
-
-       ctx.Data["Integrity"] = integrity(algo, d)
-       ctx.AddOutPathIdentifier("." + hex.EncodeToString(d[:]))
-       return nil
-}
-
-// Fingerprint applies fingerprinting of the given resource and hash algorithm.
-// It defaults to sha256 if none given, and the options are md5, sha256 or sha512.
-// The same algo is used for both the fingerprinting part (aka cache busting) and
-// the base64-encoded Subresource Integrity hash, so you will have to stay away from
-// md5 if you plan to use both.
-// See https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
-func (c *Client) Fingerprint(res resource.Resource, algo string) (resource.Resource, error) {
-       if algo == "" {
-               algo = defaultHashAlgo
-       }
-
-       return c.rs.Transform(
-               res,
-               &fingerprintTransformation{algo: algo},
-       )
-}
-
-func integrity(algo string, sum []byte) template.HTMLAttr {
-       encoded := base64.StdEncoding.EncodeToString(sum)
-       return template.HTMLAttr(algo + "-" + encoded)
-}
-
-func digest(h hash.Hash) ([]byte, error) {
-       sum := h.Sum(nil)
-       return sum, nil
-}
diff --git a/resource/minifier/minify.go b/resource/minifier/minify.go
deleted file mode 100644 (file)
index cef22ef..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-// 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 minifier
-
-import (
-       "github.com/gohugoio/hugo/minifiers"
-       "github.com/gohugoio/hugo/resource"
-)
-
-// Client for minification of Resource objects. Supported minfiers are:
-// css, html, js, json, svg and xml.
-type Client struct {
-       rs *resource.Spec
-       m  minifiers.Client
-}
-
-// New creates a new Client given a specification. Note that it is the media types
-// configured for the site that is used to match files to the correct minifier.
-func New(rs *resource.Spec) *Client {
-       return &Client{rs: rs, m: minifiers.New(rs.MediaTypes, rs.OutputFormats)}
-}
-
-type minifyTransformation struct {
-       rs *resource.Spec
-       m  minifiers.Client
-}
-
-func (t *minifyTransformation) Key() resource.ResourceTransformationKey {
-       return resource.NewResourceTransformationKey("minify")
-}
-
-func (t *minifyTransformation) Transform(ctx *resource.ResourceTransformationCtx) error {
-       if err := t.m.Minify(ctx.InMediaType, ctx.To, ctx.From); err != nil {
-               return err
-       }
-       ctx.AddOutPathIdentifier(".min")
-       return nil
-}
-
-func (c *Client) Minify(res resource.Resource) (resource.Resource, error) {
-       return c.rs.Transform(
-               res,
-               &minifyTransformation{
-                       rs: c.rs,
-                       m:  c.m},
-       )
-}
diff --git a/resource/postcss/postcss.go b/resource/postcss/postcss.go
deleted file mode 100644 (file)
index ec73543..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-// 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 postcss
-
-import (
-       "io"
-       "path/filepath"
-
-       "github.com/gohugoio/hugo/hugofs"
-       "github.com/pkg/errors"
-
-       "os"
-       "os/exec"
-
-       "github.com/mitchellh/mapstructure"
-
-       "github.com/gohugoio/hugo/common/herrors"
-       "github.com/gohugoio/hugo/resource"
-)
-
-// Some of the options from https://github.com/postcss/postcss-cli
-type Options struct {
-
-       // Set a custom path to look for a config file.
-       Config string
-
-       NoMap bool `mapstructure:"no-map"` // Disable the default inline sourcemaps
-
-       // Options for when not using a config file
-       Use         string // List of postcss plugins to use
-       Parser      string //  Custom postcss parser
-       Stringifier string // Custom postcss stringifier
-       Syntax      string // Custom postcss syntax
-}
-
-func DecodeOptions(m map[string]interface{}) (opts Options, err error) {
-       if m == nil {
-               return
-       }
-       err = mapstructure.WeakDecode(m, &opts)
-       return
-}
-
-func (opts Options) toArgs() []string {
-       var args []string
-       if opts.NoMap {
-               args = append(args, "--no-map")
-       }
-       if opts.Use != "" {
-               args = append(args, "--use", opts.Use)
-       }
-       if opts.Parser != "" {
-               args = append(args, "--parser", opts.Parser)
-       }
-       if opts.Stringifier != "" {
-               args = append(args, "--stringifier", opts.Stringifier)
-       }
-       if opts.Syntax != "" {
-               args = append(args, "--syntax", opts.Syntax)
-       }
-       return args
-}
-
-// Client is the client used to do PostCSS transformations.
-type Client struct {
-       rs *resource.Spec
-}
-
-// New creates a new Client with the given specification.
-func New(rs *resource.Spec) *Client {
-       return &Client{rs: rs}
-}
-
-type postcssTransformation struct {
-       options Options
-       rs      *resource.Spec
-}
-
-func (t *postcssTransformation) Key() resource.ResourceTransformationKey {
-       return resource.NewResourceTransformationKey("postcss", t.options)
-}
-
-// Transform shells out to postcss-cli to do the heavy lifting.
-// For this to work, you need some additional tools. To install them globally:
-// npm install -g postcss-cli
-// npm install -g autoprefixer
-func (t *postcssTransformation) Transform(ctx *resource.ResourceTransformationCtx) error {
-
-       const localPostCSSPath = "node_modules/postcss-cli/bin/"
-       const binaryName = "postcss"
-
-       // Try first in the project's node_modules.
-       csiBinPath := filepath.Join(t.rs.WorkingDir, localPostCSSPath, binaryName)
-
-       binary := csiBinPath
-
-       if _, err := exec.LookPath(binary); err != nil {
-               // Try PATH
-               binary = binaryName
-               if _, err := exec.LookPath(binary); err != nil {
-                       // This may be on a CI server etc. Will fall back to pre-built assets.
-                       return herrors.ErrFeatureNotAvailable
-               }
-       }
-
-       var configFile string
-       logger := t.rs.Logger
-
-       if t.options.Config != "" {
-               configFile = t.options.Config
-       } else {
-               configFile = "postcss.config.js"
-       }
-
-       configFile = filepath.Clean(configFile)
-
-       // We need an abolute filename to the config file.
-       if !filepath.IsAbs(configFile) {
-               // We resolve this against the virtual Work filesystem, to allow
-               // this config file to live in one of the themes if needed.
-               fi, err := t.rs.BaseFs.Work.Fs.Stat(configFile)
-               if err != nil {
-                       if t.options.Config != "" {
-                               // Only fail if the user specificed config file is not found.
-                               return errors.Wrapf(err, "postcss config %q not found:", configFile)
-                       }
-                       configFile = ""
-               } else {
-                       configFile = fi.(hugofs.RealFilenameInfo).RealFilename()
-               }
-       }
-
-       var cmdArgs []string
-
-       if configFile != "" {
-               logger.INFO.Println("postcss: use config file", configFile)
-               cmdArgs = []string{"--config", configFile}
-       }
-
-       if optArgs := t.options.toArgs(); len(optArgs) > 0 {
-               cmdArgs = append(cmdArgs, optArgs...)
-       }
-
-       cmd := exec.Command(binary, cmdArgs...)
-
-       cmd.Stdout = ctx.To
-       cmd.Stderr = os.Stderr
-
-       stdin, err := cmd.StdinPipe()
-       if err != nil {
-               return err
-       }
-
-       go func() {
-               defer stdin.Close()
-               io.Copy(stdin, ctx.From)
-       }()
-
-       err = cmd.Run()
-       if err != nil {
-               return err
-       }
-
-       return nil
-}
-
-// Process transforms the given Resource with the PostCSS processor.
-func (c *Client) Process(res resource.Resource, options Options) (resource.Resource, error) {
-       return c.rs.Transform(
-               res,
-               &postcssTransformation{rs: c.rs, options: options},
-       )
-}
diff --git a/resource/resource_factories/bundler/bundler.go b/resource/resource_factories/bundler/bundler.go
new file mode 100644 (file)
index 0000000..70b8ee5
--- /dev/null
@@ -0,0 +1,121 @@
+// 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 bundler contains functions for concatenation etc. of Resource objects.
+package bundler
+
+import (
+       "fmt"
+       "io"
+       "path/filepath"
+
+       "github.com/gohugoio/hugo/common/hugio"
+       "github.com/gohugoio/hugo/media"
+       "github.com/gohugoio/hugo/resource"
+)
+
+// Client contains methods perform concatenation and other bundling related
+// tasks to Resource objects.
+type Client struct {
+       rs *resource.Spec
+}
+
+// New creates a new Client with the given specification.
+func New(rs *resource.Spec) *Client {
+       return &Client{rs: rs}
+}
+
+type multiReadSeekCloser struct {
+       mr      io.Reader
+       sources []hugio.ReadSeekCloser
+}
+
+func (r *multiReadSeekCloser) Read(p []byte) (n int, err error) {
+       return r.mr.Read(p)
+}
+
+func (r *multiReadSeekCloser) Seek(offset int64, whence int) (newOffset int64, err error) {
+       for _, s := range r.sources {
+               newOffset, err = s.Seek(offset, whence)
+               if err != nil {
+                       return
+               }
+       }
+       return
+}
+
+func (r *multiReadSeekCloser) Close() error {
+       for _, s := range r.sources {
+               s.Close()
+       }
+       return nil
+}
+
+// Concat concatenates the list of Resource objects.
+func (c *Client) Concat(targetPath string, resources resource.Resources) (resource.Resource, error) {
+       // The CACHE_OTHER will make sure this will be re-created and published on rebuilds.
+       return c.rs.ResourceCache.GetOrCreate(resource.CACHE_OTHER, targetPath, func() (resource.Resource, error) {
+               var resolvedm media.Type
+
+               // The given set of resources must be of the same Media Type.
+               // We may improve on that in the future, but then we need to know more.
+               for i, r := range resources {
+                       if i > 0 && r.MediaType().Type() != resolvedm.Type() {
+                               return nil, fmt.Errorf("resources in Concat must be of the same Media Type, got %q and %q", r.MediaType().Type(), resolvedm.Type())
+                       }
+                       resolvedm = r.MediaType()
+               }
+
+               concatr := func() (hugio.ReadSeekCloser, error) {
+                       var rcsources []hugio.ReadSeekCloser
+                       for _, s := range resources {
+                               rcr, ok := s.(resource.ReadSeekCloserResource)
+                               if !ok {
+                                       return nil, fmt.Errorf("resource %T does not implement resource.ReadSeekerCloserResource", s)
+                               }
+                               rc, err := rcr.ReadSeekCloser()
+                               if err != nil {
+                                       // Close the already opened.
+                                       for _, rcs := range rcsources {
+                                               rcs.Close()
+                                       }
+                                       return nil, err
+                               }
+                               rcsources = append(rcsources, rc)
+                       }
+
+                       readers := make([]io.Reader, len(rcsources))
+                       for i := 0; i < len(rcsources); i++ {
+                               readers[i] = rcsources[i]
+                       }
+
+                       mr := io.MultiReader(readers...)
+
+                       return &multiReadSeekCloser{mr: mr, sources: rcsources}, nil
+               }
+
+               composite, err := c.rs.NewForFs(
+                       c.rs.FileCaches.AssetsCache().Fs,
+                       resource.ResourceSourceDescriptor{
+                               LazyPublish:        true,
+                               OpenReadSeekCloser: concatr,
+                               RelTargetFilename:  filepath.Clean(targetPath)})
+
+               if err != nil {
+                       return nil, err
+               }
+
+               return composite, nil
+       })
+
+}
diff --git a/resource/resource_factories/create/create.go b/resource/resource_factories/create/create.go
new file mode 100644 (file)
index 0000000..db23930
--- /dev/null
@@ -0,0 +1,64 @@
+// 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 create contains functions for to create Resource objects. This will
+// typically non-files.
+package create
+
+import (
+       "path/filepath"
+
+       "github.com/spf13/afero"
+
+       "github.com/gohugoio/hugo/common/hugio"
+       "github.com/gohugoio/hugo/resource"
+)
+
+// Client contains methods to create Resource objects.
+// tasks to Resource objects.
+type Client struct {
+       rs *resource.Spec
+}
+
+// New creates a new Client with the given specification.
+func New(rs *resource.Spec) *Client {
+       return &Client{rs: rs}
+}
+
+// Get creates a new Resource by opening the given filename in the given filesystem.
+func (c *Client) Get(fs afero.Fs, filename string) (resource.Resource, error) {
+       filename = filepath.Clean(filename)
+       return c.rs.ResourceCache.GetOrCreate(resource.ResourceKeyPartition(filename), filename, func() (resource.Resource, error) {
+               return c.rs.NewForFs(fs,
+                       resource.ResourceSourceDescriptor{
+                               LazyPublish:    true,
+                               SourceFilename: filename})
+       })
+
+}
+
+// FromString creates a new Resource from a string with the given relative target path.
+func (c *Client) FromString(targetPath, content string) (resource.Resource, error) {
+       return c.rs.ResourceCache.GetOrCreate(resource.CACHE_OTHER, targetPath, func() (resource.Resource, error) {
+               return c.rs.NewForFs(
+                       c.rs.FileCaches.AssetsCache().Fs,
+                       resource.ResourceSourceDescriptor{
+                               LazyPublish: true,
+                               OpenReadSeekCloser: func() (hugio.ReadSeekCloser, error) {
+                                       return hugio.NewReadSeekerNoOpCloserFromString(content), nil
+                               },
+                               RelTargetFilename: filepath.Clean(targetPath)})
+
+       })
+
+}
diff --git a/resource/resource_transformers/integrity/integrity.go b/resource/resource_transformers/integrity/integrity.go
new file mode 100644 (file)
index 0000000..535c06a
--- /dev/null
@@ -0,0 +1,105 @@
+// 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 integrity
+
+import (
+       "crypto/md5"
+       "crypto/sha256"
+       "crypto/sha512"
+       "encoding/base64"
+       "encoding/hex"
+       "fmt"
+       "hash"
+       "html/template"
+       "io"
+
+       "github.com/gohugoio/hugo/resource"
+)
+
+const defaultHashAlgo = "sha256"
+
+// Client contains methods to fingerprint (cachebusting) and other integrity-related
+// methods.
+type Client struct {
+       rs *resource.Spec
+}
+
+// New creates a new Client with the given specification.
+func New(rs *resource.Spec) *Client {
+       return &Client{rs: rs}
+}
+
+type fingerprintTransformation struct {
+       algo string
+}
+
+func (t *fingerprintTransformation) Key() resource.ResourceTransformationKey {
+       return resource.NewResourceTransformationKey("fingerprint", t.algo)
+}
+
+// Transform creates a MD5 hash of the Resource content and inserts that hash before
+// the extension in the filename.
+func (t *fingerprintTransformation) Transform(ctx *resource.ResourceTransformationCtx) error {
+       algo := t.algo
+
+       var h hash.Hash
+
+       switch algo {
+       case "md5":
+               h = md5.New()
+       case "sha256":
+               h = sha256.New()
+       case "sha512":
+               h = sha512.New()
+       default:
+               return fmt.Errorf("unsupported crypto algo: %q, use either md5, sha256 or sha512", algo)
+       }
+
+       io.Copy(io.MultiWriter(h, ctx.To), ctx.From)
+       d, err := digest(h)
+       if err != nil {
+               return err
+       }
+
+       ctx.Data["Integrity"] = integrity(algo, d)
+       ctx.AddOutPathIdentifier("." + hex.EncodeToString(d[:]))
+       return nil
+}
+
+// Fingerprint applies fingerprinting of the given resource and hash algorithm.
+// It defaults to sha256 if none given, and the options are md5, sha256 or sha512.
+// The same algo is used for both the fingerprinting part (aka cache busting) and
+// the base64-encoded Subresource Integrity hash, so you will have to stay away from
+// md5 if you plan to use both.
+// See https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
+func (c *Client) Fingerprint(res resource.Resource, algo string) (resource.Resource, error) {
+       if algo == "" {
+               algo = defaultHashAlgo
+       }
+
+       return c.rs.Transform(
+               res,
+               &fingerprintTransformation{algo: algo},
+       )
+}
+
+func integrity(algo string, sum []byte) template.HTMLAttr {
+       encoded := base64.StdEncoding.EncodeToString(sum)
+       return template.HTMLAttr(algo + "-" + encoded)
+}
+
+func digest(h hash.Hash) ([]byte, error) {
+       sum := h.Sum(nil)
+       return sum, nil
+}
diff --git a/resource/resource_transformers/minifier/minify.go b/resource/resource_transformers/minifier/minify.go
new file mode 100644 (file)
index 0000000..cef22ef
--- /dev/null
@@ -0,0 +1,58 @@
+// 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 minifier
+
+import (
+       "github.com/gohugoio/hugo/minifiers"
+       "github.com/gohugoio/hugo/resource"
+)
+
+// Client for minification of Resource objects. Supported minfiers are:
+// css, html, js, json, svg and xml.
+type Client struct {
+       rs *resource.Spec
+       m  minifiers.Client
+}
+
+// New creates a new Client given a specification. Note that it is the media types
+// configured for the site that is used to match files to the correct minifier.
+func New(rs *resource.Spec) *Client {
+       return &Client{rs: rs, m: minifiers.New(rs.MediaTypes, rs.OutputFormats)}
+}
+
+type minifyTransformation struct {
+       rs *resource.Spec
+       m  minifiers.Client
+}
+
+func (t *minifyTransformation) Key() resource.ResourceTransformationKey {
+       return resource.NewResourceTransformationKey("minify")
+}
+
+func (t *minifyTransformation) Transform(ctx *resource.ResourceTransformationCtx) error {
+       if err := t.m.Minify(ctx.InMediaType, ctx.To, ctx.From); err != nil {
+               return err
+       }
+       ctx.AddOutPathIdentifier(".min")
+       return nil
+}
+
+func (c *Client) Minify(res resource.Resource) (resource.Resource, error) {
+       return c.rs.Transform(
+               res,
+               &minifyTransformation{
+                       rs: c.rs,
+                       m:  c.m},
+       )
+}
diff --git a/resource/resource_transformers/postcss/postcss.go b/resource/resource_transformers/postcss/postcss.go
new file mode 100644 (file)
index 0000000..ec73543
--- /dev/null
@@ -0,0 +1,184 @@
+// 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 postcss
+
+import (
+       "io"
+       "path/filepath"
+
+       "github.com/gohugoio/hugo/hugofs"
+       "github.com/pkg/errors"
+
+       "os"
+       "os/exec"
+
+       "github.com/mitchellh/mapstructure"
+
+       "github.com/gohugoio/hugo/common/herrors"
+       "github.com/gohugoio/hugo/resource"
+)
+
+// Some of the options from https://github.com/postcss/postcss-cli
+type Options struct {
+
+       // Set a custom path to look for a config file.
+       Config string
+
+       NoMap bool `mapstructure:"no-map"` // Disable the default inline sourcemaps
+
+       // Options for when not using a config file
+       Use         string // List of postcss plugins to use
+       Parser      string //  Custom postcss parser
+       Stringifier string // Custom postcss stringifier
+       Syntax      string // Custom postcss syntax
+}
+
+func DecodeOptions(m map[string]interface{}) (opts Options, err error) {
+       if m == nil {
+               return
+       }
+       err = mapstructure.WeakDecode(m, &opts)
+       return
+}
+
+func (opts Options) toArgs() []string {
+       var args []string
+       if opts.NoMap {
+               args = append(args, "--no-map")
+       }
+       if opts.Use != "" {
+               args = append(args, "--use", opts.Use)
+       }
+       if opts.Parser != "" {
+               args = append(args, "--parser", opts.Parser)
+       }
+       if opts.Stringifier != "" {
+               args = append(args, "--stringifier", opts.Stringifier)
+       }
+       if opts.Syntax != "" {
+               args = append(args, "--syntax", opts.Syntax)
+       }
+       return args
+}
+
+// Client is the client used to do PostCSS transformations.
+type Client struct {
+       rs *resource.Spec
+}
+
+// New creates a new Client with the given specification.
+func New(rs *resource.Spec) *Client {
+       return &Client{rs: rs}
+}
+
+type postcssTransformation struct {
+       options Options
+       rs      *resource.Spec
+}
+
+func (t *postcssTransformation) Key() resource.ResourceTransformationKey {
+       return resource.NewResourceTransformationKey("postcss", t.options)
+}
+
+// Transform shells out to postcss-cli to do the heavy lifting.
+// For this to work, you need some additional tools. To install them globally:
+// npm install -g postcss-cli
+// npm install -g autoprefixer
+func (t *postcssTransformation) Transform(ctx *resource.ResourceTransformationCtx) error {
+
+       const localPostCSSPath = "node_modules/postcss-cli/bin/"
+       const binaryName = "postcss"
+
+       // Try first in the project's node_modules.
+       csiBinPath := filepath.Join(t.rs.WorkingDir, localPostCSSPath, binaryName)
+
+       binary := csiBinPath
+
+       if _, err := exec.LookPath(binary); err != nil {
+               // Try PATH
+               binary = binaryName
+               if _, err := exec.LookPath(binary); err != nil {
+                       // This may be on a CI server etc. Will fall back to pre-built assets.
+                       return herrors.ErrFeatureNotAvailable
+               }
+       }
+
+       var configFile string
+       logger := t.rs.Logger
+
+       if t.options.Config != "" {
+               configFile = t.options.Config
+       } else {
+               configFile = "postcss.config.js"
+       }
+
+       configFile = filepath.Clean(configFile)
+
+       // We need an abolute filename to the config file.
+       if !filepath.IsAbs(configFile) {
+               // We resolve this against the virtual Work filesystem, to allow
+               // this config file to live in one of the themes if needed.
+               fi, err := t.rs.BaseFs.Work.Fs.Stat(configFile)
+               if err != nil {
+                       if t.options.Config != "" {
+                               // Only fail if the user specificed config file is not found.
+                               return errors.Wrapf(err, "postcss config %q not found:", configFile)
+                       }
+                       configFile = ""
+               } else {
+                       configFile = fi.(hugofs.RealFilenameInfo).RealFilename()
+               }
+       }
+
+       var cmdArgs []string
+
+       if configFile != "" {
+               logger.INFO.Println("postcss: use config file", configFile)
+               cmdArgs = []string{"--config", configFile}
+       }
+
+       if optArgs := t.options.toArgs(); len(optArgs) > 0 {
+               cmdArgs = append(cmdArgs, optArgs...)
+       }
+
+       cmd := exec.Command(binary, cmdArgs...)
+
+       cmd.Stdout = ctx.To
+       cmd.Stderr = os.Stderr
+
+       stdin, err := cmd.StdinPipe()
+       if err != nil {
+               return err
+       }
+
+       go func() {
+               defer stdin.Close()
+               io.Copy(stdin, ctx.From)
+       }()
+
+       err = cmd.Run()
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// Process transforms the given Resource with the PostCSS processor.
+func (c *Client) Process(res resource.Resource, options Options) (resource.Resource, error) {
+       return c.rs.Transform(
+               res,
+               &postcssTransformation{rs: c.rs, options: options},
+       )
+}
diff --git a/resource/resource_transformers/templates/execute_as_template.go b/resource/resource_transformers/templates/execute_as_template.go
new file mode 100644 (file)
index 0000000..a126b26
--- /dev/null
@@ -0,0 +1,75 @@
+// 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 templates contains functions for template processing of Resource objects.
+package templates
+
+import (
+       "github.com/gohugoio/hugo/helpers"
+       "github.com/gohugoio/hugo/resource"
+       "github.com/gohugoio/hugo/tpl"
+       "github.com/pkg/errors"
+)
+
+// Client contains methods to perform template processing of Resource objects.
+type Client struct {
+       rs *resource.Spec
+
+       textTemplate tpl.TemplateParseFinder
+}
+
+// New creates a new Client with the given specification.
+func New(rs *resource.Spec, textTemplate tpl.TemplateParseFinder) *Client {
+       if rs == nil {
+               panic("must provice a resource Spec")
+       }
+       if textTemplate == nil {
+               panic("must provide a textTemplate")
+       }
+       return &Client{rs: rs, textTemplate: textTemplate}
+}
+
+type executeAsTemplateTransform struct {
+       rs           *resource.Spec
+       textTemplate tpl.TemplateParseFinder
+       targetPath   string
+       data         interface{}
+}
+
+func (t *executeAsTemplateTransform) Key() resource.ResourceTransformationKey {
+       return resource.NewResourceTransformationKey("execute-as-template", t.targetPath)
+}
+
+func (t *executeAsTemplateTransform) Transform(ctx *resource.ResourceTransformationCtx) error {
+       tplStr := helpers.ReaderToString(ctx.From)
+       templ, err := t.textTemplate.Parse(ctx.InPath, tplStr)
+       if err != nil {
+               return errors.Wrapf(err, "failed to parse Resource %q as Template:", ctx.InPath)
+       }
+
+       ctx.OutPath = t.targetPath
+
+       return templ.Execute(ctx.To, t.data)
+}
+
+func (c *Client) ExecuteAsTemplate(res resource.Resource, targetPath string, data interface{}) (resource.Resource, error) {
+       return c.rs.Transform(
+               res,
+               &executeAsTemplateTransform{
+                       rs:           c.rs,
+                       targetPath:   helpers.ToSlashTrimLeading(targetPath),
+                       textTemplate: c.textTemplate,
+                       data:         data,
+               },
+       )
+}
diff --git a/resource/resource_transformers/tocss/scss/client.go b/resource/resource_transformers/tocss/scss/client.go
new file mode 100644 (file)
index 0000000..126727e
--- /dev/null
@@ -0,0 +1,109 @@
+// 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/resource"
+       "github.com/mitchellh/mapstructure"
+)
+
+type Client struct {
+       rs     *resource.Spec
+       sfs    *filesystems.SourceFilesystem
+       workFs *filesystems.SourceFilesystem
+}
+
+func New(fs *filesystems.SourceFilesystem, rs *resource.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() resource.ResourceTransformationKey {
+       return resource.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/resource/resource_transformers/tocss/scss/tocss.go b/resource/resource_transformers/tocss/scss/tocss.go
new file mode 100644 (file)
index 0000000..984e14f
--- /dev/null
@@ -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/resource"
+       "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 *resource.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/resource/resource_transformers/tocss/scss/tocss_notavailable.go b/resource/resource_transformers/tocss/scss/tocss_notavailable.go
new file mode 100644 (file)
index 0000000..df918b3
--- /dev/null
@@ -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/resource"
+)
+
+// Used in tests.
+func Supports() bool {
+       return false
+}
+
+func (t *toCSSTransformation) Transform(ctx *resource.ResourceTransformationCtx) error {
+       return herrors.ErrFeatureNotAvailable
+}
diff --git a/resource/templates/execute_as_template.go b/resource/templates/execute_as_template.go
deleted file mode 100644 (file)
index a126b26..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-// 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 templates contains functions for template processing of Resource objects.
-package templates
-
-import (
-       "github.com/gohugoio/hugo/helpers"
-       "github.com/gohugoio/hugo/resource"
-       "github.com/gohugoio/hugo/tpl"
-       "github.com/pkg/errors"
-)
-
-// Client contains methods to perform template processing of Resource objects.
-type Client struct {
-       rs *resource.Spec
-
-       textTemplate tpl.TemplateParseFinder
-}
-
-// New creates a new Client with the given specification.
-func New(rs *resource.Spec, textTemplate tpl.TemplateParseFinder) *Client {
-       if rs == nil {
-               panic("must provice a resource Spec")
-       }
-       if textTemplate == nil {
-               panic("must provide a textTemplate")
-       }
-       return &Client{rs: rs, textTemplate: textTemplate}
-}
-
-type executeAsTemplateTransform struct {
-       rs           *resource.Spec
-       textTemplate tpl.TemplateParseFinder
-       targetPath   string
-       data         interface{}
-}
-
-func (t *executeAsTemplateTransform) Key() resource.ResourceTransformationKey {
-       return resource.NewResourceTransformationKey("execute-as-template", t.targetPath)
-}
-
-func (t *executeAsTemplateTransform) Transform(ctx *resource.ResourceTransformationCtx) error {
-       tplStr := helpers.ReaderToString(ctx.From)
-       templ, err := t.textTemplate.Parse(ctx.InPath, tplStr)
-       if err != nil {
-               return errors.Wrapf(err, "failed to parse Resource %q as Template:", ctx.InPath)
-       }
-
-       ctx.OutPath = t.targetPath
-
-       return templ.Execute(ctx.To, t.data)
-}
-
-func (c *Client) ExecuteAsTemplate(res resource.Resource, targetPath string, data interface{}) (resource.Resource, error) {
-       return c.rs.Transform(
-               res,
-               &executeAsTemplateTransform{
-                       rs:           c.rs,
-                       targetPath:   helpers.ToSlashTrimLeading(targetPath),
-                       textTemplate: c.textTemplate,
-                       data:         data,
-               },
-       )
-}
diff --git a/resource/tocss/scss/client.go b/resource/tocss/scss/client.go
deleted file mode 100644 (file)
index 126727e..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-// 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/resource"
-       "github.com/mitchellh/mapstructure"
-)
-
-type Client struct {
-       rs     *resource.Spec
-       sfs    *filesystems.SourceFilesystem
-       workFs *filesystems.SourceFilesystem
-}
-
-func New(fs *filesystems.SourceFilesystem, rs *resource.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() resource.ResourceTransformationKey {
-       return resource.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/resource/tocss/scss/tocss.go b/resource/tocss/scss/tocss.go
deleted file mode 100644 (file)
index 984e14f..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-// 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/resource"
-       "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 *resource.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/resource/tocss/scss/tocss_notavailable.go b/resource/tocss/scss/tocss_notavailable.go
deleted file mode 100644 (file)
index df918b3..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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/resource"
-)
-
-// Used in tests.
-func Supports() bool {
-       return false
-}
-
-func (t *toCSSTransformation) Transform(ctx *resource.ResourceTransformationCtx) error {
-       return herrors.ErrFeatureNotAvailable
-}
index a20b2e503761392e1e38da348a6c3fdb5c47012b..497611b75e159be2d9ac0922f0505b3e88c971d9 100644 (file)
@@ -23,13 +23,13 @@ import (
 
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/resource"
-       "github.com/gohugoio/hugo/resource/bundler"
-       "github.com/gohugoio/hugo/resource/create"
-       "github.com/gohugoio/hugo/resource/integrity"
-       "github.com/gohugoio/hugo/resource/minifier"
-       "github.com/gohugoio/hugo/resource/postcss"
-       "github.com/gohugoio/hugo/resource/templates"
-       "github.com/gohugoio/hugo/resource/tocss/scss"
+       "github.com/gohugoio/hugo/resource/resource_factories/bundler"
+       "github.com/gohugoio/hugo/resource/resource_factories/create"
+       "github.com/gohugoio/hugo/resource/resource_transformers/integrity"
+       "github.com/gohugoio/hugo/resource/resource_transformers/minifier"
+       "github.com/gohugoio/hugo/resource/resource_transformers/postcss"
+       "github.com/gohugoio/hugo/resource/resource_transformers/templates"
+       "github.com/gohugoio/hugo/resource/resource_transformers/tocss/scss"
        "github.com/spf13/cast"
 )