diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2023-01-04 18:24:36 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2023-05-16 18:01:29 +0200 |
commit | 241b21b0fd34d91fccb2ce69874110dceae6f926 (patch) | |
tree | d4e0118eac7e9c42f065815447a70805f8d6ad3e /resources | |
parent | 6aededf6b42011c3039f5f66487a89a8dd65e0e7 (diff) |
Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code.
Also,
* Lower case the default output format names; this is in line with the custom ones (map keys) and how
it's treated all the places. This avoids doing `stringds.EqualFold` everywhere.
Closes #10896
Closes #10620
Diffstat (limited to 'resources')
48 files changed, 1096 insertions, 1229 deletions
diff --git a/resources/assets/sunset.jpg b/resources/assets/sunset.jpg Binary files differnew file mode 100644 index 000000000..7d7307bed --- /dev/null +++ b/resources/assets/sunset.jpg diff --git a/resources/image.go b/resources/image.go index 6deb0dfe7..c61e903ab 100644 --- a/resources/image.go +++ b/resources/image.go @@ -323,7 +323,7 @@ func (i *imageResource) doWithImageConfig(conf images.ImageConfig, f func(src im if shouldFill { bgColor = conf.BgColor if bgColor == nil { - bgColor = i.Proc.Cfg.BgColor + bgColor = i.Proc.Cfg.Config.BgColor } tmp := image.NewRGBA(converted.Bounds()) draw.Draw(tmp, tmp.Bounds(), image.NewUniform(bgColor), image.Point{}, draw.Src) @@ -380,7 +380,7 @@ func (g *giphy) GIF() *gif.GIF { } // DecodeImage decodes the image source into an Image. -// This an internal method and may change. +// This for internal use only. func (i *imageResource) DecodeImage() (image.Image, error) { f, err := i.ReadSeekCloser() if err != nil { @@ -423,7 +423,7 @@ func (i *imageResource) setBasePath(conf images.ImageConfig) { func (i *imageResource) getImageMetaCacheTargetPath() string { const imageMetaVersionNumber = 1 // Increment to invalidate the meta cache - cfgHash := i.getSpec().imaging.Cfg.CfgHash + cfgHash := i.getSpec().imaging.Cfg.SourceHash df := i.getResourcePaths().relTargetDirFile if fi := i.getFileInfo(); fi != nil { df.dir = filepath.Dir(fi.Meta().Path) diff --git a/resources/image_extended_test.go b/resources/image_extended_test.go index a0b274f3e..4da603fc4 100644 --- a/resources/image_extended_test.go +++ b/resources/image_extended_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Hugo Authors. All rights reserved. +// Copyright 2023 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. @@ -14,29 +14,28 @@ //go:build extended // +build extended -package resources +package resources_test import ( "testing" - "github.com/gohugoio/hugo/media" - qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/media" ) func TestImageResizeWebP(t *testing.T) { c := qt.New(t) - image := fetchImage(c, "sunset.webp") + _, image := fetchImage(c, "sunset.webp") - c.Assert(image.MediaType(), qt.Equals, media.WEBPType) + c.Assert(image.MediaType(), qt.Equals, media.Builtin.WEBPType) c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.webp") c.Assert(image.ResourceType(), qt.Equals, "image") c.Assert(image.Exif(), qt.IsNil) resized, err := image.Resize("123x") c.Assert(err, qt.IsNil) - c.Assert(image.MediaType(), qt.Equals, media.WEBPType) + c.Assert(image.MediaType(), qt.Equals, media.Builtin.WEBPType) c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu36ee0b61ba924719ad36da960c273f96_59826_123x0_resize_q68_h2_linear_2.webp") c.Assert(resized.Width(), qt.Equals, 123) } diff --git a/resources/image_test.go b/resources/image_test.go index d401fa783..ca3efb3b3 100644 --- a/resources/image_test.go +++ b/resources/image_test.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package resources +package resources_test import ( "context" @@ -31,6 +31,7 @@ import ( "testing" "time" + "github.com/gohugoio/hugo/resources" "github.com/gohugoio/hugo/resources/images/webp" "github.com/gohugoio/hugo/common/paths" @@ -51,9 +52,6 @@ import ( ) var eq = qt.CmpEquals( - cmp.Comparer(func(p1, p2 *resourceAdapter) bool { - return p1.resourceAdapterInner == p2.resourceAdapterInner - }), cmp.Comparer(func(p1, p2 os.FileInfo) bool { return p1.Name() == p2.Name() && p1.Size() == p2.Size() && p1.IsDir() == p2.IsDir() }), @@ -65,9 +63,9 @@ var eq = qt.CmpEquals( } return p1.Name() == p2.Name() && p1.Size() == p2.Size() && p1.IsDir() == p2.IsDir() }), - cmp.Comparer(func(p1, p2 *genericResource) bool { return p1 == p2 }), + //cmp.Comparer(func(p1, p2 *genericResource) bool { return p1 == p2 }), cmp.Comparer(func(m1, m2 media.Type) bool { - return m1.Type() == m2.Type() + return m1.Type == m2.Type }), cmp.Comparer( func(v1, v2 *big.Rat) bool { @@ -82,9 +80,8 @@ var eq = qt.CmpEquals( func TestImageTransformBasic(t *testing.T) { c := qt.New(t) - image := fetchSunset(c) - - fileCache := image.(specProvider).getSpec().FileCaches.ImageCache().Fs + spec, image := fetchSunset(c) + fileCache := spec.FileCaches.ImageCache().Fs assertWidthHeight := func(img images.ImageResource, w, h int) { c.Helper() @@ -150,7 +147,7 @@ func TestImageTransformBasic(t *testing.T) { // Check cache filledAgain, err := image.Fill("200x100 bottomLeft") c.Assert(err, qt.IsNil) - c.Assert(filled, eq, filledAgain) + c.Assert(filled, qt.Equals, filledAgain) cropped, err := image.Crop("300x300 topRight") c.Assert(err, qt.IsNil) @@ -165,16 +162,15 @@ func TestImageTransformBasic(t *testing.T) { // Check cache croppedAgain, err := image.Crop("300x300 topRight") c.Assert(err, qt.IsNil) - c.Assert(cropped, eq, croppedAgain) + c.Assert(cropped, qt.Equals, croppedAgain) } func TestImageTransformFormat(t *testing.T) { c := qt.New(t) - image := fetchSunset(c) - - fileCache := image.(specProvider).getSpec().FileCaches.ImageCache().Fs + spec, image := fetchSunset(c) + fileCache := spec.FileCaches.ImageCache().Fs assertExtWidthHeight := func(img images.ImageResource, ext string, w, h int) { c.Helper() @@ -259,7 +255,7 @@ func TestImageBugs(t *testing.T) { // Issue #4261 c.Run("Transform long filename", func(c *qt.C) { - image := fetchImage(c, "1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph.jpg") + _, image := fetchImage(c, "1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph.jpg") c.Assert(image, qt.Not(qt.IsNil)) resized, err := image.Resize("200x") @@ -277,7 +273,7 @@ func TestImageBugs(t *testing.T) { // Issue #6137 c.Run("Transform upper case extension", func(c *qt.C) { - image := fetchImage(c, "sunrise.JPG") + _, image := fetchImage(c, "sunrise.JPG") resized, err := image.Resize("200x") c.Assert(err, qt.IsNil) @@ -288,7 +284,7 @@ func TestImageBugs(t *testing.T) { // Issue #7955 c.Run("Fill with smartcrop", func(c *qt.C) { - sunset := fetchImage(c, "sunset.jpg") + _, sunset := fetchImage(c, "sunset.jpg") for _, test := range []struct { originalDimensions string @@ -363,7 +359,7 @@ func TestImageTransformConcurrent(t *testing.T) { func TestImageWithMetadata(t *testing.T) { c := qt.New(t) - image := fetchSunset(c) + _, image := fetchSunset(c) meta := []map[string]any{ { @@ -373,7 +369,7 @@ func TestImageWithMetadata(t *testing.T) { }, } - c.Assert(AssignMetadata(meta, image), qt.IsNil) + c.Assert(resources.AssignMetadata(meta, image), qt.IsNil) c.Assert(image.Name(), qt.Equals, "Sunset #1") resized, err := image.Resize("200x") @@ -384,16 +380,16 @@ func TestImageWithMetadata(t *testing.T) { func TestImageResize8BitPNG(t *testing.T) { c := qt.New(t) - image := fetchImage(c, "gohugoio.png") + _, image := fetchImage(c, "gohugoio.png") - c.Assert(image.MediaType().Type(), qt.Equals, "image/png") + c.Assert(image.MediaType().Type, qt.Equals, "image/png") c.Assert(image.RelPermalink(), qt.Equals, "/a/gohugoio.png") c.Assert(image.ResourceType(), qt.Equals, "image") c.Assert(image.Exif(), qt.IsNil) resized, err := image.Resize("800x") c.Assert(err, qt.IsNil) - c.Assert(resized.MediaType().Type(), qt.Equals, "image/png") + c.Assert(resized.MediaType().Type, qt.Equals, "image/png") c.Assert(resized.RelPermalink(), qt.Equals, "/a/gohugoio_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_800x0_resize_linear_3.png") c.Assert(resized.Width(), qt.Equals, 800) } @@ -401,35 +397,33 @@ func TestImageResize8BitPNG(t *testing.T) { func TestImageResizeInSubPath(t *testing.T) { c := qt.New(t) - image := fetchImage(c, "sub/gohugoio2.png") + spec, image := fetchImage(c, "sub/gohugoio2.png") - c.Assert(image.MediaType(), eq, media.PNGType) + c.Assert(image.MediaType(), eq, media.Builtin.PNGType) c.Assert(image.RelPermalink(), qt.Equals, "/a/sub/gohugoio2.png") c.Assert(image.ResourceType(), qt.Equals, "image") c.Assert(image.Exif(), qt.IsNil) resized, err := image.Resize("101x101") c.Assert(err, qt.IsNil) - c.Assert(resized.MediaType().Type(), qt.Equals, "image/png") + c.Assert(resized.MediaType().Type, qt.Equals, "image/png") c.Assert(resized.RelPermalink(), qt.Equals, "/a/sub/gohugoio2_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_101x101_resize_linear_3.png") c.Assert(resized.Width(), qt.Equals, 101) c.Assert(resized.Exif(), qt.IsNil) publishedImageFilename := filepath.Clean(resized.RelPermalink()) - spec := image.(specProvider).getSpec() - assertImageFile(c, spec.BaseFs.PublishFs, publishedImageFilename, 101, 101) c.Assert(spec.BaseFs.PublishFs.Remove(publishedImageFilename), qt.IsNil) // Clear mem cache to simulate reading from the file cache. - spec.imageCache.clear() + spec.ClearCaches() resizedAgain, err := image.Resize("101x101") c.Assert(err, qt.IsNil) c.Assert(resizedAgain.RelPermalink(), qt.Equals, "/a/sub/gohugoio2_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_101x101_resize_linear_3.png") c.Assert(resizedAgain.Width(), qt.Equals, 101) - assertImageFile(c, image.(specProvider).getSpec().BaseFs.PublishFs, publishedImageFilename, 101, 101) + assertImageFile(c, spec.BaseFs.PublishFs, publishedImageFilename, 101, 101) } func TestSVGImage(t *testing.T) { @@ -840,7 +834,7 @@ func assetGoldenDirs(c *qt.C, dir1, dir2 string) { func BenchmarkResizeParallel(b *testing.B) { c := qt.New(b) - img := fetchSunset(c) + _, img := fetchSunset(c) b.RunParallel(func(pb *testing.PB) { for pb.Next() { diff --git a/resources/images/config.go b/resources/images/config.go index 09a7016c1..a3ca0c359 100644 --- a/resources/images/config.go +++ b/resources/images/config.go @@ -19,16 +19,16 @@ import ( "strconv" "strings" - "github.com/gohugoio/hugo/identity" + "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/media" + "github.com/mitchellh/mapstructure" "errors" "github.com/bep/gowebp/libwebp/webpoptions" "github.com/disintegration/gift" - - "github.com/mitchellh/mapstructure" ) var ( @@ -47,12 +47,12 @@ var ( } imageFormatsBySubType = map[string]Format{ - media.JPEGType.SubType: JPEG, - media.PNGType.SubType: PNG, - media.TIFFType.SubType: TIFF, - media.BMPType.SubType: BMP, - media.GIFType.SubType: GIF, - media.WEBPType.SubType: WEBP, + media.Builtin.JPEGType.SubType: JPEG, + media.Builtin.PNGType.SubType: PNG, + media.Builtin.TIFFType.SubType: TIFF, + media.Builtin.BMPType.SubType: BMP, + media.Builtin.GIFType.SubType: GIF, + media.Builtin.WEBPType.SubType: WEBP, } // Add or increment if changes to an image format's processing requires @@ -121,66 +121,83 @@ func ImageFormatFromMediaSubType(sub string) (Format, bool) { const ( defaultJPEGQuality = 75 defaultResampleFilter = "box" - defaultBgColor = "ffffff" + defaultBgColor = "#ffffff" defaultHint = "photo" ) -var defaultImaging = Imaging{ - ResampleFilter: defaultResampleFilter, - BgColor: defaultBgColor, - Hint: defaultHint, - Quality: defaultJPEGQuality, -} - -func DecodeConfig(m map[string]any) (ImagingConfig, error) { - if m == nil { - m = make(map[string]any) +var ( + defaultImaging = map[string]any{ + "resampleFilter": defaultResampleFilter, + "bgColor": defaultBgColor, + "hint": defaultHint, + "quality": defaultJPEGQuality, } - i := ImagingConfig{ - Cfg: defaultImaging, - CfgHash: identity.HashString(m), - } + defaultImageConfig *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal] +) - if err := mapstructure.WeakDecode(m, &i.Cfg); err != nil { - return i, err +func init() { + var err error + defaultImageConfig, err = DecodeConfig(defaultImaging) + if err != nil { + panic(err) } +} - if err := i.Cfg.init(); err != nil { - return i, err +func DecodeConfig(in map[string]any) (*config.ConfigNamespace[ImagingConfig, ImagingConfigInternal], error) { + if in == nil { + in = make(map[string]any) } - var err error - i.BgColor, err = hexStringToColor(i.Cfg.BgColor) - if err != nil { - return i, err - } + buildConfig := func(in any) (ImagingConfigInternal, any, error) { + m, err := maps.ToStringMapE(in) + if err != nil { + return ImagingConfigInternal{}, nil, err + } + // Merge in the defaults. + maps.MergeShallow(m, defaultImaging) + + var i ImagingConfigInternal + if err := mapstructure.Decode(m, &i.Imaging); err != nil { + return i, nil, err + } + + if err := i.Imaging.init(); err != nil { + return i, nil, err + } + + i.BgColor, err = hexStringToColor(i.Imaging.BgColor) + if err != nil { + return i, nil, err + } - if i.Cfg.Anchor != "" && i.Cfg.Anchor != smartCropIdentifier { - anchor, found := anchorPositions[i.Cfg.Anchor] + if i.Imaging.Anchor != "" && i.Imaging.Anchor != smartCropIdentifier { + anchor, found := anchorPositions[i.Imaging.Anchor] + if !found { + return i, nil, fmt.Errorf("invalid anchor value %q in imaging config", i.Anchor) + } + i.Anchor = anchor + } + + filter, found := imageFilters[i.Imaging.ResampleFilter] if !found { - return i, fmt.Errorf("invalid anchor value %q in imaging config", i.Anchor) + return i, nil, fmt.Errorf("%q is not a valid resample filter", filter) } - i.Anchor = anchor - } else { - i.Cfg.Anchor = smartCropIdentifier - } - filter, found := imageFilters[i.Cfg.ResampleFilter] - if !found { - return i, fmt.Errorf("%q is not a valid resample filter", filter) + i.ResampleFilter = filter + + return i, nil, nil } - i.ResampleFilter = filter - if strings.TrimSpace(i.Cfg.Exif.IncludeFields) == "" && strings.TrimSpace(i.Cfg.Exif.ExcludeFields) == "" { - // Don't change this for no good reason. Please don't. - i.Cfg.Exif.ExcludeFields = "GPS|Exif|Exposure[M|P|B]|Contrast|Resolution|Sharp|JPEG|Metering|Sensing|Saturation|ColorSpace|Flash|WhiteBalance" + ns, err := config.DecodeNamespace[ImagingConfig](in, buildConfig) + if err != nil { + return nil, fmt.Errorf("failed to decode media types: %w", err) } + return ns, nil - return i, nil } -func DecodeImageConfig(action, config string, defaults ImagingConfig, sourceFormat Format) (ImageConfig, error) { +func DecodeImageConfig(action, config string, defaults *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal], sourceFormat Format) (ImageConfig, error) { var ( c ImageConfig = GetDefaultImageConfig(action, defaults) err error @@ -268,8 +285,8 @@ func DecodeImageConfig(action, config string, defaults ImagingConfig, sourceForm } if c.FilterStr == "" { - c.FilterStr = defaults.Cfg.ResampleFilter - c.Filter = defaults.ResampleFilter + c.FilterStr = defaults.Config.Imaging.ResampleFilter + c.Filter = defaults.Config.ResampleFilter } if c.Hint == 0 { @@ -277,8 +294,8 @@ func DecodeImageConfig(action, config string, defaults ImagingConfig, sourceForm } if c.AnchorStr == "" { - c.AnchorStr = defaults.Cfg.Anchor - c.Anchor = defaults.Anchor + c.AnchorStr = defaults.Config.Imaging.Anchor + c.Anchor = defaults.Config.Anchor } // default to the source format @@ -288,13 +305,13 @@ func DecodeImageConfig(action, config string, defaults ImagingConfig, sourceForm if c.Quality <= 0 && c.TargetFormat.RequiresDefaultQuality() { // We need a quality setting for all JPEGs and WEBPs. - c.Quality = defaults.Cfg.Quality + c.Quality = defaults.Config.Imaging.Quality } if c.BgColor == nil && c.TargetFormat != sourceFormat { if sourceFormat.SupportsTransparency() && !c.TargetFormat.SupportsTransparency() { - c.BgColor = defaults.BgColor - c.BgColorStr = defaults.Cfg.BgColor + c.BgColor = |