mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-25 15:58:53 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10ddc2ef92 | |||
| c406fd3a0e | |||
| 286821e360 | |||
| 79f15be5b0 | |||
| 5c2e014a51 | |||
| cc44583cc3 | |||
| 4da672af88 | |||
| f1886f8c37 | |||
| 5bec50838c | |||
| f650e4d751 | |||
| c934a45069 | |||
| 91b02091a5 |
+1
-1
@@ -348,7 +348,7 @@ description = ""
|
||||
homepage = "http://example.com/"
|
||||
tags = []
|
||||
features = []
|
||||
min_version = "0.115.2"
|
||||
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: 2,
|
||||
Suffix: "",
|
||||
Minor: 116,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
@@ -553,7 +553,7 @@ type RootConfig struct {
|
||||
// See Modules for more a more flexible way to load themes.
|
||||
Theme []string
|
||||
|
||||
// Timeout for generating page contents, specified as a duration or in milliseconds.
|
||||
// Timeout for generating page contents, specified as a duration or in seconds.
|
||||
Timeout string
|
||||
|
||||
// The time zone (or location), e.g. Europe/Oslo, used to parse front matter dates without such information and in the time function.
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -1792,7 +1792,7 @@
|
||||
"^postcss$"
|
||||
],
|
||||
"osEnv": [
|
||||
"(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+)$"
|
||||
"(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG)$"
|
||||
]
|
||||
},
|
||||
"funcs": {
|
||||
|
||||
@@ -1321,6 +1321,9 @@ Home.
|
||||
|
||||
b.AssertFileContent("public/en/index.html", "Home.")
|
||||
b.AssertFileContent("public/en/foo/bar.txt", "Foo.")
|
||||
b.AssertFileContent("public/index.html", "refresh")
|
||||
b.AssertFileContent("public/sitemap.xml", "sitemapindex")
|
||||
b.AssertFileContent("public/en/sitemap.xml", "urlset")
|
||||
})
|
||||
|
||||
t.Run("Two languages, default in sub dir", func(t *testing.T) {
|
||||
@@ -1351,6 +1354,9 @@ Home.
|
||||
|
||||
b.AssertFileContent("public/en/index.html", "Home.")
|
||||
b.AssertFileContent("public/en/foo/bar.txt", "Foo.")
|
||||
b.AssertFileContent("public/index.html", "refresh")
|
||||
b.AssertFileContent("public/sitemap.xml", "sitemapindex")
|
||||
b.AssertFileContent("public/en/sitemap.xml", "urlset")
|
||||
})
|
||||
|
||||
t.Run("Two languages, default in root", func(t *testing.T) {
|
||||
@@ -1381,6 +1387,8 @@ Home.
|
||||
|
||||
b.AssertFileContent("public/index.html", "Home.")
|
||||
b.AssertFileContent("public/foo/bar.txt", "Foo.")
|
||||
b.AssertFileContent("public/sitemap.xml", "sitemapindex")
|
||||
b.AssertFileContent("public/en/sitemap.xml", "urlset")
|
||||
})
|
||||
|
||||
}
|
||||
@@ -1411,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")
|
||||
}
|
||||
|
||||
@@ -437,7 +437,7 @@ func (cfg *BuildCfg) shouldRender(p *pageState) bool {
|
||||
}
|
||||
|
||||
func (h *HugoSites) renderCrossSitesSitemap() error {
|
||||
if !h.isMultiLingual() || h.Conf.IsMultihost() {
|
||||
if h.Conf.IsMultihost() || !(h.Conf.DefaultContentLanguageInSubdir() || h.Conf.IsMultiLingual()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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 != "" {
|
||||
|
||||
@@ -557,6 +557,9 @@ func (p *pageContentOutput) RenderWithTemplateInfo(ctx context.Context, info tpl
|
||||
}
|
||||
|
||||
func (p *pageContentOutput) Render(ctx context.Context, layout ...string) (template.HTML, error) {
|
||||
if len(layout) == 0 {
|
||||
return "", errors.New("no layout given")
|
||||
}
|
||||
templ, found, err := p.p.resolveTemplate(layout...)
|
||||
if err != nil {
|
||||
return "", p.p.wrapError(err)
|
||||
|
||||
@@ -1983,3 +1983,25 @@ title: "p2"
|
||||
b.Assert(identity.HashString(p1), qt.Not(qt.Equals), identity.HashString(p2))
|
||||
b.Assert(identity.HashString(sites[0]), qt.Not(qt.Equals), identity.HashString(sites[1]))
|
||||
}
|
||||
|
||||
// Issue #11243
|
||||
func TestRenderWithoutArgument(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
-- layouts/index.html --
|
||||
{{ .Render }}
|
||||
`
|
||||
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
Running: true,
|
||||
},
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ func (s *Site) renderAliases() error {
|
||||
// renderMainLanguageRedirect creates a redirect to the main language home,
|
||||
// depending on if it lives in sub folder (e.g. /en) or not.
|
||||
func (s *Site) renderMainLanguageRedirect() error {
|
||||
if !s.h.isMultiLingual() || s.h.Conf.IsMultihost() {
|
||||
if s.h.Conf.IsMultihost() || !(s.h.Conf.DefaultContentLanguageInSubdir() || s.h.Conf.IsMultiLingual()) {
|
||||
// No need for a redirect
|
||||
return nil
|
||||
}
|
||||
|
||||
+4
-2
@@ -1,7 +1,9 @@
|
||||
# Release env.
|
||||
# These will be replaced by script before release.
|
||||
HUGORELEASER_TAG=v0.115.1
|
||||
HUGORELEASER_COMMITISH=857374e69358f788bd31ddc55255c5c8e3dcfd80
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ func (t *buildTransformation) Transform(ctx *resources.ResourceTransformationCtx
|
||||
opts.resolveDir = t.c.rs.Cfg.BaseConfig().WorkingDir // where node_modules gets resolved
|
||||
opts.contents = string(src)
|
||||
opts.mediaType = ctx.InMediaType
|
||||
opts.tsConfig = t.c.rs.ResolveJSConfigFile("tsconfig.json")
|
||||
|
||||
buildOptions, err := toBuildOptions(opts)
|
||||
if err != nil {
|
||||
|
||||
@@ -48,7 +48,7 @@ export function hello3() {
|
||||
-- layouts/index.html --
|
||||
{{ $js := resources.Get "js/main.js" | js.Build }}
|
||||
JS Content:{{ $js.Content }}:End:
|
||||
|
||||
|
||||
`
|
||||
|
||||
c.Run("Basic", func(c *qt.C) {
|
||||
@@ -90,9 +90,9 @@ disableKinds=["page", "section", "taxonomy", "term", "sitemap", "robotsTXT"]
|
||||
path="github.com/gohugoio/hugoTestProjectJSModImports"
|
||||
-- go.mod --
|
||||
module github.com/gohugoio/tests/testHugoModules
|
||||
|
||||
|
||||
go 1.16
|
||||
|
||||
|
||||
require github.com/gohugoio/hugoTestProjectJSModImports v0.10.0 // indirect
|
||||
-- package.json --
|
||||
{
|
||||
@@ -100,7 +100,7 @@ require github.com/gohugoio/hugoTestProjectJSModImports v0.10.0 // indirect
|
||||
"date-fns": "^2.16.1"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
`
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
@@ -142,7 +142,7 @@ console.log("included");
|
||||
-- assets/js/main.js --
|
||||
import "./included";
|
||||
import { toCamelCase } from "to-camel-case";
|
||||
|
||||
|
||||
console.log("main");
|
||||
console.log("To camel:", toCamelCase("space case"));
|
||||
-- assets/js/myjsx.jsx --
|
||||
@@ -222,7 +222,7 @@ import { hello1, hello2 } from './util1';
|
||||
hello1();
|
||||
hello2();
|
||||
-- assets/js/util1.js --
|
||||
/* Some
|
||||
/* Some
|
||||
comments.
|
||||
*/
|
||||
import { hello3 } from './util2';
|
||||
@@ -239,7 +239,7 @@ export function hello3() {
|
||||
-- layouts/index.html --
|
||||
{{ $js := resources.Get "js/main.js" | js.Build }}
|
||||
JS Content:{{ $js.Content }}:End:
|
||||
|
||||
|
||||
`
|
||||
|
||||
c.Run("Import from main not found", func(c *qt.C) {
|
||||
@@ -292,9 +292,9 @@ import 'imp3/foo.js';
|
||||
}).Build()
|
||||
|
||||
expected := `
|
||||
IMPORT_SRC_DIR:imp1/index.js
|
||||
IMPORT_SRC_DIR:imp1/index.js
|
||||
IMPORT_SRC_DIR:imp2/index.ts
|
||||
IMPORT_SRC_DIR:imp3/foo.ts
|
||||
IMPORT_SRC_DIR:imp3/foo.ts
|
||||
`
|
||||
expected = strings.ReplaceAll(expected, "IMPORT_SRC_DIR", importSrcDir)
|
||||
|
||||
@@ -340,7 +340,36 @@ console.log("Hello 2");
|
||||
License util1
|
||||
License util2
|
||||
Main license
|
||||
|
||||
|
||||
`)
|
||||
|
||||
}
|
||||
|
||||
// Issue #11232
|
||||
func TestTypeScriptExperimentalDecorators(t *testing.T) {
|
||||
t.Parallel()
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['RSS','sitemap','taxonomy','term']
|
||||
-- tsconfig.json --
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
}
|
||||
}
|
||||
-- assets/ts/main.ts --
|
||||
function addFoo(target: any) {target.prototype.foo = 'bar'}
|
||||
@addFoo
|
||||
class A {}
|
||||
-- layouts/index.html --
|
||||
{{ $opts := dict "target" "es2020" "targetPath" "js/main.js" }}
|
||||
{{ (resources.Get "ts/main.ts" | js.Build $opts).Publish }}
|
||||
`
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
NeedsOsFS: true,
|
||||
TxtarString: files,
|
||||
}).Build()
|
||||
b.AssertFileContent("public/js/main.js", "__decorateClass")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math/rand"
|
||||
@@ -99,7 +100,7 @@ func (ns *Namespace) After(n any, l any) (any, error) {
|
||||
|
||||
// Delimit takes a given list l and returns a string delimited by sep.
|
||||
// If last is passed to the function, it will be used as the final delimiter.
|
||||
func (ns *Namespace) Delimit(l, sep any, last ...any) (template.HTML, error) {
|
||||
func (ns *Namespace) Delimit(ctx context.Context, l, sep any, last ...any) (template.HTML, error) {
|
||||
d, err := cast.ToStringE(sep)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -125,7 +126,7 @@ func (ns *Namespace) Delimit(l, sep any, last ...any) (template.HTML, error) {
|
||||
var str string
|
||||
switch lv.Kind() {
|
||||
case reflect.Map:
|
||||
sortSeq, err := ns.Sort(l)
|
||||
sortSeq, err := ns.Sort(ctx, l)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
@@ -166,9 +167,9 @@ func TestDelimit(t *testing.T) {
|
||||
var err error
|
||||
|
||||
if test.last == nil {
|
||||
result, err = ns.Delimit(test.seq, test.delimiter)
|
||||
result, err = ns.Delimit(context.Background(), test.seq, test.delimiter)
|
||||
} else {
|
||||
result, err = ns.Delimit(test.seq, test.delimiter, test.last)
|
||||
result, err = ns.Delimit(context.Background(), test.seq, test.delimiter, test.last)
|
||||
}
|
||||
|
||||
c.Assert(err, qt.IsNil, errMsg)
|
||||
|
||||
@@ -155,3 +155,44 @@ func TestAppendNilsToSliceWithNils(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Issue 11234.
|
||||
func TestWhereWithWordCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
baseURL = 'http://example.com/'
|
||||
-- layouts/index.html --
|
||||
Home: {{ range where site.RegularPages "WordCount" "gt" 50 }}{{ .Title }}|{{ end }}
|
||||
-- layouts/shortcodes/lorem.html --
|
||||
{{ "ipsum " | strings.Repeat (.Get 0 | int) }}
|
||||
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
{{< lorem 100 >}}
|
||||
-- content/p2.md --
|
||||
---
|
||||
title: "p2"
|
||||
---
|
||||
{{< lorem 20 >}}
|
||||
-- content/p3.md --
|
||||
---
|
||||
title: "p3"
|
||||
---
|
||||
{{< lorem 60 >}}
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", `
|
||||
Home: p1|p3|
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"sort"
|
||||
@@ -26,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
// Sort returns a sorted copy of the list l.
|
||||
func (ns *Namespace) Sort(l any, args ...any) (any, error) {
|
||||
func (ns *Namespace) Sort(ctx context.Context, l any, args ...any) (any, error) {
|
||||
if l == nil {
|
||||
return nil, errors.New("sequence must be provided")
|
||||
}
|
||||
@@ -36,6 +37,8 @@ func (ns *Namespace) Sort(l any, args ...any) (any, error) {
|
||||
return nil, errors.New("can't iterate over a nil value")
|
||||
}
|
||||
|
||||
ctxv := reflect.ValueOf(ctx)
|
||||
|
||||
var sliceType reflect.Type
|
||||
switch seqv.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
@@ -78,7 +81,7 @@ func (ns *Namespace) Sort(l any, args ...any) (any, error) {
|
||||
v := p.Pairs[i].Value
|
||||
var err error
|
||||
for i, elemName := range path {
|
||||
v, err = evaluateSubElem(v, elemName)
|
||||
v, err = evaluateSubElem(ctxv, v, elemName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -108,7 +111,7 @@ func (ns *Namespace) Sort(l any, args ...any) (any, error) {
|
||||
v := p.Pairs[i].Value
|
||||
var err error
|
||||
for i, elemName := range path {
|
||||
v, err = evaluateSubElem(v, elemName)
|
||||
v, err = evaluateSubElem(ctxv, v, elemName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -240,9 +241,9 @@ func TestSort(t *testing.T) {
|
||||
var result any
|
||||
var err error
|
||||
if test.sortByField == nil {
|
||||
result, err = ns.Sort(test.seq)
|
||||
result, err = ns.Sort(context.Background(), test.seq)
|
||||
} else {
|
||||
result, err = ns.Sort(test.seq, test.sortByField, test.sortAsc)
|
||||
result, err = ns.Sort(context.Background(), test.seq, test.sortByField, test.sortAsc)
|
||||
}
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
|
||||
+23
-12
@@ -14,6 +14,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -24,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
// Where returns a filtered subset of collection c.
|
||||
func (ns *Namespace) Where(c, key any, args ...any) (any, error) {
|
||||
func (ns *Namespace) Where(ctx context.Context, c, key any, args ...any) (any, error) {
|
||||
seqv, isNil := indirect(reflect.ValueOf(c))
|
||||
if isNil {
|
||||
return nil, errors.New("can't iterate over a nil value of type " + reflect.ValueOf(c).Type().String())
|
||||
@@ -35,6 +36,8 @@ func (ns *Namespace) Where(c, key any, args ...any) (any, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctxv := reflect.ValueOf(ctx)
|
||||
|
||||
var path []string
|
||||
kv := reflect.ValueOf(key)
|
||||
if kv.Kind() == reflect.String {
|
||||
@@ -43,9 +46,9 @@ func (ns *Namespace) Where(c, key any, args ...any) (any, error) {
|
||||
|
||||
switch seqv.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
return ns.checkWhereArray(seqv, kv, mv, path, op)
|
||||
return ns.checkWhereArray(ctxv, seqv, kv, mv, path, op)
|
||||
case reflect.Map:
|
||||
return ns.checkWhereMap(seqv, kv, mv, path, op)
|
||||
return ns.checkWhereMap(ctxv, seqv, kv, mv, path, op)
|
||||
default:
|
||||
return nil, fmt.Errorf("can't iterate over %v", c)
|
||||
}
|
||||
@@ -275,7 +278,7 @@ func (ns *Namespace) checkCondition(v, mv reflect.Value, op string) (bool, error
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error) {
|
||||
func evaluateSubElem(ctx, obj reflect.Value, elemName string) (reflect.Value, error) {
|
||||
if !obj.IsValid() {
|
||||
return zero, errors.New("can't evaluate an invalid value")
|
||||
}
|
||||
@@ -301,12 +304,20 @@ func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error)
|
||||
|
||||
index := hreflect.GetMethodIndexByName(objPtr.Type(), elemName)
|
||||
if index != -1 {
|
||||
var args []reflect.Value
|
||||
mt := objPtr.Type().Method(index)
|
||||
num := mt.Type.NumIn()
|
||||
maxNumIn := 1
|
||||
if num > 1 && mt.Type.In(1).Implements(hreflect.ContextInterface) {
|
||||
args = []reflect.Value{ctx}
|
||||
maxNumIn = 2
|
||||
}
|
||||
|
||||
switch {
|
||||
case mt.PkgPath != "":
|
||||
return zero, fmt.Errorf("%s is an unexported method of type %s", elemName, typ)
|
||||
case mt.Type.NumIn() > 1:
|
||||
return zero, fmt.Errorf("%s is a method of type %s but requires more than 1 parameter", elemName, typ)
|
||||
case mt.Type.NumIn() > maxNumIn:
|
||||
return zero, fmt.Errorf("%s is a method of type %s but requires more than %d parameter", elemName, typ, maxNumIn)
|
||||
case mt.Type.NumOut() == 0:
|
||||
return zero, fmt.Errorf("%s is a method of type %s but returns no output", elemName, typ)
|
||||
case mt.Type.NumOut() > 2:
|
||||
@@ -316,7 +327,7 @@ func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error)
|
||||
case mt.Type.NumOut() == 2 && !mt.Type.Out(1).Implements(errorType):
|
||||
return zero, fmt.Errorf("%s is a method of type %s returning two values but the second value is not an error type", elemName, typ)
|
||||
}
|
||||
res := objPtr.Method(mt.Index).Call([]reflect.Value{})
|
||||
res := objPtr.Method(mt.Index).Call(args)
|
||||
if len(res) == 2 && !res[1].IsNil() {
|
||||
return zero, fmt.Errorf("error at calling a method %s of type %s: %s", elemName, typ, res[1].Interface().(error))
|
||||
}
|
||||
@@ -371,7 +382,7 @@ func parseWhereArgs(args ...any) (mv reflect.Value, op string, err error) {
|
||||
|
||||
// checkWhereArray handles the where-matching logic when the seqv value is an
|
||||
// Array or Slice.
|
||||
func (ns *Namespace) checkWhereArray(seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
|
||||
func (ns *Namespace) checkWhereArray(ctxv, seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
|
||||
rv := reflect.MakeSlice(seqv.Type(), 0, 0)
|
||||
|
||||
for i := 0; i < seqv.Len(); i++ {
|
||||
@@ -385,7 +396,7 @@ func (ns *Namespace) checkWhereArray(seqv, kv, mv reflect.Value, path []string,
|
||||
vvv = rvv
|
||||
for i, elemName := range path {
|
||||
var err error
|
||||
vvv, err = evaluateSubElem(vvv, elemName)
|
||||
vvv, err = evaluateSubElem(ctxv, vvv, elemName)
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -417,14 +428,14 @@ func (ns *Namespace) checkWhereArray(seqv, kv, mv reflect.Value, path []string,
|
||||
}
|
||||
|
||||
// checkWhereMap handles the where-matching logic when the seqv value is a Map.
|
||||
func (ns *Namespace) checkWhereMap(seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
|
||||
func (ns *Namespace) checkWhereMap(ctxv, seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
|
||||
rv := reflect.MakeMap(seqv.Type())
|
||||
keys := seqv.MapKeys()
|
||||
for _, k := range keys {
|
||||
elemv := seqv.MapIndex(k)
|
||||
switch elemv.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
r, err := ns.checkWhereArray(elemv, kv, mv, path, op)
|
||||
r, err := ns.checkWhereArray(ctxv, elemv, kv, mv, path, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -443,7 +454,7 @@ func (ns *Namespace) checkWhereMap(seqv, kv, mv reflect.Value, path []string, op
|
||||
|
||||
switch elemvv.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
r, err := ns.checkWhereArray(elemvv, kv, mv, path, op)
|
||||
r, err := ns.checkWhereArray(ctxv, elemvv, kv, mv, path, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"reflect"
|
||||
@@ -641,9 +642,9 @@ func TestWhere(t *testing.T) {
|
||||
var err error
|
||||
|
||||
if len(test.op) > 0 {
|
||||
results, err = ns.Where(test.seq, test.key, test.op, test.match)
|
||||
results, err = ns.Where(context.Background(), test.seq, test.key, test.op, test.match)
|
||||
} else {
|
||||
results, err = ns.Where(test.seq, test.key, test.match)
|
||||
results, err = ns.Where(context.Background(), test.seq, test.key, test.match)
|
||||
}
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
if err == nil {
|
||||
@@ -662,17 +663,17 @@ func TestWhere(t *testing.T) {
|
||||
}
|
||||
|
||||
var err error
|
||||
_, err = ns.Where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1)
|
||||
_, err = ns.Where(context.Background(), map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1)
|
||||
if err == nil {
|
||||
t.Errorf("Where called with none string op value didn't return an expected error")
|
||||
}
|
||||
|
||||
_, err = ns.Where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1, 2)
|
||||
_, err = ns.Where(context.Background(), map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1, 2)
|
||||
if err == nil {
|
||||
t.Errorf("Where called with more than two variable arguments didn't return an expected error")
|
||||
}
|
||||
|
||||
_, err = ns.Where(map[string]int{"a": 1, "b": 2}, "a")
|
||||
_, err = ns.Where(context.Background(), map[string]int{"a": 1, "b": 2}, "a")
|
||||
if err == nil {
|
||||
t.Errorf("Where called with no variable arguments didn't return an expected error")
|
||||
}
|
||||
@@ -842,7 +843,7 @@ func TestEvaluateSubElem(t *testing.T) {
|
||||
{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), "1", false},
|
||||
{reflect.ValueOf([]string{"foo", "bar"}), "1", false},
|
||||
} {
|
||||
result, err := evaluateSubElem(test.value, test.key)
|
||||
result, err := evaluateSubElem(reflect.ValueOf(context.Background()), test.value, test.key)
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
if err == nil {
|
||||
t.Errorf("[%d] evaluateSubElem didn't return an expected error", i)
|
||||
|
||||
Reference in New Issue
Block a user