Prevent resource publishing for transformed inline resources
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 21 Dec 2018 09:05:57 +0000 (10:05 +0100)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 21 Dec 2018 12:41:46 +0000 (13:41 +0100)
That is, if only `.Content` is accessed.

This means that, for a transformed resource to be published to `/public`, you need to access either `.RelPermalink` or `Permalink`.

Fixes #4944

hugolib/resource_chain_test.go
resource/transform.go

index 0335339a3c8aecfaa67597ef977de48d97024e98..66a0a7ce6b5fb3cd6fb42b5086ec3ab6bf769788 100644 (file)
@@ -168,6 +168,8 @@ T1: {{ $r.Content }}
 func TestResourceChain(t *testing.T) {
        t.Parallel()
 
+       assert := require.New(t)
+
        tests := []struct {
                name      string
                shouldRun func() bool
@@ -199,7 +201,7 @@ T6: {{ $bundle1.Permalink }}
                        b.AssertFileContent("public/index.html", `T5 RelPermalink: /sass/styles3.css|`)
                        b.AssertFileContent("public/index.html", `T6: http://example.com/styles/bundle1.css`)
 
-                       b.AssertFileContent("public/styles/templ.min.css", `.home{color:blue}`)
+                       assert.False(b.CheckExists("public/styles/templ.min.css"))
                        b.AssertFileContent("public/styles/bundle1.css", `.home{color:blue}body{color:#333}`)
 
                }},
@@ -313,6 +315,30 @@ T1: {{ $r1.Permalink }}|{{ $r1.RelPermalink }}
 
                }},
 
+               // https://github.com/gohugoio/hugo/issues/4944
+               {"Prevent resource publish on .Content only", func() bool { return true }, func(b *sitesBuilder) {
+                       b.WithTemplates("home.html", `
+{{ $cssInline := "body { color: green; }" | resources.FromString "inline.css" | minify }}
+{{ $cssPublish1 := "body { color: blue; }" | resources.FromString "external1.css" | minify }}
+{{ $cssPublish2 := "body { color: orange; }" | resources.FromString "external2.css" | minify }}
+
+Inline: {{ $cssInline.Content }}
+Publish 1: {{ $cssPublish1.Content }} {{ $cssPublish1.RelPermalink }}
+Publish 2: {{ $cssPublish2.Permalink }}
+`)
+
+               }, func(b *sitesBuilder) {
+                       b.AssertFileContent("public/index.html",
+                               `Inline: body{color:green}`,
+                               "Publish 1: body{color:blue} /external1.min.css",
+                               "Publish 2: http://example.com/external2.min.css",
+                       )
+                       assert.True(b.CheckExists("public/external2.min.css"), "Referenced content should be copied to /public")
+                       assert.True(b.CheckExists("public/external1.min.css"), "Referenced content should be copied to /public")
+
+                       assert.False(b.CheckExists("public/inline.min.css"), "Inline content should not be copied to /public")
+               }},
+
                {"template", func() bool { return true }, func(b *sitesBuilder) {}, func(b *sitesBuilder) {
                }},
        }
index 0b5772dde3bf6daa364a604ad0c7151371e4dffb..796c7ee2330415c7cf0ccf1c22a9778eb45368fe 100644 (file)
@@ -183,6 +183,11 @@ type transformedResource struct {
        transformInit sync.Once
        transformErr  error
 
+       // We delay publishing until either .RelPermalink or .Permalink
+       // is invoked.
+       publishInit sync.Once
+       published   bool
+
        // The transformed values
        content     string
        contentInit sync.Once
@@ -220,7 +225,7 @@ func (r *transformedResource) tryTransformedFileCache(key string) io.ReadCloser
 }
 
 func (r *transformedResource) Content() (interface{}, error) {
-       if err := r.initTransform(true); err != nil {
+       if err := r.initTransform(true, false); err != nil {
                return nil, err
        }
        if err := r.initContent(); err != nil {
@@ -230,14 +235,14 @@ func (r *transformedResource) Content() (interface{}, error) {
 }
 
 func (r *transformedResource) Data() interface{} {
-       if err := r.initTransform(false); err != nil {
+       if err := r.initTransform(false, false); err != nil {
                return noData
        }
        return r.MetaData
 }
 
 func (r *transformedResource) MediaType() media.Type {
-       if err := r.initTransform(false); err != nil {
+       if err := r.initTransform(false, false); err != nil {
                return media.Type{}
        }
        m, _ := r.cache.rs.MediaTypes.GetByType(r.MediaTypeV)
@@ -245,14 +250,14 @@ func (r *transformedResource) MediaType() media.Type {
 }
 
 func (r *transformedResource) Permalink() string {
-       if err := r.initTransform(false); err != nil {
+       if err := r.initTransform(false, true); err != nil {
                return ""
        }
        return r.linker.permalinkFor(r.Target)
 }
 
 func (r *transformedResource) RelPermalink() string {
-       if err := r.initTransform(false); err != nil {
+       if err := r.initTransform(false, true); err != nil {
                return ""
        }
        return r.linker.relPermalinkFor(r.Target)
@@ -271,11 +276,11 @@ func (r *transformedResource) initContent() error {
        return err
 }
 
-func (r *transformedResource) transform(setContent bool) (err error) {
+func (r *transformedResource) openPublishFileForWriting(relTargetPath string) (io.WriteCloser, error) {
+       return helpers.OpenFilesForWriting(r.cache.rs.PublishFs, r.linker.relTargetPathsFor(relTargetPath)...)
+}
 
-       openPublishFileForWriting := func(relTargetPath string) (io.WriteCloser, error) {
-               return helpers.OpenFilesForWriting(r.cache.rs.PublishFs, r.linker.relTargetPathsFor(relTargetPath)...)
-       }
+func (r *transformedResource) transform(setContent, publish bool) (err error) {
 
        // This can be the last resource in a chain.
        // Rewind and create a processing chain.
@@ -345,7 +350,7 @@ func (r *transformedResource) transform(setContent bool) (err error) {
 
        tctx := &ResourceTransformationCtx{
                Data:                  r.transformedResourceMetadata.MetaData,
-               OpenResourcePublisher: openPublishFileForWriting,
+               OpenResourcePublisher: r.openPublishFileForWriting,
        }
 
        tctx.InMediaType = first.MediaType()
@@ -426,14 +431,18 @@ func (r *transformedResource) transform(setContent bool) (err error) {
                r.MediaTypeV = tctx.OutMediaType.Type()
        }
 
-       publicw, err := openPublishFileForWriting(r.Target)
-       if err != nil {
-               r.transformErr = err
-               return
-       }
-       defer publicw.Close()
+       var publishwriters []io.WriteCloser
+
+       if publish {
+               publicw, err := r.openPublishFileForWriting(r.Target)
+               if err != nil {
+                       r.transformErr = err
+                       return err
+               }
+               defer publicw.Close()
 
-       publishwriters := []io.WriteCloser{publicw}
+               publishwriters = append(publishwriters, publicw)
+       }
 
        if transformedContentr == nil {
                // Also write it to the cache
@@ -474,13 +483,49 @@ func (r *transformedResource) transform(setContent bool) (err error) {
        return nil
 
 }
-func (r *transformedResource) initTransform(setContent bool) error {
+func (r *transformedResource) initTransform(setContent, publish bool) error {
        r.transformInit.Do(func() {
-               if err := r.transform(setContent); err != nil {
+               r.published = publish
+               if err := r.transform(setContent, publish); err != nil {
                        r.transformErr = err
                        r.cache.rs.Logger.ERROR.Println("error: failed to transform resource:", err)
                }
+
+       })
+
+       if !publish {
+               return r.transformErr
+       }
+
+       r.publishInit.Do(func() {
+               if r.published {
+                       return
+               }
+
+               r.published = true
+
+               // Copy the file from cache to /public
+               _, src, err := r.cache.fileCache.Get(r.sourceFilename)
+
+               if err == nil {
+                       defer src.Close()
+
+                       var dst io.WriteCloser
+                       dst, err = r.openPublishFileForWriting(r.Target)
+                       if err == nil {
+                               defer dst.Close()
+                               io.Copy(dst, src)
+                       }
+               }
+
+               if err != nil {
+                       r.transformErr = err
+                       r.cache.rs.Logger.ERROR.Println("error: failed to publish resource:", err)
+                       return
+               }
+
        })
+
        return r.transformErr
 }