From cd0112a05a9ddb7043c9808284f93d8099c48473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Tue, 24 May 2022 09:34:36 +0200 Subject: [PATCH] Add resources.Copy Implemented by most Resource objects, but not Page (for now). Fixes #9313 --- helpers/general.go | 10 +- resources/image.go | 11 +- resources/resource.go | 27 ++++- resources/resource/resourcetypes.go | 3 +- resources/resource_factories/create/create.go | 7 ++ resources/transform.go | 15 +++ tpl/resources/integration_test.go | 100 ++++++++++++++++++ tpl/resources/resources.go | 9 ++ 8 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 tpl/resources/integration_test.go diff --git a/helpers/general.go b/helpers/general.go index e31bbfc9d..462ec773d 100644 --- a/helpers/general.go +++ b/helpers/general.go @@ -33,8 +33,6 @@ import ( "github.com/mitchellh/hashstructure" - "github.com/gohugoio/hugo/hugofs" - "github.com/gohugoio/hugo/common/hugo" "github.com/spf13/afero" @@ -521,13 +519,7 @@ func PrintFs(fs afero.Fs, path string, w io.Writer) { } afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error { - var filename string - var meta any - if fim, ok := info.(hugofs.FileMetaInfo); ok { - filename = fim.Meta().Filename - meta = fim.Meta() - } - fmt.Fprintf(w, " %q %q\t\t%v\n", path, filename, meta) + fmt.Println(path) return nil }) } diff --git a/resources/image.go b/resources/image.go index f431784b4..3eda709d9 100644 --- a/resources/image.go +++ b/resources/image.go @@ -134,7 +134,7 @@ func (i *imageResource) getExif() *exif.ExifInfo { return i.meta.Exif } -// Cloneis for internal use. +// Clone is for internal use. func (i *imageResource) Clone() resource.Resource { gr := i.baseResource.Clone().(baseResource) return &imageResource{ @@ -144,6 +144,15 @@ func (i *imageResource) Clone() resource.Resource { } } +func (i *imageResource) cloneTo(targetPath string) resource.Resource { + gr := i.baseResource.cloneTo(targetPath).(baseResource) + return &imageResource{ + root: i.root, + Image: i.WithSpec(gr), + baseResource: gr, + } +} + func (i *imageResource) cloneWithUpdates(u *transformationUpdate) (baseResource, error) { base, err := i.baseResource.cloneWithUpdates(u) if err != nil { diff --git a/resources/resource.go b/resources/resource.go index ead483d65..e38090dbe 100644 --- a/resources/resource.go +++ b/resources/resource.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Hugo Authors. All rights reserved. +// Copyright 2022 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. @@ -120,8 +120,19 @@ func (t transformerNotAvailable) Key() internal.ResourceTransformationKey { return t.key } +// resourceCopier is for internal use. +type resourceCopier interface { + cloneTo(targetPath string) resource.Resource +} + +// Copy copies r to the targetPath given. +func Copy(r resource.Resource, targetPath string) resource.Resource { + return r.(resourceCopier).cloneTo(targetPath) +} + type baseResourceResource interface { resource.Cloner + resourceCopier resource.ContentProvider resource.Resource resource.Identifier @@ -225,6 +236,20 @@ func (l *genericResource) Clone() resource.Resource { return l.clone() } +func (l *genericResource) cloneTo(targetPath string) resource.Resource { + c := l.clone() + + targetPath = helpers.ToSlashTrimLeading(targetPath) + dir, file := path.Split(targetPath) + + c.resourcePathDescriptor = &resourcePathDescriptor{ + relTargetDirFile: dirFile{dir: dir, file: file}, + } + + return c + +} + func (l *genericResource) Content() (any, error) { if err := l.initContent(); err != nil { return nil, err diff --git a/resources/resource/resourcetypes.go b/resources/resource/resourcetypes.go index e3251aabe..4ba95c170 100644 --- a/resources/resource/resourcetypes.go +++ b/resources/resource/resourcetypes.go @@ -26,8 +26,7 @@ var ( _ ResourceError = (*resourceError)(nil) ) -// Cloner is an internal template and not meant for use in the templates. It -// may change without notice. +// Cloner is for internal use. type Cloner interface { Clone() Resource } diff --git a/resources/resource_factories/create/create.go b/resources/resource_factories/create/create.go index 3827411a9..075d25736 100644 --- a/resources/resource_factories/create/create.go +++ b/resources/resource_factories/create/create.go @@ -51,6 +51,13 @@ func New(rs *resources.Spec) *Client { } } +// Copy copies r to the new targetPath. +func (c *Client) Copy(r resource.Resource, targetPath string) (resource.Resource, error) { + return c.rs.ResourceCache.GetOrCreate(resources.ResourceCacheKey(targetPath), func() (resource.Resource, error) { + return resources.Copy(r, targetPath), nil + }) +} + // Get creates a new Resource by opening the given filename in the assets filesystem. func (c *Client) Get(filename string) (resource.Resource, error) { filename = filepath.Clean(filename) diff --git a/resources/transform.go b/resources/transform.go index a470c94da..bb1608cbd 100644 --- a/resources/transform.go +++ b/resources/transform.go @@ -42,6 +42,7 @@ import ( var ( _ resource.ContentResource = (*resourceAdapter)(nil) + _ resourceCopier = (*resourceAdapter)(nil) _ resource.ReadSeekCloserResource = (*resourceAdapter)(nil) _ resource.Resource = (*resourceAdapter)(nil) _ resource.Source = (*resourceAdapter)(nil) @@ -175,6 +176,19 @@ func (r *resourceAdapter) Data() any { return r.target.Data() } +func (r resourceAdapter) cloneTo(targetPath string) resource.Resource { + newtTarget := r.target.cloneTo(targetPath) + newInner := &resourceAdapterInner{ + spec: r.spec, + target: newtTarget.(transformableResource), + } + if r.resourceAdapterInner.publishOnce != nil { + newInner.publishOnce = &publishOnce{} + } + r.resourceAdapterInner = newInner + return &r +} + func (r *resourceAdapter) Crop(spec string) (images.ImageResource, error) { return r.getImageOps().Crop(spec) } @@ -596,6 +610,7 @@ type transformableResource interface { resource.ContentProvider resource.Resource resource.Identifier + resourceCopier } type transformationUpdate struct { diff --git a/tpl/resources/integration_test.go b/tpl/resources/integration_test.go new file mode 100644 index 000000000..06f98eeee --- /dev/null +++ b/tpl/resources/integration_test.go @@ -0,0 +1,100 @@ +// Copyright 2022s 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 resources_test + +import ( + "testing" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/hugolib" +) + +func TestCopy(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +baseURL = "http://example.com/blog" +-- assets/images/pixel.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- layouts/index.html -- +{{/* Image resources */}} +{{ $img := resources.Get "images/pixel.png" }} +{{ $imgCopy1 := $img | resources.Copy "images/copy.png" }} +{{ $imgCopy1 = $imgCopy1.Resize "3x4"}} +{{ $imgCopy2 := $imgCopy1 | resources.Copy "images/copy2.png" }} +{{ $imgCopy3 := $imgCopy1 | resources.Copy "images/copy3.png" }} +Image Orig: {{ $img.RelPermalink}}|{{ $img.MediaType }}|{{ $img.Width }}|{{ $img.Height }}| +Image Copy1: {{ $imgCopy1.RelPermalink}}|{{ $imgCopy1.MediaType }}|{{ $imgCopy1.Width }}|{{ $imgCopy1.Height }}| +Image Copy2: {{ $imgCopy2.RelPermalink}}|{{ $imgCopy2.MediaType }}|{{ $imgCopy2.Width }}|{{ $imgCopy2.Height }}| +Image Copy3: {{ $imgCopy3.MediaType }}|{{ $imgCopy3.Width }}|{{ $imgCopy3.Height }}| + +{{/* Generic resources */}} +{{ $targetPath := "js/vars.js" }} +{{ $orig := "let foo;" | resources.FromString "js/foo.js" }} +{{ $copy1 := $orig | resources.Copy "js/copies/bar.js" }} +{{ $copy2 := $orig | resources.Copy "js/copies/baz.js" | fingerprint "md5" }} +{{ $copy3 := $copy2 | resources.Copy "js/copies/moo.js" | minify }} + +Orig: {{ $orig.RelPermalink}}|{{ $orig.MediaType }}|{{ $orig.Content | safeJS }}| +Copy1: {{ $copy1.RelPermalink}}|{{ $copy1.MediaType }}|{{ $copy1.Content | safeJS }}| +Copy2: {{ $copy2.RelPermalink}}|{{ $copy2.MediaType }}|{{ $copy2.Content | safeJS }}| +Copy3: {{ $copy3.RelPermalink}}|{{ $copy3.MediaType }}|{{ $copy3.Content | safeJS }}| + + ` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }).Build() + + b.AssertFileContent("public/index.html", ` +Image Orig: /blog/images/pixel.png|image/png|1|1| +Image Copy1: /blog/images/copy_hu8aa3346827e49d756ff4e630147c42b5_70_3x4_resize_box_3.png|image/png|3|4| +Image Copy2: /blog/images/copy2.png|image/png|3|4 +Image Copy3: image/png|3|4| +Orig: /blog/js/foo.js|application/javascript|let foo;| +Copy1: /blog/js/copies/bar.js|application/javascript|let foo;| +Copy2: /blog/js/copies/baz.a677329fc6c4ad947e0c7116d91f37a2.js|application/javascript|let foo;| +Copy3: /blog/js/copies/moo.a677329fc6c4ad947e0c7116d91f37a2.min.js|application/javascript|let foo| + + `) + + b.AssertDestinationExists("images/copy2.png", true) + // No permalink used. + b.AssertDestinationExists("images/copy3.png", false) + +} + +func TestCopyPageShouldFail(t *testing.T) { + t.Parallel() + + files := ` +-- config.toml -- +-- layouts/index.html -- +{{/* This is currently not supported. */}} +{{ $copy := .Copy "copy.md" }} + + ` + + b, err := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{ + T: t, + TxtarString: files, + }).BuildE() + + b.Assert(err, qt.IsNotNil) + +} diff --git a/tpl/resources/resources.go b/tpl/resources/resources.go index 2adec358c..165152c78 100644 --- a/tpl/resources/resources.go +++ b/tpl/resources/resources.go @@ -111,6 +111,15 @@ func (ns *Namespace) getscssClientDartSass() (*dartsass.Client, error) { return ns.scssClientDartSass, err } +// Copy copies r to the new targetPath in s. +func (ns *Namespace) Copy(s any, r resource.Resource) (resource.Resource, error) { + targetPath, err := cast.ToStringE(s) + if err != nil { + panic(err) + } + return ns.createClient.Copy(r, targetPath) +} + // Get locates the filename given in Hugo's assets filesystem // and creates a Resource object that can be used for further transformations. func (ns *Namespace) Get(filename any) resource.Resource {