mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-30 18:22:37 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10ddc2ef92 | |||
| c406fd3a0e | |||
| 286821e360 | |||
| 79f15be5b0 | |||
| 5c2e014a51 | |||
| cc44583cc3 | |||
| 4da672af88 | |||
| f1886f8c37 | |||
| 5bec50838c | |||
| f650e4d751 | |||
| c934a45069 | |||
| 91b02091a5 | |||
| 8966424e0e | |||
| 72510969a6 | |||
| 70c5e485bb | |||
| a78b17d7f1 | |||
| a481942532 | |||
| 0f921ace6f | |||
| 6019953769 | |||
| 92e86702ea | |||
| 6c9ea022a9 | |||
| 12d3469dd1 | |||
| bf7ee8a91a | |||
| d912491f2d |
@@ -176,7 +176,6 @@ github.com/aws/aws-sdk-go-v2/service/sts="v1.19.2"
|
||||
github.com/aws/aws-sdk-go-v2="v1.18.1"
|
||||
github.com/aws/aws-sdk-go="v1.44.284"
|
||||
github.com/aws/smithy-go="v1.13.5"
|
||||
github.com/bep/clock="v0.3.0"
|
||||
github.com/bep/clocks="v0.5.0"
|
||||
github.com/bep/debounce="v1.2.0"
|
||||
github.com/bep/gitmap="v1.1.2"
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
|
||||
"go.uber.org/automaxprocs/maxprocs"
|
||||
|
||||
"github.com/bep/clock"
|
||||
"github.com/bep/clocks"
|
||||
"github.com/bep/lazycache"
|
||||
"github.com/bep/logg"
|
||||
"github.com/bep/overlayfs"
|
||||
@@ -173,7 +173,7 @@ func (r *rootCommand) ConfigFromConfig(key int32, oldConf *commonConfig) (*commo
|
||||
|
||||
if !configs.Base.C.Clock.IsZero() {
|
||||
// TODO(bep) find a better place for this.
|
||||
htime.Clock = clock.Start(configs.Base.C.Clock)
|
||||
htime.Clock = clocks.Start(configs.Base.C.Clock)
|
||||
}
|
||||
|
||||
return &commonConfig{
|
||||
@@ -283,7 +283,7 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
|
||||
|
||||
if !base.C.Clock.IsZero() {
|
||||
// TODO(bep) find a better place for this.
|
||||
htime.Clock = clock.Start(configs.Base.C.Clock)
|
||||
htime.Clock = clocks.Start(configs.Base.C.Clock)
|
||||
}
|
||||
|
||||
if base.PrintPathWarnings {
|
||||
|
||||
@@ -1042,8 +1042,8 @@ func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) error {
|
||||
if c.fastRenderMode {
|
||||
c.withConf(func(conf *commonConfig) {
|
||||
// Make sure we always render the home pages
|
||||
for _, l := range conf.configs.Languages {
|
||||
langPath := h.GetLangSubDir(l.Lang)
|
||||
for _, l := range conf.configs.ConfigLangs() {
|
||||
langPath := l.LanguagePrefix()
|
||||
if langPath != "" {
|
||||
langPath = langPath + "/"
|
||||
}
|
||||
|
||||
+1
-1
@@ -348,7 +348,7 @@ description = ""
|
||||
homepage = "http://example.com/"
|
||||
tags = []
|
||||
features = []
|
||||
min_version = "0.115.1"
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bep/clock"
|
||||
"github.com/bep/clocks"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/gohugoio/locales"
|
||||
@@ -75,7 +75,7 @@ var (
|
||||
"December",
|
||||
}
|
||||
|
||||
Clock = clock.System()
|
||||
Clock = clocks.System()
|
||||
)
|
||||
|
||||
func NewTimeFormatter(ltr locales.Translator) TimeFormatter {
|
||||
|
||||
@@ -17,7 +17,7 @@ package hugo
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 115,
|
||||
PatchLevel: 1,
|
||||
Suffix: "",
|
||||
Minor: 116,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
@@ -274,6 +274,14 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
}
|
||||
disabledLangs[lang] = true
|
||||
}
|
||||
for lang, language := range c.Languages {
|
||||
if language.Disabled {
|
||||
disabledLangs[lang] = true
|
||||
if lang == c.DefaultContentLanguage {
|
||||
return fmt.Errorf("cannot disable default content language %q", lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ignoredErrors := make(map[string]bool)
|
||||
for _, err := range c.IgnoreErrors {
|
||||
@@ -545,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)
|
||||
|
||||
@@ -41,6 +41,16 @@ func (c ConfigLanguage) LanguagesDefaultFirst() langs.Languages {
|
||||
return c.m.LanguagesDefaultFirst
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) LanguagePrefix() string {
|
||||
if c.DefaultContentLanguageInSubdir() && c.DefaultContentLanguage() == c.Language().Lang {
|
||||
return c.Language().Lang
|
||||
}
|
||||
if !c.IsMultiLingual() || c.DefaultContentLanguage() == c.Language().Lang {
|
||||
return ""
|
||||
}
|
||||
return c.Language().Lang
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) BaseURL() urls.BaseURL {
|
||||
return c.config.C.BaseURL
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,6 +27,7 @@ type AllProvider interface {
|
||||
Language() *langs.Language
|
||||
Languages() langs.Languages
|
||||
LanguagesDefaultFirst() langs.Languages
|
||||
LanguagePrefix() string
|
||||
BaseURL() urls.BaseURL
|
||||
BaseURLLiveReload() urls.BaseURL
|
||||
Environment() string
|
||||
|
||||
@@ -42,7 +42,7 @@ var DefaultConfig = Config{
|
||||
),
|
||||
// These have been tested to work with Hugo's external programs
|
||||
// on Windows, Linux and MacOS.
|
||||
OsEnv: MustNewWhitelist(`(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+)$`),
|
||||
OsEnv: MustNewWhitelist(`(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG)$`),
|
||||
},
|
||||
Funcs: Funcs{
|
||||
Getenv: MustNewWhitelist("^HUGO_", "^CI$"),
|
||||
|
||||
@@ -140,7 +140,7 @@ func TestToTOML(t *testing.T) {
|
||||
got := DefaultConfig.ToTOML()
|
||||
|
||||
c.Assert(got, qt.Equals,
|
||||
"[security]\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^npx$', '^postcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.goTemplates]\n AllowActionJSTmpl = false\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
|
||||
"[security]\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^npx$', '^postcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.goTemplates]\n AllowActionJSTmpl = false\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -154,9 +154,6 @@ func TestDecodeConfigDefault(t *testing.T) {
|
||||
c.Assert(pc.Exec.Allow.Accept("a"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.Allow.Accept("npx"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.Allow.Accept("Npx"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("a"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
||||
|
||||
c.Assert(pc.HTTP.URLs.Accept("https://example.org"), qt.IsTrue)
|
||||
c.Assert(pc.HTTP.Methods.Accept("POST"), qt.IsTrue)
|
||||
@@ -167,6 +164,10 @@ func TestDecodeConfigDefault(t *testing.T) {
|
||||
|
||||
c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("GOROOT"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("HOME"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("SSH_AUTH_SOCK"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("a"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("MYSECRET"), qt.IsFalse)
|
||||
|
||||
}
|
||||
|
||||
@@ -623,7 +623,13 @@ status = 404
|
||||
|
||||
Set `titleCaseStyle` to specify the title style used by the [title](/functions/title/) template function and the automatic section titles in Hugo.
|
||||
|
||||
By default, Hugo adheres to the capitalization rules in the [Associated Press (AP) Stylebook]. Set `titleCaseStyle` to `chicago` if you would prefer to follow the [Chicago Manual of Style], or set if to `go` to use Go's convention of capitalizing every word.
|
||||
Can be one of:
|
||||
|
||||
* `ap` (default), the capitalization rules in the [Associated Press (AP) Stylebook]
|
||||
* `chicago`, the [Chicago Manual of Style]
|
||||
* `go`, Go's convention of capitalizing every word.
|
||||
* `firstupper`, capitalize the first letter of the first word.
|
||||
* `none`, no capitalization.
|
||||
|
||||
[Associated Press (AP) Stylebook]: https://www.apstylebook.com/
|
||||
[Chicago Manual of Style]: https://www.chicagomanualofstyle.org/home.html
|
||||
|
||||
+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": {
|
||||
|
||||
@@ -6,7 +6,7 @@ require (
|
||||
github.com/alecthomas/chroma/v2 v2.8.0
|
||||
github.com/armon/go-radix v1.0.0
|
||||
github.com/aws/aws-sdk-go v1.43.5
|
||||
github.com/bep/clock v0.3.0
|
||||
github.com/bep/clocks v0.5.0
|
||||
github.com/bep/debounce v1.2.0
|
||||
github.com/bep/gitmap v1.1.2
|
||||
github.com/bep/goat v0.5.0
|
||||
@@ -104,7 +104,6 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 // indirect
|
||||
github.com/aws/smithy-go v1.13.5 // indirect
|
||||
github.com/bep/clocks v0.5.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
|
||||
@@ -174,8 +174,6 @@ github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAm
|
||||
github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
|
||||
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bep/clock v0.3.0 h1:vfOA6+wVb6pPQEiXow9f/too92vNTLe9MuwO13PfI0M=
|
||||
github.com/bep/clock v0.3.0/go.mod h1:6Gz2lapnJ9vxpvPxQ2u6FcXFRoj4kkiqQ6pm0ERZlwk=
|
||||
github.com/bep/clocks v0.5.0 h1:hhvKVGLPQWRVsBP/UB7ErrHYIO42gINVbvqxvYTPVps=
|
||||
github.com/bep/clocks v0.5.0/go.mod h1:SUq3q+OOq41y2lRQqH5fsOoxN8GbxSiT6jvoVVLCVhU=
|
||||
github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo=
|
||||
|
||||
@@ -205,6 +205,8 @@ func ReaderContains(r io.Reader, subslice []byte) bool {
|
||||
// - "Go" (strings.Title)
|
||||
// - "AP" (see https://www.apstylebook.com/)
|
||||
// - "Chicago" (see http://www.chicagomanualofstyle.org/home.html)
|
||||
// - "FirstUpper" (only the first character is upper case)
|
||||
// - "None" (no transformation)
|
||||
//
|
||||
// If an unknown or empty style is provided, AP style is what you get.
|
||||
func GetTitleFunc(style string) func(s string) string {
|
||||
@@ -214,6 +216,10 @@ func GetTitleFunc(style string) func(s string) string {
|
||||
case "chicago":
|
||||
tc := transform.NewTitleConverter(transform.ChicagoStyle)
|
||||
return tc.Title
|
||||
case "none":
|
||||
return func(s string) string { return s }
|
||||
case "firstupper":
|
||||
return FirstUpper
|
||||
default:
|
||||
tc := transform.NewTitleConverter(transform.APStyle)
|
||||
return tc.Title
|
||||
|
||||
@@ -203,7 +203,7 @@ func TestReaderContains(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetTitleFunc(t *testing.T) {
|
||||
title := "somewhere over the rainbow"
|
||||
title := "somewhere over the Rainbow"
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(helpers.GetTitleFunc("go")(title), qt.Equals, "Somewhere Over The Rainbow")
|
||||
@@ -213,6 +213,8 @@ func TestGetTitleFunc(t *testing.T) {
|
||||
c.Assert(helpers.GetTitleFunc("ap")(title), qt.Equals, "Somewhere Over the Rainbow")
|
||||
c.Assert(helpers.GetTitleFunc("")(title), qt.Equals, "Somewhere Over the Rainbow")
|
||||
c.Assert(helpers.GetTitleFunc("unknown")(title), qt.Equals, "Somewhere Over the Rainbow")
|
||||
c.Assert(helpers.GetTitleFunc("none")(title), qt.Equals, title)
|
||||
c.Assert(helpers.GetTitleFunc("firstupper")(title), qt.Equals, "Somewhere over the Rainbow")
|
||||
}
|
||||
|
||||
func BenchmarkReaderContains(b *testing.B) {
|
||||
|
||||
+39
-18
@@ -109,8 +109,16 @@ func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
|
||||
"weight": 10,
|
||||
},
|
||||
})
|
||||
v.Set("defaultContentLanguage", "en")
|
||||
} else {
|
||||
v.Set("defaultContentLanguage", lang)
|
||||
v.Set("languages", map[string]any{
|
||||
lang: map[string]interface{}{
|
||||
"weight": 10,
|
||||
},
|
||||
})
|
||||
}
|
||||
v.Set("defaultContentLanguage", "en")
|
||||
|
||||
v.Set("defaultContentLanguageInSubdir", defaultInSubDir)
|
||||
v.Set("baseURL", test.baseURL)
|
||||
|
||||
@@ -118,16 +126,21 @@ func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
|
||||
if multilingual {
|
||||
configLang = lang
|
||||
}
|
||||
defaultContentLanguage := lang
|
||||
if multilingual {
|
||||
defaultContentLanguage = "en"
|
||||
}
|
||||
|
||||
p := newTestPathSpecFromCfgAndLang(v, configLang)
|
||||
|
||||
output := p.AbsURL(test.input, addLanguage)
|
||||
expected := test.expected
|
||||
if multilingual && addLanguage {
|
||||
if !defaultInSubDir && lang == "en" {
|
||||
expected = strings.Replace(expected, "MULTI", "", 1)
|
||||
} else {
|
||||
expected = strings.Replace(expected, "MULTI", lang+"/", 1)
|
||||
}
|
||||
if addLanguage {
|
||||
addLanguage = defaultInSubDir && lang == defaultContentLanguage
|
||||
addLanguage = addLanguage || (lang != defaultContentLanguage && multilingual)
|
||||
}
|
||||
if addLanguage {
|
||||
expected = strings.Replace(expected, "MULTI", lang+"/", 1)
|
||||
} else {
|
||||
expected = strings.Replace(expected, "MULTI", "", 1)
|
||||
}
|
||||
@@ -162,8 +175,16 @@ func doTestRelURL(t testing.TB, defaultInSubDir, addLanguage, multilingual bool,
|
||||
"weight": 10,
|
||||
},
|
||||
})
|
||||
v.Set("defaultContentLanguage", "en")
|
||||
} else {
|
||||
v.Set("defaultContentLanguage", lang)
|
||||
v.Set("languages", map[string]any{
|
||||
lang: map[string]interface{}{
|
||||
"weight": 10,
|
||||
},
|
||||
})
|
||||
}
|
||||
v.Set("defaultContentLanguage", "en")
|
||||
|
||||
v.Set("defaultContentLanguageInSubdir", defaultInSubDir)
|
||||
|
||||
tests := []struct {
|
||||
@@ -209,25 +230,25 @@ func doTestRelURL(t testing.TB, defaultInSubDir, addLanguage, multilingual bool,
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
c.Run(fmt.Sprintf("%v/%t%t%t/%s", test, defaultInSubDir, addLanguage, multilingual, lang), func(c *qt.C) {
|
||||
c.Run(fmt.Sprintf("%v/defaultInSubDir=%t;addLanguage=%t;multilingual=%t/%s", test, defaultInSubDir, addLanguage, multilingual, lang), func(c *qt.C) {
|
||||
|
||||
v.Set("baseURL", test.baseURL)
|
||||
v.Set("canonifyURLs", test.canonify)
|
||||
var configLang string
|
||||
defaultContentLanguage := lang
|
||||
if multilingual {
|
||||
configLang = lang
|
||||
defaultContentLanguage = "en"
|
||||
}
|
||||
p := newTestPathSpecFromCfgAndLang(v, configLang)
|
||||
p := newTestPathSpecFromCfgAndLang(v, lang)
|
||||
|
||||
output := p.RelURL(test.input, addLanguage)
|
||||
|
||||
expected := test.expected
|
||||
if multilingual && addLanguage {
|
||||
if !defaultInSubDir && lang == "en" {
|
||||
expected = strings.Replace(expected, "MULTI", "", 1)
|
||||
} else {
|
||||
expected = strings.Replace(expected, "MULTI", "/"+lang, 1)
|
||||
}
|
||||
if addLanguage {
|
||||
addLanguage = defaultInSubDir && lang == defaultContentLanguage
|
||||
addLanguage = addLanguage || (lang != defaultContentLanguage && multilingual)
|
||||
}
|
||||
if addLanguage {
|
||||
expected = strings.Replace(expected, "MULTI", "/"+lang, 1)
|
||||
} else {
|
||||
expected = strings.Replace(expected, "MULTI", "", 1)
|
||||
}
|
||||
|
||||
@@ -1295,3 +1295,242 @@ Home.
|
||||
b.Assert(b.H.Configs.LanguageConfigSlice[0].Module.Mounts, qt.HasLen, 7)
|
||||
|
||||
}
|
||||
|
||||
func TestDefaultContentLanguageInSubdirOnlyOneLanguage(t *testing.T) {
|
||||
|
||||
t.Run("One language, default in sub dir", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
disableKinds = ["taxonomy", "term", "page", "section"]
|
||||
-- content/foo/bar.txt --
|
||||
Foo.
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
`
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
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) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
disableKinds = ["taxonomy", "term", "page", "section"]
|
||||
[languages]
|
||||
[languages.en]
|
||||
title = "English Title"
|
||||
[languages.sv]
|
||||
title = "Swedish Title"
|
||||
-- content/foo/bar.txt --
|
||||
Foo.
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
`
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
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) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = false
|
||||
disableKinds = ["taxonomy", "term", "page", "section"]
|
||||
[languages]
|
||||
[languages.en]
|
||||
title = "English Title"
|
||||
[languages.sv]
|
||||
title = "Swedish Title"
|
||||
-- content/foo/bar.txt --
|
||||
Foo.
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
`
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
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")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestLanguagesDisabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[languages]
|
||||
[languages.en]
|
||||
title = "English Title"
|
||||
[languages.sv]
|
||||
title = "Swedish Title"
|
||||
disabled = true
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
|
||||
|
||||
`
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
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)
|
||||
|
||||
+25
-3
@@ -22,7 +22,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bep/clock"
|
||||
"github.com/bep/clocks"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/markup/asciidocext"
|
||||
"github.com/gohugoio/hugo/markup/rst"
|
||||
@@ -1502,8 +1502,8 @@ func TestShouldBuild(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShouldBuildWithClock(t *testing.T) {
|
||||
htime.Clock = clock.Start(time.Date(2021, 11, 17, 20, 34, 58, 651387237, time.UTC))
|
||||
t.Cleanup(func() { htime.Clock = clock.System() })
|
||||
htime.Clock = clocks.Start(time.Date(2021, 11, 17, 20, 34, 58, 651387237, time.UTC))
|
||||
t.Cleanup(func() { htime.Clock = clocks.System() })
|
||||
past := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
|
||||
future := time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC)
|
||||
zero := time.Time{}
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ func TestPageBundlerSiteRegular(t *testing.T) {
|
||||
fs, cfg := newTestBundleSources(c)
|
||||
cfg.Set("baseURL", baseURL)
|
||||
cfg.Set("canonifyURLs", canonify)
|
||||
cfg.Set("defaultContentLanguageInSubdir", false)
|
||||
|
||||
cfg.Set("permalinks", map[string]string{
|
||||
"a": ":sections/:filename",
|
||||
|
||||
+2
-35
@@ -103,48 +103,15 @@ func (p *Paths) Lang() string {
|
||||
}
|
||||
|
||||
func (p *Paths) GetTargetLanguageBasePath() string {
|
||||
if len(p.Cfg.Languages()) > 1 {
|
||||
if p.Cfg.IsMultihost() {
|
||||
// In a multihost configuration all assets will be published below the language code.
|
||||
return p.Lang()
|
||||
}
|
||||
return p.GetLanguagePrefix()
|
||||
}
|
||||
|
||||
func (p *Paths) GetURLLanguageBasePath() string {
|
||||
if len(p.Cfg.Languages()) > 1 {
|
||||
return ""
|
||||
}
|
||||
return p.GetLanguagePrefix()
|
||||
}
|
||||
|
||||
func (p *Paths) GetLanguagePrefix() string {
|
||||
if len(p.Cfg.Languages()) < 2 {
|
||||
return ""
|
||||
}
|
||||
defaultLang := p.Cfg.DefaultContentLanguage()
|
||||
defaultInSubDir := p.Cfg.DefaultContentLanguageInSubdir()
|
||||
currentLang := p.Cfg.Language().Lang
|
||||
if currentLang == "" || (currentLang == defaultLang && !defaultInSubDir) {
|
||||
return ""
|
||||
}
|
||||
return currentLang
|
||||
}
|
||||
|
||||
// GetLangSubDir returns the given language's subdir if needed.
|
||||
func (p *Paths) GetLangSubDir(lang string) string {
|
||||
if len(p.Cfg.Languages()) < 2 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if p.Cfg.IsMultihost() {
|
||||
return ""
|
||||
}
|
||||
|
||||
if lang == "" || (lang == p.Cfg.DefaultContentLanguage() && !p.Cfg.DefaultContentLanguageInSubdir()) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return lang
|
||||
return p.Cfg.LanguagePrefix()
|
||||
}
|
||||
|
||||
// AbsPathify creates an absolute path if given a relative path. If already
|
||||
|
||||
+3
-8
@@ -887,21 +887,16 @@ func (s *Site) getLanguageTargetPathLang(alwaysInSubDir bool) string {
|
||||
|
||||
// get any language code to prefix the relative permalink with.
|
||||
func (s *Site) getLanguagePermalinkLang(alwaysInSubDir bool) string {
|
||||
if !s.h.isMultiLingual() || s.h.Conf.IsMultihost() {
|
||||
if s.h.Conf.IsMultihost() {
|
||||
return ""
|
||||
}
|
||||
|
||||
if alwaysInSubDir {
|
||||
if s.h.Conf.IsMultiLingual() && alwaysInSubDir {
|
||||
return s.Language().Lang
|
||||
}
|
||||
|
||||
isDefault := s.Language().Lang == s.conf.DefaultContentLanguage
|
||||
return s.GetLanguagePrefix()
|
||||
|
||||
if !isDefault || s.conf.DefaultContentLanguageInSubdir {
|
||||
return s.Language().Lang
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Site) getTaxonomyKey(key string) string {
|
||||
|
||||
+3
-8
@@ -470,16 +470,11 @@ func (s *Site) IsMultiLingual() bool {
|
||||
}
|
||||
|
||||
func (s *Site) LanguagePrefix() string {
|
||||
conf := s.s.Conf
|
||||
if !conf.IsMultiLingual() {
|
||||
prefix := s.GetLanguagePrefix()
|
||||
if prefix == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if !conf.DefaultContentLanguageInSubdir() && s.language.Lang == conf.DefaultContentLanguage() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return "/" + s.language.Lang
|
||||
return "/" + prefix
|
||||
}
|
||||
|
||||
// Returns the identity of this site.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -38,35 +38,6 @@ var urlFakeSource = [][2]string{
|
||||
{filepath.FromSlash("content/blue/doc2.md"), slugDoc2},
|
||||
}
|
||||
|
||||
func TestPageCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
cfg, fs := newTestCfg()
|
||||
cfg.Set("uglyURLs", false)
|
||||
cfg.Set("paginate", 10)
|
||||
configs, err := loadTestConfigFromProvider(cfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
writeSourcesToSource(t, "", fs, urlFakeSource...)
|
||||
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Configs: configs}, BuildCfg{})
|
||||
|
||||
_, err = s.Fs.WorkingDirReadOnly.Open("public/blue")
|
||||
if err != nil {
|
||||
t.Errorf("No indexed rendered.")
|
||||
}
|
||||
|
||||
for _, pth := range []string{
|
||||
"public/sd1/foo/index.html",
|
||||
"public/sd2/index.html",
|
||||
"public/sd3/index.html",
|
||||
"public/sd4.html",
|
||||
} {
|
||||
if _, err := s.Fs.WorkingDirReadOnly.Open(filepath.FromSlash(pth)); err != nil {
|
||||
t.Errorf("No alias rendered: %s", pth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUglyURLsPerSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ func doTestSitemapOutput(t *testing.T, internal bool) {
|
||||
c := qt.New(t)
|
||||
cfg, fs := newTestCfg()
|
||||
cfg.Set("baseURL", "http://auth/bub/")
|
||||
cfg.Set("defaultContentLanguageInSubdir", false)
|
||||
configs, err := loadTestConfigFromProvider(cfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
writeSource(t, fs, "layouts/sitemap.xml", sitemapTemplate)
|
||||
|
||||
@@ -937,8 +937,7 @@ func newTestCfgBasic() (config.Provider, *hugofs.Fs) {
|
||||
func newTestCfg(withConfig ...func(cfg config.Provider) error) (config.Provider, *hugofs.Fs) {
|
||||
mm := afero.NewMemMapFs()
|
||||
cfg := config.New()
|
||||
// Default is false, but true is easier to use as default in tests
|
||||
cfg.Set("defaultContentLanguageInSubdir", true)
|
||||
cfg.Set("defaultContentLanguageInSubdir", false)
|
||||
cfg.Set("publishDir", "public")
|
||||
|
||||
fs := hugofs.NewFromOld(hugofs.NewBaseFileDecorator(mm), cfg)
|
||||
|
||||
+5
-2
@@ -1,7 +1,10 @@
|
||||
# Release env.
|
||||
# These will be replaced by script before release.
|
||||
HUGORELEASER_TAG=v0.115.0
|
||||
HUGORELEASER_COMMITISH=67caf50698783d3b3d78559ac81483d83e5e1a35
|
||||
HUGORELEASER_TAG=v0.115.3
|
||||
HUGORELEASER_COMMITISH=5c2e014a5150553a9fa4f9c1eb7dc4db89c0f1ab
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ type LanguageConfig struct {
|
||||
// The language weight. When set to a non-zero value, this will
|
||||
// be the main sort criteria for the language.
|
||||
Weight int
|
||||
|
||||
// Set to true to disable this language.
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
func DecodeConfig(m map[string]any) (map[string]LanguageConfig, error) {
|
||||
|
||||
@@ -50,6 +50,7 @@ func TestUnfinished(t *testing.T) {
|
||||
|
||||
p := commonTestScriptsParam
|
||||
p.Dir = "testscripts/unfinished"
|
||||
//p.UpdateScripts = true
|
||||
|
||||
testscript.Run(t, p)
|
||||
}
|
||||
|
||||
+3
-1
@@ -292,8 +292,10 @@ func (c *Client) Vendor() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Also include any theme.toml or config.* files in the root.
|
||||
// Also include any theme.toml or config.* or hugo.* files in the root.
|
||||
configFiles, _ := afero.Glob(c.fs, filepath.Join(dir, "config.*"))
|
||||
configFiles2, _ := afero.Glob(c.fs, filepath.Join(dir, "hugo.*"))
|
||||
configFiles = append(configFiles, configFiles2...)
|
||||
configFiles = append(configFiles, filepath.Join(dir, "theme.toml"))
|
||||
for _, configFile := range configFiles {
|
||||
if err := hugio.CopyFile(c.fs, configFile, filepath.Join(vendorDir, t.Path(), filepath.Base(configFile))); err != nil {
|
||||
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
+45
-13
@@ -1,16 +1,33 @@
|
||||
name: hugo
|
||||
issues: https://github.com/gohugoio/hugo/issues
|
||||
source-code: https://github.com/gohugoio/hugo.git
|
||||
website: https://gohugo.io/
|
||||
summary: Fast and Flexible Static Site Generator
|
||||
description: |
|
||||
Hugo is a static HTML and CSS website generator written in Go. It is
|
||||
optimized for speed, easy use and configurability. Hugo takes a directory
|
||||
with content and templates and renders them into a full HTML website.
|
||||
license: "Apache-2.0"
|
||||
base: core20
|
||||
confinement: strict
|
||||
adopt-info: hugo
|
||||
title: Hugo
|
||||
icon: snap/local/logo.png
|
||||
summary: Fast and Flexible Static Site Generator
|
||||
description: |
|
||||
Hugo is a static site generator written in Go, optimized for speed and
|
||||
designed for flexibility. With its advanced templating system and fast asset
|
||||
pipelines, Hugo renders a complete site in seconds, often less.
|
||||
|
||||
Due to its flexible framework, multilingual support, and powerful taxonomy
|
||||
system, Hugo is widely used to create:
|
||||
|
||||
* Corporate, government, nonprofit, education, news, event, and project sites
|
||||
* Documentation sites
|
||||
* Image portfolios
|
||||
* Landing pages
|
||||
* Business, professional, and personal blogs
|
||||
* Resumes and CVs
|
||||
|
||||
Use Hugo's embedded web server during development to instantly see changes to
|
||||
content, structure, behavior, and presentation. Then deploy the site to your
|
||||
host, or push changes to your Git provider for automated builds and
|
||||
deployment.
|
||||
issues: https://github.com/gohugoio/hugo/issues
|
||||
license: "Apache-2.0"
|
||||
source-code: https://github.com/gohugoio/hugo.git
|
||||
website: https://gohugo.io/
|
||||
|
||||
package-repositories:
|
||||
- type: apt
|
||||
@@ -28,8 +45,20 @@ plugs:
|
||||
interface: personal-files
|
||||
read:
|
||||
- $HOME/.gitconfig
|
||||
- $HOME/.config/git # Allows $HOME/.config/git/config and more
|
||||
- $HOME/.gitconfig.local # See #10337
|
||||
- $HOME/.config/git
|
||||
- $HOME/.gitconfig.local
|
||||
dot-aws:
|
||||
interface: personal-files
|
||||
read:
|
||||
- $HOME/.aws
|
||||
dot-azure:
|
||||
interface: personal-files
|
||||
read:
|
||||
- $HOME/.azure
|
||||
dot-config-gcloud:
|
||||
interface: personal-files
|
||||
read:
|
||||
- $HOME/.config/gcloud
|
||||
|
||||
environment:
|
||||
HOME: $SNAP_REAL_HOME
|
||||
@@ -44,14 +73,14 @@ environment:
|
||||
# HUGO_SECURITY_EXEC_OSENV
|
||||
#
|
||||
# Default value:
|
||||
# (?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)$
|
||||
# Bundled applications require additional access:
|
||||
# git: GIT_EXEC_PATH and LD_LIBRARY_PATH
|
||||
# npx: npm_config_{cache,init_module,userconfig}
|
||||
# pandoc: pandoc_datadir
|
||||
# rst2html: PYTHONHOME and SNAP
|
||||
# asciidoctor: RUBYLIB
|
||||
HUGO_SECURITY_EXEC_OSENV: (?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|GIT_EXEC_PATH|LD_LIBRARY_PATH|npm_config_(cache|init_module|userconfig)|pandoc_datadir|PYTHONHOME|SNAP|RUBYLIB)$
|
||||
HUGO_SECURITY_EXEC_OSENV: (?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|GIT_EXEC_PATH|LD_LIBRARY_PATH|npm_config_(cache|init_module|userconfig)|pandoc_datadir|PYTHONHOME|SNAP|RUBYLIB)$
|
||||
|
||||
apps:
|
||||
hugo:
|
||||
@@ -63,6 +92,9 @@ apps:
|
||||
- removable-media
|
||||
- etc-gitconfig
|
||||
- gitconfig
|
||||
- dot-aws
|
||||
- dot-azure
|
||||
- dot-config-gcloud
|
||||
|
||||
parts:
|
||||
git:
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
dostounix golden/vendor.txt
|
||||
|
||||
hugo mod vendor
|
||||
cmp _vendor/modules.txt golden/vendor.txt
|
||||
ls _vendor/github.com/gohugoio/hugo-mod-integrationtests/withconfigtoml
|
||||
stdout 'config.toml'
|
||||
ls _vendor/github.com/gohugoio/hugo-mod-integrationtests/withhugotoml
|
||||
stdout 'hugo.toml'
|
||||
|
||||
|
||||
-- hugo.toml --
|
||||
title = "Hugo Modules Test"
|
||||
[module]
|
||||
[[module.imports]]
|
||||
path="github.com/gohugoio/hugo-mod-integrationtests/withconfigtoml"
|
||||
[[module.imports]]
|
||||
path="github.com/gohugoio/hugo-mod-integrationtests/withhugotoml"
|
||||
-- go.mod --
|
||||
go 1.19
|
||||
|
||||
module github.com/gohugoio/testmod
|
||||
-- golden/vendor.txt --
|
||||
# github.com/gohugoio/hugo-mod-integrationtests/withconfigtoml v1.0.0
|
||||
# github.com/gohugoio/hugo-mod-integrationtests/withhugotoml v1.0.0
|
||||
@@ -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)
|
||||
|
||||
+23
-7
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/bep/overlayfs"
|
||||
"github.com/gohugoio/hugo/resources/images"
|
||||
|
||||
// Importing image codecs for image.DecodeConfig
|
||||
@@ -31,23 +32,38 @@ import (
|
||||
_ "golang.org/x/image/webp"
|
||||
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// New returns a new instance of the images-namespaced template functions.
|
||||
func New(deps *deps.Deps) *Namespace {
|
||||
func New(d *deps.Deps) *Namespace {
|
||||
var readFileFs afero.Fs
|
||||
|
||||
// The docshelper script does not have or need all the dependencies set up.
|
||||
if d.PathSpec != nil {
|
||||
readFileFs = overlayfs.New(overlayfs.Options{
|
||||
Fss: []afero.Fs{
|
||||
d.PathSpec.BaseFs.Work,
|
||||
d.PathSpec.BaseFs.Content.Fs,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return &Namespace{
|
||||
Filters: &images.Filters{},
|
||||
cache: map[string]image.Config{},
|
||||
deps: deps,
|
||||
readFileFs: readFileFs,
|
||||
Filters: &images.Filters{},
|
||||
cache: map[string]image.Config{},
|
||||
deps: d,
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace provides template functions for the "images" namespace.
|
||||
type Namespace struct {
|
||||
*images.Filters
|
||||
cacheMu sync.RWMutex
|
||||
cache map[string]image.Config
|
||||
readFileFs afero.Fs
|
||||
cacheMu sync.RWMutex
|
||||
cache map[string]image.Config
|
||||
|
||||
deps *deps.Deps
|
||||
}
|
||||
@@ -73,7 +89,7 @@ func (ns *Namespace) Config(path any) (image.Config, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
f, err := ns.deps.Fs.WorkingDirReadOnly.Open(filename)
|
||||
f, err := ns.readFileFs.Open(filename)
|
||||
if err != nil {
|
||||
return image.Config{}, err
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ import (
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/testconfig"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
@@ -86,11 +84,10 @@ func TestNSConfig(t *testing.T) {
|
||||
afs := afero.NewMemMapFs()
|
||||
v := config.New()
|
||||
v.Set("workingDir", "/a/b")
|
||||
conf := testconfig.GetTestConfig(afs, v)
|
||||
bcfg := conf.BaseConfig()
|
||||
fs := hugofs.NewFrom(afs, bcfg)
|
||||
d := testconfig.GetTestDeps(afs, v)
|
||||
bcfg := d.Conf
|
||||
|
||||
ns := New(&deps.Deps{Fs: fs, Conf: conf})
|
||||
ns := New(d)
|
||||
|
||||
for _, test := range configTests {
|
||||
|
||||
@@ -104,7 +101,7 @@ func TestNSConfig(t *testing.T) {
|
||||
// cast path to string for afero.WriteFile
|
||||
sp, err := cast.ToStringE(test.path)
|
||||
c.Assert(err, qt.IsNil)
|
||||
afero.WriteFile(ns.deps.Fs.Source, filepath.Join(bcfg.WorkingDir, sp), test.input, 0755)
|
||||
afero.WriteFile(ns.deps.Fs.Source, filepath.Join(bcfg.WorkingDir(), sp), test.input, 0755)
|
||||
|
||||
result, err := ns.Config(test.path)
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package images_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestImageConfigFromModule(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = 'http://example.com/'
|
||||
theme = ["mytheme"]
|
||||
-- static/images/pixel1.png --
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
-- themes/mytheme/static/images/pixel2.png --
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
-- layouts/index.html --
|
||||
{{ $path := "static/images/pixel1.png" }}
|
||||
fileExists OK: {{ fileExists $path }}|
|
||||
imageConfig OK: {{ (imageConfig $path).Width }}|
|
||||
{{ $path2 := "static/images/pixel2.png" }}
|
||||
fileExists2 OK: {{ fileExists $path2 }}|
|
||||
imageConfig2 OK: {{ (imageConfig $path2).Width }}|
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", `
|
||||
fileExists OK: true|
|
||||
imageConfig OK: 1|
|
||||
fileExists2 OK: true|
|
||||
imageConfig2 OK: 1|
|
||||
`)
|
||||
}
|
||||
Reference in New Issue
Block a user