mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-25 15:58:53 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10ddc2ef92 | |||
| c406fd3a0e | |||
| 286821e360 | |||
| 79f15be5b0 |
+1
-1
@@ -348,7 +348,7 @@ description = ""
|
||||
homepage = "http://example.com/"
|
||||
tags = []
|
||||
features = []
|
||||
min_version = "0.115.3"
|
||||
min_version = "0.115.0"
|
||||
|
||||
[author]
|
||||
name = ""
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user