Compare commits

..

38 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
hugoreleaser 857374e693 releaser: Bump versions for release of 0.115.1
[ci skip]
2023-07-03 17:28:25 +00:00
Bjørn Erik Pedersen c27639b9a0 docs: Regen docs helper 2023-07-03 19:25:27 +02:00
Bjørn Erik Pedersen ceb486f98c Fix buildStats when tags and classes are disabled
Fixes #11202
2023-07-03 09:58:33 +02:00
Bjørn Erik Pedersen 5afc89f2bf Rework the build.writeStats struct
Mostly to make it easier to toggle on/off this feature from the env.

See #11191
2023-07-02 13:04:11 +02:00
Joe Mooring c1eac616d5 snap: Explicitly set security.exec.osenv during build
Fixes #11199
2023-07-01 17:55:23 -07:00
Joe Mooring 3c8256a137 snap: Restore security.exec.osenv whitelist
Fixes #11195
2023-07-01 14:55:57 -07:00
Bjørn Erik Pedersen 19d76ae96b github: Build for Dragonfly in CI build
To discover upstream issues before we release.
2023-07-01 18:53:48 +02:00
Bjørn Erik Pedersen 11ecea6106 Make build.writeStats a struct
So you can do

```toml
[build.writeStats]
  tags = true
  classes = true
  ids = false
```

Fixes #11191
2023-07-01 15:38:32 +02:00
dependabot[bot] da98724bc8 build(deps): bump github.com/evanw/esbuild from 0.18.10 to 0.18.11
Bumps [github.com/evanw/esbuild](https://github.com/evanw/esbuild) from 0.18.10 to 0.18.11.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.18.10...v0.18.11)

---
updated-dependencies:
- dependency-name: github.com/evanw/esbuild
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-01 15:33:24 +02:00
dependabot[bot] 4d470bb732 build(deps): bump github.com/alecthomas/chroma/v2 from 2.7.0 to 2.8.0
Bumps [github.com/alecthomas/chroma/v2](https://github.com/alecthomas/chroma) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/alecthomas/chroma/releases)
- [Changelog](https://github.com/alecthomas/chroma/blob/master/.goreleaser.yml)
- [Commits](https://github.com/alecthomas/chroma/compare/v2.7.0...v2.8.0)

---
updated-dependencies:
- dependency-name: github.com/alecthomas/chroma/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-01 10:33:49 +02:00
Bjørn Erik Pedersen 0ff8e13c1c commands: Fix index out of range in hugo mod get
Fixes #11190
2023-06-30 15:57:46 +02:00
Bjørn Erik Pedersen ffd37d4f75 Only print the path warnings once
We could reset and rerun it on server rebuilds, but that report needs a full build to make sense.

Also clean up the config vs flags in this area: Make all config settings match the flags e.g. `printPathWarnings`, but set up aliases for the
old.

Fixes #11187
2023-06-30 10:24:28 +02:00
Bjørn Erik Pedersen b4b65245b2 Update README.md 2023-06-30 08:01:25 +02:00
hugoreleaser 87886f40d1 releaser: Prepare repository for 0.116.0-DEV
[ci skip]
2023-06-29 16:09:06 +00:00
70 changed files with 1068 additions and 286 deletions
+7
View File
@@ -104,3 +104,10 @@ jobs:
- name: Build tags
run: |
go install -tags extended,nodeploy
- if: matrix.os == 'ubuntu-latest'
name: Build for dragonfly
run: |
go install
env:
GOARCH: amd64
GOOS: dragonfly
+1 -2
View File
@@ -63,7 +63,7 @@ And with [Hugo Modules], you can share content, assets, data, translations, them
<p>&nbsp;</p>
<p float="left">
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://cloudcannon.com/?utm_campaign=HugoSponsorship&utm_source=sponsor&utm_content=gohugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/cloudcannon-blue.svg" width="220" alt="CloudCannon"></a>
<a href="https://cloudcannon.com/hugo-cms/?utm_campaign=HugoSponsorship&utm_source=sponsor&utm_content=gohugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/cloudcannon-blue.svg" width="220" alt="CloudCannon"></a>
<p>&nbsp;</p>
## Installation
@@ -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"
+10 -12
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"
@@ -110,12 +110,10 @@ type rootCommand struct {
environment string
// Common build flags.
baseURL string
gc bool
poll string
forceSyncStatic bool
printPathWarnings bool
printUnusedTemplates bool
baseURL string
gc bool
poll string
forceSyncStatic bool
// Profile flags (for debugging of performance problems)
cpuprofile string
@@ -175,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{
@@ -285,10 +283,10 @@ 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.LogPathWarnings {
if base.PrintPathWarnings {
// Note that we only care about the "dynamic creates" here,
// so skip the static fs.
fs.PublishDir = hugofs.NewCreateCountingFs(fs.PublishDir)
@@ -544,8 +542,8 @@ func applyLocalFlagsBuild(cmd *cobra.Command, r *rootCommand) {
cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
cmd.Flags().BoolP("noBuildLock", "", false, "don't create .hugo_build.lock file")
cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations")
cmd.Flags().BoolVarP(&r.printPathWarnings, "printPathWarnings", "", false, "print warnings on duplicate target paths etc.")
cmd.Flags().BoolVarP(&r.printUnusedTemplates, "printUnusedTemplates", "", false, "print warnings on unused templates.")
cmd.Flags().BoolP("printPathWarnings", "", false, "print warnings on duplicate target paths etc.")
cmd.Flags().BoolP("printUnusedTemplates", "", false, "print warnings on unused templates.")
cmd.Flags().StringVarP(&r.cpuprofile, "profile-cpu", "", "", "write cpu profile to `file`")
cmd.Flags().StringVarP(&r.memprofile, "profile-mem", "", "", "write memory profile to `file`")
cmd.Flags().BoolVarP(&r.printm, "printMemoryUsage", "", false, "print memory usage to screen at intervals")
+3 -5
View File
@@ -79,11 +79,9 @@ func flagsToCfgWithAdditionalConfigBase(cd *simplecobra.Commandeer, cfg config.P
// Flags with a different name in the config.
keyMap := map[string]string{
"minify": "minifyOutput",
"destination": "publishDir",
"printI18nWarnings": "logI18nWarnings",
"printPathWarnings": "logPathWarnings",
"editor": "newContentEditor",
"minify": "minifyOutput",
"destination": "publishDir",
"editor": "newContentEditor",
}
// Flags that we for some reason don't want to expose in the site config.
+2 -10
View File
@@ -43,7 +43,6 @@ import (
"github.com/gohugoio/hugo/hugolib/filesystems"
"github.com/gohugoio/hugo/livereload"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/watcher"
"github.com/spf13/fsync"
"golang.org/x/sync/errgroup"
@@ -418,13 +417,6 @@ func (c *hugoBuilder) build() error {
return err
}
if c.r.printUnusedTemplates {
unusedTemplates := h.Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
for _, unusedTemplate := range unusedTemplates {
c.r.logger.Warnf("Template %s is unused, source file %s", unusedTemplate.Name(), unusedTemplate.Filename())
}
}
h.PrintProcessingStats(os.Stdout)
c.r.Println()
}
@@ -1050,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
@@ -224,7 +224,7 @@ Run "go help get" for more information. All flags available for "go get" is also
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
// We currently just pass on the flags we get to Go and
// need to do the flag handling manually.
if len(args) == 1 && args[0] == "-h" || args[0] == "--help" {
if len(args) == 1 && (args[0] == "-h" || args[0] == "--help") {
return errHelp
}
+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 {
+2 -2
View File
@@ -17,7 +17,7 @@ package hugo
// This should be the only one.
var CurrentVersion = Version{
Major: 0,
Minor: 115,
Minor: 116,
PatchLevel: 0,
Suffix: "",
Suffix: "-DEV",
}
+15 -7
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 {
@@ -484,12 +492,6 @@ type RootConfig struct {
// Enable to print greppable placeholders (on the form "[i18n] TRANSLATIONID") for missing translation strings.
EnableMissingTranslationPlaceholders bool
// Enable to print warnings for missing translation strings.
LogI18nWarnings bool
// ENable to print warnings for multiple files published to the same destination.
LogPathWarnings bool
// Enable to panic on warning log entries. This may make it easier to detect the source.
PanicOnWarning bool
@@ -525,6 +527,12 @@ type RootConfig struct {
// Whether to track and print unused templates during the build.
PrintUnusedTemplates bool
// Enable to print warnings for missing translation strings.
PrintI18nWarnings bool
// ENable to print warnings for multiple files published to the same destination.
PrintPathWarnings bool
// URL to be used as a placeholder when a page reference cannot be found in ref or relref. Is used as-is.
RefLinksNotFoundURL string
@@ -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)
+12 -2
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
}
@@ -199,8 +209,8 @@ func (c ConfigLanguage) EnableMissingTranslationPlaceholders() bool {
return c.config.EnableMissingTranslationPlaceholders
}
func (c ConfigLanguage) LogI18nWarnings() bool {
return c.config.LogI18nWarnings
func (c ConfigLanguage) PrintI18nWarnings() bool {
return c.config.PrintI18nWarnings
}
func (c ConfigLanguage) CreateTitle(s string) string {
+18
View File
@@ -69,3 +69,21 @@ Title: {{ .Title }}
b.Assert(modConf.Mounts[1].Lang, qt.Equals, "sv")
}
func TestConfigAliases(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
logI18nWarnings = true
logPathWarnings = true
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{T: t, TxtarString: files},
).Build()
conf := b.H.Configs.Base
b.Assert(conf.PrintI18nWarnings, qt.Equals, true)
b.Assert(conf.PrintPathWarnings, qt.Equals, true)
}
+17 -5
View File
@@ -139,7 +139,11 @@ type configLoader struct {
// Handle some legacy values.
func (l configLoader) applyConfigAliases() error {
aliases := []types.KeyValueStr{{Key: "taxonomies", Value: "indexes"}}
aliases := []types.KeyValueStr{
{Key: "indexes", Value: "taxonomies"},
{Key: "logI18nWarnings", Value: "printI18nWarnings"},
{Key: "logPathWarnings", Value: "printPathWarnings"},
}
for _, alias := range aliases {
if l.cfg.IsSet(alias.Key) {
@@ -289,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)
}
}
}
}
+28 -4
View File
@@ -82,7 +82,7 @@ type LoadConfigResult struct {
var defaultBuild = BuildConfig{
UseResourceCacheWhen: "fallback",
WriteStats: false,
BuildStats: BuildStats{},
CacheBusters: []CacheBuster{
{
@@ -111,7 +111,8 @@ type BuildConfig struct {
// When enabled, will collect and write a hugo_stats.json with some build
// related aggregated data (e.g. CSS class names).
WriteStats bool
// Note that this was a bool <= v0.115.0.
BuildStats BuildStats
// Can be used to toggle off writing of the IntelliSense /assets/jsconfig.js
// file.
@@ -121,6 +122,21 @@ type BuildConfig struct {
CacheBusters []CacheBuster
}
// BuildStats configures if and what to write to the hugo_stats.json file.
type BuildStats struct {
Enable bool
DisableTags bool
DisableClasses bool
DisableIDs bool
}
func (w BuildStats) Enabled() bool {
if !w.Enable {
return false
}
return !w.DisableTags || !w.DisableClasses || !w.DisableIDs
}
func (b BuildConfig) clone() BuildConfig {
b.CacheBusters = append([]CacheBuster{}, b.CacheBusters...)
return b
@@ -132,7 +148,7 @@ func (b BuildConfig) UseResourceCache(err error) bool {
}
if b.UseResourceCacheWhen == "fallback" {
return err == herrors.ErrFeatureNotAvailable
return herrors.IsFeatureNotAvailableError(err)
}
return true
@@ -171,14 +187,22 @@ func (b *BuildConfig) CompileConfig(logger loggers.Logger) error {
func DecodeBuildConfig(cfg Provider) BuildConfig {
m := cfg.GetStringMap("build")
b := defaultBuild.clone()
if m == nil {
return b
}
// writeStats was a bool <= v0.115.0.
if writeStats, ok := m["writestats"]; ok {
if bb, ok := writeStats.(bool); ok {
m["buildstats"] = BuildStats{Enable: bb}
}
}
err := mapstructure.WeakDecode(m, &b)
if err != nil {
return defaultBuild
return b
}
b.UseResourceCacheWhen = strings.ToLower(b.UseResourceCacheWhen)
+2 -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
@@ -57,7 +58,7 @@ type AllProvider interface {
EnableMissingTranslationPlaceholders() bool
TemplateMetrics() bool
TemplateMetricsHints() bool
LogI18nWarnings() bool
PrintI18nWarnings() bool
CreateTitle(s string) string
IgnoreFile(s string) bool
NewContentEditor() 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
+45 -2
View File
@@ -73,6 +73,12 @@
"applescript"
]
},
{
"Name": "ArangoDB AQL",
"Aliases": [
"aql"
]
},
{
"Name": "Arduino",
"Aliases": [
@@ -85,6 +91,19 @@
"armasm"
]
},
{
"Name": "AutoHotkey",
"Aliases": [
"autohotkey",
"ahk"
]
},
{
"Name": "AutoIt",
"Aliases": [
"autoit"
]
},
{
"Name": "Awk",
"Aliases": [
@@ -666,6 +685,12 @@
"io"
]
},
{
"Name": "ISCdhcpd",
"Aliases": [
"iscdhcpd"
]
},
{
"Name": "J",
"Aliases": [
@@ -1230,6 +1255,12 @@
"sieve"
]
},
{
"Name": "Smali",
"Aliases": [
"smali"
]
},
{
"Name": "Smalltalk",
"Aliases": [
@@ -1324,6 +1355,13 @@
"tablegen"
]
},
{
"Name": "Tal",
"Aliases": [
"tal",
"uxntal"
]
},
{
"Name": "TASM",
"Aliases": [
@@ -1505,6 +1543,12 @@
"Name": "WDTE",
"Aliases": null
},
{
"Name": "WebGPU Shading Language",
"Aliases": [
"wgsl"
]
},
{
"Name": "Whiley",
"Aliases": [
@@ -1723,7 +1767,6 @@
"js": {
"precision": 0,
"keepVarNames": false,
"noNullishOperator": false,
"version": 2022
},
"json": {
@@ -1749,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": {
+3 -4
View File
@@ -3,10 +3,10 @@ module github.com/gohugoio/hugo
require (
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69
github.com/PuerkitoBio/purell v1.1.1
github.com/alecthomas/chroma/v2 v2.7.0
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
@@ -25,7 +25,7 @@ require (
github.com/cli/safeexec v1.0.1
github.com/disintegration/gift v1.2.1
github.com/dustin/go-humanize v1.0.1
github.com/evanw/esbuild v0.18.10
github.com/evanw/esbuild v0.18.11
github.com/fatih/color v1.15.0
github.com/fortytw2/leaktest v1.3.0
github.com/frankban/quicktest v1.14.5
@@ -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
+4 -6
View File
@@ -126,8 +126,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
github.com/alecthomas/chroma/v2 v2.7.0 h1:hm1rY6c/Ob4eGclpQ7X/A3yhqBOZNUTk9q+yhyLIViI=
github.com/alecthomas/chroma/v2 v2.7.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
github.com/alecthomas/chroma/v2 v2.8.0 h1:w9WJUjFFmHHB2e8mRpL9jjy3alYDlU0QLDezj1xE264=
github.com/alecthomas/chroma/v2 v2.8.0/go.mod h1:yrkMI9807G1ROx13fhe1v6PN2DDeaR73L3d+1nmYQtw=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
@@ -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=
@@ -260,8 +258,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanw/esbuild v0.18.10 h1:QPpRUR5aOMQ4LQQDA9zZFpSuyT8+luBoyvPqVy8Vuv0=
github.com/evanw/esbuild v0.18.10/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
github.com/evanw/esbuild v0.18.11 h1:Mb0qb9KyJQraob0Y7LA0DOdD8ijJs2TxhqgqrDeGq4w=
github.com/evanw/esbuild v0.18.11/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
+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")
}
+8 -4
View File
@@ -77,6 +77,8 @@ type HugoSites struct {
contentInit sync.Once
content *pageMaps
postRenderInit sync.Once
// Keeps track of bundle directories and symlinks to enable partial rebuilding.
ContentChanges *contentChangeMap
@@ -435,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
}
@@ -553,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
}
}
@@ -568,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()
@@ -579,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 != "" {
+32 -13
View File
@@ -25,6 +25,7 @@ import (
"github.com/bep/logg"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/publisher"
"github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/hugofs"
@@ -144,18 +145,8 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
h.SendError(fmt.Errorf("render: %w", err))
}
if h.Configs.Base.LogPathWarnings {
// We need to do this before any post processing, as that may write to the same files twice
// and create false positives.
hugofs.WalkFilesystems(h.Fs.PublishDir, func(fs afero.Fs) bool {
if dfs, ok := fs.(hugofs.DuplicatesReporter); ok {
dupes := dfs.ReportDuplicates()
if dupes != "" {
h.Log.Warnln("Duplicate target paths:", dupes)
}
}
return false
})
if err := h.postRenderOnce(); err != nil {
h.SendError(fmt.Errorf("postRenderOnce: %w", err))
}
if err := h.postProcess(infol); err != nil {
@@ -329,6 +320,34 @@ func (h *HugoSites) render(l logg.LevelLogger, config *BuildCfg) error {
return nil
}
func (h *HugoSites) postRenderOnce() error {
h.postRenderInit.Do(func() {
conf := h.Configs.Base
if conf.PrintPathWarnings {
// We need to do this before any post processing, as that may write to the same files twice
// and create false positives.
hugofs.WalkFilesystems(h.Fs.PublishDir, func(fs afero.Fs) bool {
if dfs, ok := fs.(hugofs.DuplicatesReporter); ok {
dupes := dfs.ReportDuplicates()
if dupes != "" {
h.Log.Warnln("Duplicate target paths:", dupes)
}
}
return false
})
}
if conf.PrintUnusedTemplates {
unusedTemplates := h.Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
for _, unusedTemplate := range unusedTemplates {
h.Log.Warnf("Template %s is unused, source file %s", unusedTemplate.Name(), unusedTemplate.Filename())
}
}
})
return nil
}
func (h *HugoSites) postProcess(l logg.LevelLogger) error {
defer h.timeTrack(l, time.Now(), "postProcess")
@@ -456,7 +475,7 @@ func (h *HugoSites) writeBuildStats() error {
if h.ResourceSpec == nil {
panic("h.ResourceSpec is nil")
}
if !h.ResourceSpec.BuildConfig().WriteStats {
if !h.ResourceSpec.BuildConfig().BuildStats.Enabled() {
return nil
}
+9
View File
@@ -147,6 +147,15 @@ func (s *IntegrationTestBuilder) AssertFileContent(filename string, matches ...s
if match == "" || strings.HasPrefix(match, "#") {
continue
}
var negate bool
if strings.HasPrefix(match, "! ") {
negate = true
match = strings.TrimPrefix(match, "! ")
}
if negate {
s.Assert(content, qt.Not(qt.Contains), match, qt.Commentf(m))
continue
}
s.Assert(content, qt.Contains, match, qt.Commentf(m))
}
}
+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
}
+85
View File
@@ -1162,6 +1162,91 @@ Some text.
}
}
func TestClassCollectorConfigWriteStats(t *testing.T) {
r := func(writeStatsConfig string) *IntegrationTestBuilder {
files := `
-- hugo.toml --
WRITE_STATS_CONFIG
-- layouts/_default/list.html --
<div id="myid" class="myclass">Foo</div>
`
files = strings.Replace(files, "WRITE_STATS_CONFIG", writeStatsConfig, 1)
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: true,
},
).Build()
return b
}
// Legacy config.
b := r(`
[build]
writeStats = true
`)
b.AssertFileContent("hugo_stats.json", "myclass", "div", "myid")
b = r(`
[build]
writeStats = false
`)
b.AssertDestinationExists("hugo_stats.json", false)
b = r(`
[build.buildStats]
enable = true
`)
b.AssertFileContent("hugo_stats.json", "myclass", "div", "myid")
b = r(`
[build.buildStats]
enable = true
disableids = true
`)
b.AssertFileContent("hugo_stats.json", "myclass", "div", "! myid")
b = r(`
[build.buildStats]
enable = true
disableclasses = true
`)
b.AssertFileContent("hugo_stats.json", "! myclass", "div", "myid")
b = r(`
[build.buildStats]
enable = true
disabletags = true
`)
b.AssertFileContent("hugo_stats.json", "myclass", "! div", "myid")
b = r(`
[build.buildStats]
enable = true
disabletags = true
disableclasses = true
`)
b.AssertFileContent("hugo_stats.json", "! myclass", "! div", "myid")
b = r(`
[build.buildStats]
enable = false
`)
b.AssertDestinationExists("hugo_stats.json", false)
}
func TestClassCollectorStress(t *testing.T) {
statsFilename := "hugo_stats.json"
defer os.Remove(statsFilename)
-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)
+6 -2
View File
@@ -1,7 +1,11 @@
# Release env.
# These will be replaced by script before release.
HUGORELEASER_TAG=v0.114.1
HUGORELEASER_COMMITISH=e9b716ad9869b79e7c374bbdae4daf5ee6406bd4
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 -1
View File
@@ -119,7 +119,7 @@ func (t Translator) initFuncs(bndl *i18n.Bundle) {
t.logger.Warnf("Failed to get translated string for language %q and ID %q: %s", currentLangStr, translationID, err)
}
if t.cfg.LogI18nWarnings() {
if t.cfg.PrintI18nWarnings() {
t.logger.Warnf("i18n|MISSING_TRANSLATION|%s|%s", currentLangStr, translationID)
}
+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:
+17 -5
View File
@@ -24,6 +24,7 @@ import (
"golang.org/x/net/html"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
)
@@ -46,8 +47,9 @@ var (
}
)
func newHTMLElementsCollector() *htmlElementsCollector {
func newHTMLElementsCollector(conf config.BuildStats) *htmlElementsCollector {
return &htmlElementsCollector{
conf: conf,
elementSet: make(map[string]bool),
}
}
@@ -93,6 +95,8 @@ type htmlElement struct {
}
type htmlElementsCollector struct {
conf config.BuildStats
// Contains the raw HTML string. We will get the same element
// several times, and want to avoid costly reparsing when this
// is used for aggregated data only.
@@ -113,7 +117,9 @@ func (c *htmlElementsCollector) getHTMLElements() HTMLElements {
for _, el := range c.elements {
classes = append(classes, el.Classes...)
ids = append(ids, el.IDs...)
tags = append(tags, el.Tag)
if !c.conf.DisableTags {
tags = append(tags, el.Tag)
}
}
classes = helpers.UniqueStringsSorted(classes)
@@ -246,7 +252,7 @@ func (w *htmlElementsCollectorWriter) lexElementInside(resolve htmlCollectorStat
}
// Parse each collected element.
el, err := parseHTMLElement(s)
el, err := w.parseHTMLElement(s)
if err != nil {
w.err = err
return resolve
@@ -363,7 +369,8 @@ func htmlLexToEndOfComment(w *htmlElementsCollectorWriter) htmlCollectorStateFun
return htmlLexToEndOfComment
}
func parseHTMLElement(elStr string) (el htmlElement, err error) {
func (w *htmlElementsCollectorWriter) parseHTMLElement(elStr string) (el htmlElement, err error) {
conf := w.collector.conf
tagName := parseStartTag(elStr)
@@ -390,8 +397,13 @@ func parseHTMLElement(elStr string) (el htmlElement, err error) {
switch {
case strings.EqualFold(a.Key, "id"):
// There should be only one, but one never knows...
el.IDs = append(el.IDs, a.Val)
if !conf.DisableIDs {
el.IDs = append(el.IDs, a.Val)
}
default:
if conf.DisableClasses {
continue
}
if classAttrRe.MatchString(a.Key) {
el.Classes = append(el.Classes, strings.Fields(a.Val)...)
} else {
+10 -3
View File
@@ -22,6 +22,7 @@ import (
"testing"
"time"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/testconfig"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/minifiers"
@@ -136,7 +137,9 @@ func TestClassCollector(t *testing.T) {
} {
c.Run(fmt.Sprintf("%s--minify-%t", test.name, variant.minify), func(c *qt.C) {
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector())
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector(
config.BuildStats{Enable: true},
))
if variant.minify {
if skipMinifyTest[test.name] {
c.Skip("skip minify test")
@@ -240,7 +243,9 @@ func BenchmarkElementsCollectorWriter(b *testing.B) {
</html>
`
for i := 0; i < b.N; i++ {
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector())
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector(
config.BuildStats{Enable: true},
))
fmt.Fprint(w, benchHTML)
}
@@ -262,7 +267,9 @@ func BenchmarkElementsCollectorWriterPre(b *testing.B) {
<div class="foo"></div>
`
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector())
w := newHTMLElementsCollectorWriter(newHTMLElementsCollector(
config.BuildStats{Enable: true},
))
for i := 0; i < b.N; i++ {
fmt.Fprint(w, benchHTML)
+2 -2
View File
@@ -81,8 +81,8 @@ func NewDestinationPublisher(rs *resources.Spec, outputFormats output.Formats, m
fs := rs.BaseFs.PublishFs
cfg := rs.Cfg
var classCollector *htmlElementsCollector
if rs.BuildConfig().WriteStats {
classCollector = newHTMLElementsCollector()
if rs.BuildConfig().BuildStats.Enabled() {
classCollector = newHTMLElementsCollector(rs.BuildConfig().BuildStats)
}
pub = DestinationPublisher{fs: fs, htmlElementsCollector: classCollector}
pub.min, err = minifiers.New(mediaTypes, outputFormats, cfg)
@@ -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

+54 -20
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
@@ -41,6 +70,17 @@ environment:
pandoc_datadir: $SNAP/usr/share/pandoc
PYTHONHOME: /usr:$SNAP/usr
RUBYLIB: $SNAP/usr/lib/ruby/vendor_ruby/2.7.0:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/ruby/vendor_ruby/2.7.0:$SNAP/usr/lib/ruby/vendor_ruby:$SNAP/usr/lib/ruby/2.7.0:$SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/ruby/2.7.0
# HUGO_SECURITY_EXEC_OSENV
#
# Default value:
# (?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+|(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:
@@ -52,6 +92,9 @@ apps:
- removable-media
- etc-gitconfig
- gitconfig
- dot-aws
- dot-azure
- dot-config-gcloud
parts:
git:
@@ -92,15 +135,6 @@ parts:
export GOPATH=$(realpath ../go)
export PATH=$GOPATH/bin:$PATH
echo " * Patch securityConfig.go to allow"
echo " - GIT_EXEC_PATH and LD_LIBRARY_PATH to be passed to git"
echo " - npm_config_{cache,init_module,userconfig} to be passed to npx"
echo " - pandoc_datadir to be passed to pandoc"
echo " - PYTHONHOME and SNAP to be passed to rst2html"
echo " - RUBYLIB to be passed to asciidoctor"
sed -i '/OsEnv: NewWhitelist/s/)\$/|GIT_EXEC_PATH|LD_LIBRARY_PATH|npm_config_(cache|init_module|userconfig)|pandoc_datadir|PYTHONHOME|RUBYLIB|SNAP&/' config/security/securityConfig.go
git diff config/security/securityConfig.go
HUGO_BUILD_TAGS="extended"
echo " * Building hugo (HUGO_BUILD_TAGS=\"$HUGO_BUILD_TAGS\")..."
go build -v -ldflags "-X github.com/gohugoio/hugo/common/hugo.vendorInfo=snap:$(git describe --tags --always --match 'v[0-9]*' | sed 's/^v//; s/-/+git/; s/-g/./')" -tags "$HUGO_BUILD_TAGS"
+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|
`)
}