summaryrefslogtreecommitdiffstats
path: root/resources
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-01-04 18:24:36 +0100
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2023-05-16 18:01:29 +0200
commit241b21b0fd34d91fccb2ce69874110dceae6f926 (patch)
treed4e0118eac7e9c42f065815447a70805f8d6ad3e /resources
parent6aededf6b42011c3039f5f66487a89a8dd65e0e7 (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')
-rw-r--r--resources/assets/sunset.jpgbin0 -> 90587 bytes
-rw-r--r--resources/image.go6
-rw-r--r--resources/image_extended_test.go13
-rw-r--r--resources/image_test.go54
-rw-r--r--resources/images/config.go175
-rw-r--r--resources/images/config_test.go26
-rw-r--r--resources/images/image.go28
-rw-r--r--resources/images/image_resource.go2
-rw-r--r--resources/page/page.go4
-rw-r--r--resources/page/page_marshaljson.autogen.go2
-rw-r--r--resources/page/page_matcher.go109
-rw-r--r--resources/page/page_matcher_test.go89
-rw-r--r--resources/page/page_nop.go6
-rw-r--r--resources/page/page_paths.go11
-rw-r--r--resources/page/page_paths_test.go141
-rw-r--r--resources/page/pagemeta/page_frontmatter.go69
-rw-r--r--resources/page/pagemeta/page_frontmatter_test.go111
-rw-r--r--resources/page/pagemeta/pagemeta_test.go44
-rw-r--r--resources/page/pages_language_merge.go1
-rw-r--r--resources/page/pagination.go6
-rw-r--r--resources/page/pagination_test.go55
-rw-r--r--resources/page/permalinks.go24
-rw-r--r--resources/page/permalinks_test.go38
-rw-r--r--resources/page/site.go214
-rw-r--r--resources/page/testhelpers_page_test.go38
-rw-r--r--resources/page/testhelpers_test.go178
-rw-r--r--resources/postpub/fields_test.go4
-rw-r--r--resources/resource.go13
-rw-r--r--resources/resource/resources.go1
-rw-r--r--resources/resource_cache.go17
-rw-r--r--resources/resource_factories/bundler/bundler.go6
-rw-r--r--resources/resource_factories/create/create.go1
-rw-r--r--resources/resource_factories/create/remote.go2
-rw-r--r--resources/resource_metadata_test.go221
-rw-r--r--resources/resource_spec.go102
-rw-r--r--resources/resource_test.go236
-rw-r--r--resources/resource_transformers/babel/babel.go2
-rw-r--r--resources/resource_transformers/htesting/testhelpers.go20
-rw-r--r--resources/resource_transformers/js/build.go6
-rw-r--r--resources/resource_transformers/js/options.go10
-rw-r--r--resources/resource_transformers/js/options_test.go13
-rw-r--r--resources/resource_transformers/minifier/minify.go2
-rw-r--r--resources/resource_transformers/postcss/postcss.go11
-rw-r--r--resources/resource_transformers/tocss/dartsass/transform.go4
-rw-r--r--resources/resource_transformers/tocss/scss/tocss.go10
-rw-r--r--resources/testhelpers_test.go98
-rw-r--r--resources/transform.go10
-rw-r--r--resources/transform_test.go92
48 files changed, 1096 insertions, 1229 deletions
diff --git a/resources/assets/sunset.jpg b/resources/assets/sunset.jpg
new file mode 100644
index 000000000..7d7307bed
--- /dev/null
+++ b/resources/assets/sunset.jpg
Binary files differ
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 = defaults.Config.BgColor
+ c.BgColorStr = defaults.Config.Imaging.BgColor
}
}
@@ -389,22 +406,43 @@ func (i ImageConfig) GetKey(format Format) string {
return k
}
-type ImagingConfig struct {
+type ImagingConfigInternal struct {
BgColor color.Color
Hint webpoptions.EncodingPreset
ResampleFilter gift.Resampling
Anchor gift.Anchor
- // Config as provided by the user.
- Cfg Imaging
+ Imaging ImagingConfig
+}
+
+func (i *ImagingConfigInternal) Compile(externalCfg *ImagingConfig) error {
+ var err error
+ i.BgColor, err = hexStringToColor(externalCfg.BgColor)
+ if err != nil {
+ return err
+ }
+
+ if externalCfg.Anchor != "" && externalCfg.Anchor != smartCropIdentifier {
+ anchor, found := anchorPositions[externalCfg.Anchor]
+ if !found {
+ return fmt.Errorf("invalid anchor value %q in imaging config", i.Anchor)
+ }