mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
Add resources.Copy
Implemented by most Resource objects, but not Page (for now). Fixes #9313
This commit is contained in:
parent
6f7fbe03b1
commit
cd0112a05a
8 changed files with 169 additions and 13 deletions
|
@ -33,8 +33,6 @@ import (
|
||||||
|
|
||||||
"github.com/mitchellh/hashstructure"
|
"github.com/mitchellh/hashstructure"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/hugofs"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/hugo"
|
"github.com/gohugoio/hugo/common/hugo"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"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 {
|
afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error {
|
||||||
var filename string
|
fmt.Println(path)
|
||||||
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)
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,7 @@ func (i *imageResource) getExif() *exif.ExifInfo {
|
||||||
return i.meta.Exif
|
return i.meta.Exif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cloneis for internal use.
|
// Clone is for internal use.
|
||||||
func (i *imageResource) Clone() resource.Resource {
|
func (i *imageResource) Clone() resource.Resource {
|
||||||
gr := i.baseResource.Clone().(baseResource)
|
gr := i.baseResource.Clone().(baseResource)
|
||||||
return &imageResource{
|
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) {
|
func (i *imageResource) cloneWithUpdates(u *transformationUpdate) (baseResource, error) {
|
||||||
base, err := i.baseResource.cloneWithUpdates(u)
|
base, err := i.baseResource.cloneWithUpdates(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with 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
|
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 {
|
type baseResourceResource interface {
|
||||||
resource.Cloner
|
resource.Cloner
|
||||||
|
resourceCopier
|
||||||
resource.ContentProvider
|
resource.ContentProvider
|
||||||
resource.Resource
|
resource.Resource
|
||||||
resource.Identifier
|
resource.Identifier
|
||||||
|
@ -225,6 +236,20 @@ func (l *genericResource) Clone() resource.Resource {
|
||||||
return l.clone()
|
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) {
|
func (l *genericResource) Content() (any, error) {
|
||||||
if err := l.initContent(); err != nil {
|
if err := l.initContent(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -26,8 +26,7 @@ var (
|
||||||
_ ResourceError = (*resourceError)(nil)
|
_ ResourceError = (*resourceError)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cloner is an internal template and not meant for use in the templates. It
|
// Cloner is for internal use.
|
||||||
// may change without notice.
|
|
||||||
type Cloner interface {
|
type Cloner interface {
|
||||||
Clone() Resource
|
Clone() Resource
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
// Get creates a new Resource by opening the given filename in the assets filesystem.
|
||||||
func (c *Client) Get(filename string) (resource.Resource, error) {
|
func (c *Client) Get(filename string) (resource.Resource, error) {
|
||||||
filename = filepath.Clean(filename)
|
filename = filepath.Clean(filename)
|
||||||
|
|
|
@ -42,6 +42,7 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ resource.ContentResource = (*resourceAdapter)(nil)
|
_ resource.ContentResource = (*resourceAdapter)(nil)
|
||||||
|
_ resourceCopier = (*resourceAdapter)(nil)
|
||||||
_ resource.ReadSeekCloserResource = (*resourceAdapter)(nil)
|
_ resource.ReadSeekCloserResource = (*resourceAdapter)(nil)
|
||||||
_ resource.Resource = (*resourceAdapter)(nil)
|
_ resource.Resource = (*resourceAdapter)(nil)
|
||||||
_ resource.Source = (*resourceAdapter)(nil)
|
_ resource.Source = (*resourceAdapter)(nil)
|
||||||
|
@ -175,6 +176,19 @@ func (r *resourceAdapter) Data() any {
|
||||||
return r.target.Data()
|
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) {
|
func (r *resourceAdapter) Crop(spec string) (images.ImageResource, error) {
|
||||||
return r.getImageOps().Crop(spec)
|
return r.getImageOps().Crop(spec)
|
||||||
}
|
}
|
||||||
|
@ -596,6 +610,7 @@ type transformableResource interface {
|
||||||
resource.ContentProvider
|
resource.ContentProvider
|
||||||
resource.Resource
|
resource.Resource
|
||||||
resource.Identifier
|
resource.Identifier
|
||||||
|
resourceCopier
|
||||||
}
|
}
|
||||||
|
|
||||||
type transformationUpdate struct {
|
type transformationUpdate struct {
|
||||||
|
|
100
tpl/resources/integration_test.go
Normal file
100
tpl/resources/integration_test.go
Normal file
|
@ -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)
|
||||||
|
|
||||||
|
}
|
|
@ -111,6 +111,15 @@ func (ns *Namespace) getscssClientDartSass() (*dartsass.Client, error) {
|
||||||
return ns.scssClientDartSass, err
|
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
|
// Get locates the filename given in Hugo's assets filesystem
|
||||||
// and creates a Resource object that can be used for further transformations.
|
// and creates a Resource object that can be used for further transformations.
|
||||||
func (ns *Namespace) Get(filename any) resource.Resource {
|
func (ns *Namespace) Get(filename any) resource.Resource {
|
||||||
|
|
Loading…
Reference in a new issue