Compare commits

..

4 Commits

Author SHA1 Message Date
Bjørn Erik Pedersen 10ddc2ef92 Improve error messages for PostCSS etc.
Fixes #9730
2023-07-17 19:42:13 +02:00
Bjørn Erik Pedersen c406fd3a0e Fix setting config from env with complex (e.g. YAML) strings
So you can do

```
HUGO_OUTPUTS="home: [rss]"  hugo
```

And similar.

Fixes #11249
2023-07-16 18:08:23 +02:00
David Karlsson 286821e360 Fix for data mounts in sub folders
Before this change, data files from Hugo modules were always mounted at the
root of the `data` directory. The File and FileMetaInfo structs for modules
are different from 'native' data directories.

This changes how the keyParts for data files are generated so that data
from modules or native directories are treated the same.
2023-07-15 11:13:08 +02:00
hugoreleaser 79f15be5b0 releaser: Prepare repository for 0.116.0-DEV
[ci skip]
2023-07-13 16:23:50 +00:00
16 changed files with 232 additions and 22 deletions
+1 -1
View File
@@ -348,7 +348,7 @@ description = ""
homepage = "http://example.com/"
tags = []
features = []
min_version = "0.115.3"
min_version = "0.115.0"
[author]
name = ""
+24 -1
View File
@@ -59,11 +59,34 @@ func GetGID() uint64 {
return n
}
// IsFeatureNotAvailableError returns true if the given error is or contains a FeatureNotAvailableError.
func IsFeatureNotAvailableError(err error) bool {
return errors.Is(err, &FeatureNotAvailableError{})
}
// ErrFeatureNotAvailable denotes that a feature is unavailable.
//
// We will, at least to begin with, make some Hugo features (SCSS with libsass) optional,
// and this error is used to signal those situations.
var ErrFeatureNotAvailable = errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information")
var ErrFeatureNotAvailable = &FeatureNotAvailableError{Cause: errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information")}
// FeatureNotAvailableError is an error type used to signal that a feature is not available.
type FeatureNotAvailableError struct {
Cause error
}
func (e *FeatureNotAvailableError) Unwrap() error {
return e.Cause
}
func (e *FeatureNotAvailableError) Error() string {
return e.Cause.Error()
}
func (e *FeatureNotAvailableError) Is(target error) bool {
_, ok := target.(*FeatureNotAvailableError)
return ok
}
// Must panics if err != nil.
func Must(err error) {
+10
View File
@@ -14,6 +14,7 @@
package herrors
import (
"errors"
"fmt"
"testing"
@@ -34,3 +35,12 @@ func TestIsNotExist(t *testing.T) {
// os.IsNotExist returns false for wrapped errors.
c.Assert(IsNotExist(fmt.Errorf("foo: %w", afero.ErrFileNotFound)), qt.Equals, true)
}
func TestIsFeatureNotAvailableError(t *testing.T) {
c := qt.New(t)
c.Assert(IsFeatureNotAvailableError(ErrFeatureNotAvailable), qt.Equals, true)
c.Assert(IsFeatureNotAvailableError(&FeatureNotAvailableError{}), qt.Equals, true)
c.Assert(IsFeatureNotAvailableError(errors.New("asdf")), qt.Equals, false)
}
+3 -3
View File
@@ -17,7 +17,7 @@ package hugo
// This should be the only one.
var CurrentVersion = Version{
Major: 0,
Minor: 115,
PatchLevel: 3,
Suffix: "",
Minor: 116,
PatchLevel: 0,
Suffix: "-DEV",
}
+1 -1
View File
@@ -150,7 +150,7 @@ var allDecoderSetups = map[string]decodeWeight{
key: "outputs",
decode: func(d decodeWeight, p decodeConfig) error {
defaults := createDefaultOutputFormats(p.c.OutputFormats.Config)
m := p.p.GetStringMap("outputs")
m := maps.CleanConfigStringMap(p.p.GetStringMap("outputs"))
p.c.Outputs = make(map[string][]string)
for k, v := range m {
s := types.ToStringSlicePreserveString(v)
+12 -4
View File
@@ -293,11 +293,19 @@ func (l configLoader) applyOsEnvOverrides(environ []string) error {
} else {
l.cfg.Set(env.Key, val)
}
} else if nestedKey != "" {
owner[nestedKey] = env.Value
} else {
// The container does not exist yet.
l.cfg.Set(strings.ReplaceAll(env.Key, delim, "."), env.Value)
if nestedKey != "" {
owner[nestedKey] = env.Value
} else {
var val any = env.Value
if _, ok := allDecoderSetups[env.Key]; ok {
// A map.
val, err = metadecoders.Default.UnmarshalStringTo(env.Value, map[string]interface{}{})
}
if err == nil {
l.cfg.Set(strings.ReplaceAll(env.Key, delim, "."), val)
}
}
}
}
+1 -1
View File
@@ -148,7 +148,7 @@ func (b BuildConfig) UseResourceCache(err error) bool {
}
if b.UseResourceCacheWhen == "fallback" {
return err == herrors.ErrFeatureNotAvailable
return herrors.IsFeatureNotAvailableError(err)
}
return true
+115
View File
@@ -1419,3 +1419,118 @@ Home.
b.Assert(len(b.H.Sites), qt.Equals, 1)
}
func TestLoadConfigYamlEnvVar(t *testing.T) {
defaultEnv := []string{`HUGO_OUTPUTS=home: ['json']`}
runVariant := func(t testing.TB, files string, env []string) *IntegrationTestBuilder {
if env == nil {
env = defaultEnv
}
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
Environ: env,
BuildCfg: BuildCfg{SkipRender: true},
},
).Build()
outputs := b.H.Configs.Base.Outputs
if env == nil {
home := outputs["home"]
b.Assert(home, qt.Not(qt.IsNil))
b.Assert(home, qt.DeepEquals, []string{"json"})
}
return b
}
t.Run("with empty slice", func(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
[outputs]
home = ["html"]
`
b := runVariant(t, files, []string{`HUGO_OUTPUTS=section: []`})
outputs := b.H.Configs.Base.Outputs
b.Assert(outputs, qt.DeepEquals, map[string][]string{
"home": {"html"},
"page": {"html"},
"rss": {"rss"},
"section": nil,
"taxonomy": {"html", "rss"},
"term": {"html", "rss"},
})
})
t.Run("with existing outputs", func(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
[outputs]
home = ["html"]
`
runVariant(t, files, nil)
})
{
t.Run("with existing outputs direct", func(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
[outputs]
home = ["html"]
`
runVariant(t, files, []string{"HUGO_OUTPUTS_HOME=json"})
})
}
t.Run("without existing outputs", func(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
`
runVariant(t, files, nil)
})
t.Run("without existing outputs direct", func(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
`
runVariant(t, files, []string{"HUGO_OUTPUTS_HOME=json"})
})
}
+29
View File
@@ -1178,3 +1178,32 @@ target = "content/resources-b"
b.AssertFileContent("public/resources-a/subdir/about/index.html", "Single")
b.AssertFileContent("public/resources-b/subdir/about/index.html", "Single")
}
func TestMountData(t *testing.T) {
files := `
-- hugo.toml --
baseURL = 'https://example.org/'
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
[[module.mounts]]
source = "data"
target = "data"
[[module.mounts]]
source = "extra-data"
target = "data/extra"
-- extra-data/test.yaml --
message: Hugo Rocks
-- layouts/index.html --
{{ site.Data.extra.test.message }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b.AssertFileContent("public/index.html", "Hugo Rocks")
}
+5 -3
View File
@@ -555,13 +555,14 @@ func (h *HugoSites) loadData(fis []hugofs.FileMetaInfo) (err error) {
h.data = make(map[string]any)
for _, fi := range fis {
basePath := fi.Meta().Path
fileSystem := spec.NewFilesystemFromFileMetaInfo(fi)
files, err := fileSystem.Files()
if err != nil {
return err
}
for _, r := range files {
if err := h.handleDataFile(r); err != nil {
if err := h.handleDataFile(basePath, r); err != nil {
return err
}
}
@@ -570,7 +571,7 @@ func (h *HugoSites) loadData(fis []hugofs.FileMetaInfo) (err error) {
return
}
func (h *HugoSites) handleDataFile(r source.File) error {
func (h *HugoSites) handleDataFile(basePath string, r source.File) error {
var current map[string]any
f, err := r.FileInfo().Meta().Open()
@@ -581,7 +582,8 @@ func (h *HugoSites) handleDataFile(r source.File) error {
// Crawl in data tree to insert data
current = h.data
keyParts := strings.Split(r.Dir(), helpers.FilePathSeparator)
dataPath := filepath.Join(basePath, r.Dir())
keyParts := strings.Split(dataPath, helpers.FilePathSeparator)
for _, key := range keyParts {
if key != "" {
+3 -2
View File
@@ -1,7 +1,8 @@
# Release env.
# These will be replaced by script before release.
HUGORELEASER_TAG=v0.115.2
HUGORELEASER_COMMITISH=8966424e0e521fb1627216a84efd2c5d5917cb0d
HUGORELEASER_TAG=v0.115.3
HUGORELEASER_COMMITISH=5c2e014a5150553a9fa4f9c1eb7dc4db89c0f1ab
+2 -1
View File
@@ -23,6 +23,7 @@ import (
"strings"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/maps"
"github.com/niklasfasching/go-org/org"
xml "github.com/clbanning/mxj/v2"
@@ -90,7 +91,7 @@ func (d Decoder) UnmarshalStringTo(data string, typ any) (any, error) {
switch typ.(type) {
case string:
return data, nil
case map[string]any:
case map[string]any, maps.Params:
format := d.FormatFromContentString(data)
return d.UnmarshalToMap([]byte(data), format)
case []any:
@@ -181,7 +181,7 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx
if err != nil {
if hexec.IsNotFound(err) {
// This may be on a CI server etc. Will fall back to pre-built assets.
return herrors.ErrFeatureNotAvailable
return &herrors.FeatureNotAvailableError{Cause: err}
}
return err
}
@@ -200,7 +200,7 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx
err = cmd.Run()
if err != nil {
if hexec.IsNotFound(err) {
return herrors.ErrFeatureNotAvailable
return &herrors.FeatureNotAvailableError{Cause: err}
}
return fmt.Errorf(errBuf.String()+": %w", err)
}
@@ -168,6 +168,25 @@ func TestTransformPostCSSError(t *testing.T) {
}
func TestTransformPostCSSNotInstalledError(t *testing.T) {
if !htesting.IsCI() {
t.Skip("Skip long running test when running locally")
}
c := qt.New(t)
s, err := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: c,
NeedsOsFS: true,
TxtarString: postCSSIntegrationTestFiles,
}).BuildE()
s.AssertIsFileError(err)
c.Assert(err.Error(), qt.Contains, `binary with name "npx" not found`)
}
// #9895
func TestTransformPostCSSImportError(t *testing.T) {
if !htesting.IsCI() {
@@ -205,7 +205,7 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC
if err != nil {
if hexec.IsNotFound(err) {
// This may be on a CI server etc. Will fall back to pre-built assets.
return herrors.ErrFeatureNotAvailable
return &herrors.FeatureNotAvailableError{Cause: err}
}
return err
}
@@ -240,7 +240,9 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC
err = cmd.Run()
if err != nil {
if hexec.IsNotFound(err) {
return herrors.ErrFeatureNotAvailable
return &herrors.FeatureNotAvailableError{
Cause: err,
}
}
return imp.toFileError(errBuf.String())
}
+1 -1
View File
@@ -449,7 +449,7 @@ func (r *resourceAdapter) transform(publish, setContent bool) error {
newErr := func(err error) error {
msg := fmt.Sprintf("%s: failed to transform %q (%s)", strings.ToUpper(tr.Key().Name), tctx.InPath, tctx.InMediaType.Type)
if err == herrors.ErrFeatureNotAvailable {
if herrors.IsFeatureNotAvailableError(err) {
var errMsg string
if tr.Key().Name == "postcss" {
// This transformation is not available in this