Compare commits

..

24 Commits

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

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

And similar.

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

This changes how the keyParts for data files are generated so that data
from modules or native directories are treated the same.
2023-07-15 11:13:08 +02:00
hugoreleaser 79f15be5b0 releaser: Prepare repository for 0.116.0-DEV
[ci skip]
2023-07-13 16:23:50 +00:00
hugoreleaser 5c2e014a51 releaser: Bump versions for release of 0.115.3
[ci skip]
2023-07-13 16:11:34 +00:00
Bjørn Erik Pedersen cc44583cc3 Improve behavior of defaultContentLanguageInSubdir when only the default language is enabled
1 .Create sitemapindex in root of publishDir (will contain link to sitemap.xml in defaultContentLanguage site)
2. Create index.html in root of publishDir (will redirect to defaultContentLanguage site)

Fixes #11229
2023-07-13 18:06:36 +02:00
Bjørn Erik Pedersen 4da672af88 Return error when .Render is invoked without arg
Fixes #11243
2023-07-13 18:06:36 +02:00
Joe Mooring f1886f8c37 js: Pass tsconfig.json to esBuild
Note that esBuild only inspects/honors certain fields.
See https://esbuild.github.io/content-types/#tsconfig-json.

Fixes #11232
2023-07-13 15:19:04 +02:00
Bjørn Erik Pedersen 5bec50838c tpl/collections: Fix WordCount (etc.) regression in Where, Sort, Delimit
Fixes #11234
2023-07-11 12:11:39 +02:00
Joe Mooring f650e4d751 config/allconfig: Update timeout description 2023-07-08 23:22:26 +02:00
Joe Mooring c934a45069 docs: Refresh docs.json 2023-07-08 23:20:11 +02:00
hugoreleaser 91b02091a5 releaser: Prepare repository for 0.116.0-DEV
[ci skip]
2023-07-08 17:19:03 +00:00
hugoreleaser 8966424e0e releaser: Bump versions for release of 0.115.2
[ci skip]
2023-07-08 17:06:56 +00:00
Joe Mooring 72510969a6 snap: Allow access to AWS, Azure, and GCS config/credentials
Fixes #11122
2023-07-08 09:43:04 -07:00
Joe Mooring 70c5e485bb snap: Update metadata and security.exec.osEnv
See #11217
2023-07-08 09:43:04 -07:00
Bjørn Erik Pedersen a78b17d7f1 Make imageConfig work with modules
Fixes #11205
2023-07-08 17:20:18 +02:00
Bjørn Erik Pedersen a481942532 Restore language.disabled config
Fixes #11219
2023-07-08 16:51:48 +02:00
Bjørn Erik Pedersen 0f921ace6f Fix hugo mod vendor for modules with hugo.toml
Fixes #11221
2023-07-08 16:08:46 +02:00
Bjørn Erik Pedersen 6019953769 Fix static content files multilingual root regression
Fixes #11223
2023-07-08 16:08:46 +02:00
Bjørn Erik Pedersen 92e86702ea Fix defaultContentLanguageInSubdir with only 1 language
Fixes #10064
2023-07-08 16:08:46 +02:00
David Karlsson 6c9ea022a9 config: Expand default security.exec.osEnv policy
To better support private Hugo modules and automatically
launching a text editor on content creation, this change
adds the following environment variables to the default
security policy:

- HOME
- XDG_CONFIG_HOME
- USERPROFILE
- SSH_AUTH_SOCK
- DISPLAY
- LANG

Fixes #9333
2023-07-07 13:54:06 +02:00
Bjørn Erik Pedersen 12d3469dd1 Add titleCaseStyle none and firstupper
Fixes #11204
2023-07-05 20:35:20 +02:00
Anthony Fok bf7ee8a91a Bump github.com/bep/clock v0.3.0 to renamed github.com/bep/clocks v0.5.0 2023-07-04 09:14:48 +02:00
hugoreleaser d912491f2d releaser: Prepare repository for 0.116.0-DEV
[ci skip]
2023-07-03 17:39:56 +00:00
60 changed files with 774 additions and 213 deletions
-1
View File
@@ -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"
+3 -3
View File
@@ -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 {
+2 -2
View File
@@ -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
View File
@@ -348,7 +348,7 @@ description = ""
homepage = "http://example.com/"
tags = []
features = []
min_version = "0.115.1"
min_version = "0.115.0"
[author]
name = ""
+24 -1
View File
@@ -59,11 +59,34 @@ func GetGID() uint64 {
return n
}
// IsFeatureNotAvailableError returns true if the given error is or contains a FeatureNotAvailableError.
func IsFeatureNotAvailableError(err error) bool {
return errors.Is(err, &FeatureNotAvailableError{})
}
// ErrFeatureNotAvailable denotes that a feature is unavailable.
//
// We will, at least to begin with, make some Hugo features (SCSS with libsass) optional,
// and this error is used to signal those situations.
var ErrFeatureNotAvailable = errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information")
var ErrFeatureNotAvailable = &FeatureNotAvailableError{Cause: errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information")}
// FeatureNotAvailableError is an error type used to signal that a feature is not available.
type FeatureNotAvailableError struct {
Cause error
}
func (e *FeatureNotAvailableError) Unwrap() error {
return e.Cause
}
func (e *FeatureNotAvailableError) Error() string {
return e.Cause.Error()
}
func (e *FeatureNotAvailableError) Is(target error) bool {
_, ok := target.(*FeatureNotAvailableError)
return ok
}
// Must panics if err != nil.
func Must(err error) {
+10
View File
@@ -14,6 +14,7 @@
package herrors
import (
"errors"
"fmt"
"testing"
@@ -34,3 +35,12 @@ func TestIsNotExist(t *testing.T) {
// os.IsNotExist returns false for wrapped errors.
c.Assert(IsNotExist(fmt.Errorf("foo: %w", afero.ErrFileNotFound)), qt.Equals, true)
}
func TestIsFeatureNotAvailableError(t *testing.T) {
c := qt.New(t)
c.Assert(IsFeatureNotAvailableError(ErrFeatureNotAvailable), qt.Equals, true)
c.Assert(IsFeatureNotAvailableError(&FeatureNotAvailableError{}), qt.Equals, true)
c.Assert(IsFeatureNotAvailableError(errors.New("asdf")), qt.Equals, false)
}
+2 -2
View File
@@ -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 {
+3 -3
View File
@@ -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",
}
+9 -1
View File
@@ -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.
+1 -1
View File
@@ -150,7 +150,7 @@ var allDecoderSetups = map[string]decodeWeight{
key: "outputs",
decode: func(d decodeWeight, p decodeConfig) error {
defaults := createDefaultOutputFormats(p.c.OutputFormats.Config)
m := p.p.GetStringMap("outputs")
m := maps.CleanConfigStringMap(p.p.GetStringMap("outputs"))
p.c.Outputs = make(map[string][]string)
for k, v := range m {
s := types.ToStringSlicePreserveString(v)
+10
View File
@@ -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
}
+12 -4
View File
@@ -293,11 +293,19 @@ func (l configLoader) applyOsEnvOverrides(environ []string) error {
} else {
l.cfg.Set(env.Key, val)
}
} else if nestedKey != "" {
owner[nestedKey] = env.Value
} else {
// The container does not exist yet.
l.cfg.Set(strings.ReplaceAll(env.Key, delim, "."), env.Value)
if nestedKey != "" {
owner[nestedKey] = env.Value
} else {
var val any = env.Value
if _, ok := allDecoderSetups[env.Key]; ok {
// A map.
val, err = metadecoders.Default.UnmarshalStringTo(env.Value, map[string]interface{}{})
}
if err == nil {
l.cfg.Set(strings.ReplaceAll(env.Key, delim, "."), val)
}
}
}
}
+1 -1
View File
@@ -148,7 +148,7 @@ func (b BuildConfig) UseResourceCache(err error) bool {
}
if b.UseResourceCacheWhen == "fallback" {
return err == herrors.ErrFeatureNotAvailable
return herrors.IsFeatureNotAvailableError(err)
}
return true
+1
View File
@@ -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
+1 -1
View File
@@ -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$"),
+5 -4
View File
@@ -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
View File
@@ -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": {
+1 -2
View File
@@ -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
-2
View File
@@ -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=
+6
View File
@@ -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
+3 -1
View File
@@ -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
View File
@@ -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)
}
+239
View File
@@ -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"})
})
}
+29
View File
@@ -1178,3 +1178,32 @@ target = "content/resources-b"
b.AssertFileContent("public/resources-a/subdir/about/index.html", "Single")
b.AssertFileContent("public/resources-b/subdir/about/index.html", "Single")
}
func TestMountData(t *testing.T) {
files := `
-- hugo.toml --
baseURL = 'https://example.org/'
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
[[module.mounts]]
source = "data"
target = "data"
[[module.mounts]]
source = "extra-data"
target = "data/extra"
-- extra-data/test.yaml --
message: Hugo Rocks
-- layouts/index.html --
{{ site.Data.extra.test.message }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b.AssertFileContent("public/index.html", "Hugo Rocks")
}
+6 -4
View File
@@ -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 != "" {
+3
View File
@@ -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
View File
@@ -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)
}
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+1 -1
View File
@@ -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
}
-29
View File
@@ -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()
+1
View File
@@ -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)
+1 -2
View File
@@ -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
View File
@@ -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
+3
View File
@@ -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) {
+1
View File
@@ -50,6 +50,7 @@ func TestUnfinished(t *testing.T) {
p := commonTestScriptsParam
p.Dir = "testscripts/unfinished"
//p.UpdateScripts = true
testscript.Run(t, p)
}
+3 -1
View File
@@ -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 {
+2 -1
View File
@@ -23,6 +23,7 @@ import (
"strings"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/maps"
"github.com/niklasfasching/go-org/org"
xml "github.com/clbanning/mxj/v2"
@@ -90,7 +91,7 @@ func (d Decoder) UnmarshalStringTo(data string, typ any) (any, error) {
switch typ.(type) {
case string:
return data, nil
case map[string]any:
case map[string]any, maps.Params:
format := d.FormatFromContentString(data)
return d.UnmarshalToMap([]byte(data), format)
case []any:
@@ -181,7 +181,7 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx
if err != nil {
if hexec.IsNotFound(err) {
// This may be on a CI server etc. Will fall back to pre-built assets.
return herrors.ErrFeatureNotAvailable
return &herrors.FeatureNotAvailableError{Cause: err}
}
return err
}
@@ -200,7 +200,7 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx
err = cmd.Run()
if err != nil {
if hexec.IsNotFound(err) {
return herrors.ErrFeatureNotAvailable
return &herrors.FeatureNotAvailableError{Cause: err}
}
return fmt.Errorf(errBuf.String()+": %w", err)
}
@@ -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())
}
+1 -1
View File
@@ -449,7 +449,7 @@ func (r *resourceAdapter) transform(publish, setContent bool) error {
newErr := func(err error) error {
msg := fmt.Sprintf("%s: failed to transform %q (%s)", strings.ToUpper(tr.Key().Name), tctx.InPath, tctx.InMediaType.Type)
if err == herrors.ErrFeatureNotAvailable {
if herrors.IsFeatureNotAvailableError(err) {
var errMsg string
if tr.Key().Name == "postcss" {
// This transformation is not available in this
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

+45 -13
View File
@@ -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:
+24
View File
@@ -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
+3 -2
View File
@@ -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
}
+3 -2
View File
@@ -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)
+41
View File
@@ -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|
`)
}
+6 -3
View File
@@ -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
}
+3 -2
View File
@@ -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
View File
@@ -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
}
+7 -6
View File
@@ -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
View File
@@ -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
}
+4 -7
View File
@@ -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)
+56
View File
@@ -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|
`)
}