mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-26 00:08:53 +00:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 10ddc2ef92 | |||
| c406fd3a0e | |||
| 286821e360 | |||
| 79f15be5b0 | |||
| 5c2e014a51 | |||
| cc44583cc3 | |||
| 4da672af88 | |||
| f1886f8c37 | |||
| 5bec50838c | |||
| f650e4d751 | |||
| c934a45069 | |||
| 91b02091a5 | |||
| 8966424e0e | |||
| 72510969a6 | |||
| 70c5e485bb | |||
| a78b17d7f1 | |||
| a481942532 | |||
| 0f921ace6f | |||
| 6019953769 | |||
| 92e86702ea | |||
| 6c9ea022a9 | |||
| 12d3469dd1 | |||
| bf7ee8a91a | |||
| d912491f2d | |||
| 857374e693 | |||
| c27639b9a0 | |||
| ceb486f98c | |||
| 5afc89f2bf | |||
| c1eac616d5 | |||
| 3c8256a137 | |||
| 19d76ae96b | |||
| 11ecea6106 | |||
| da98724bc8 | |||
| 4d470bb732 | |||
| 0ff8e13c1c | |||
| ffd37d4f75 | |||
| b4b65245b2 | |||
| 87886f40d1 |
@@ -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
|
||||
|
||||
@@ -63,7 +63,7 @@ And with [Hugo Modules], you can share content, assets, data, translations, them
|
||||
<p> </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>
|
||||
<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> </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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -59,11 +59,34 @@ func GetGID() uint64 {
|
||||
return n
|
||||
}
|
||||
|
||||
// IsFeatureNotAvailableError returns true if the given error is or contains a FeatureNotAvailableError.
|
||||
func IsFeatureNotAvailableError(err error) bool {
|
||||
return errors.Is(err, &FeatureNotAvailableError{})
|
||||
}
|
||||
|
||||
// ErrFeatureNotAvailable denotes that a feature is unavailable.
|
||||
//
|
||||
// We will, at least to begin with, make some Hugo features (SCSS with libsass) optional,
|
||||
// and this error is used to signal those situations.
|
||||
var ErrFeatureNotAvailable = errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information")
|
||||
var ErrFeatureNotAvailable = &FeatureNotAvailableError{Cause: errors.New("this feature is not available in your current Hugo version, see https://goo.gl/YMrWcn for more information")}
|
||||
|
||||
// FeatureNotAvailableError is an error type used to signal that a feature is not available.
|
||||
type FeatureNotAvailableError struct {
|
||||
Cause error
|
||||
}
|
||||
|
||||
func (e *FeatureNotAvailableError) Unwrap() error {
|
||||
return e.Cause
|
||||
}
|
||||
|
||||
func (e *FeatureNotAvailableError) Error() string {
|
||||
return e.Cause.Error()
|
||||
}
|
||||
|
||||
func (e *FeatureNotAvailableError) Is(target error) bool {
|
||||
_, ok := target.(*FeatureNotAvailableError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Must panics if err != nil.
|
||||
func Must(err error) {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package herrors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
@@ -34,3 +35,12 @@ func TestIsNotExist(t *testing.T) {
|
||||
// os.IsNotExist returns false for wrapped errors.
|
||||
c.Assert(IsNotExist(fmt.Errorf("foo: %w", afero.ErrFileNotFound)), qt.Equals, true)
|
||||
}
|
||||
|
||||
func TestIsFeatureNotAvailableError(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(IsFeatureNotAvailableError(ErrFeatureNotAvailable), qt.Equals, true)
|
||||
c.Assert(IsFeatureNotAvailableError(&FeatureNotAvailableError{}), qt.Equals, true)
|
||||
c.Assert(IsFeatureNotAvailableError(errors.New("asdf")), qt.Equals, false)
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bep/clock"
|
||||
"github.com/bep/clocks"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/gohugoio/locales"
|
||||
@@ -75,7 +75,7 @@ var (
|
||||
"December",
|
||||
}
|
||||
|
||||
Clock = clock.System()
|
||||
Clock = clocks.System()
|
||||
)
|
||||
|
||||
func NewTimeFormatter(ltr locales.Translator) TimeFormatter {
|
||||
|
||||
@@ -17,7 +17,7 @@ package hugo
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 115,
|
||||
Minor: 116,
|
||||
PatchLevel: 0,
|
||||
Suffix: "",
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
@@ -274,6 +274,14 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
}
|
||||
disabledLangs[lang] = true
|
||||
}
|
||||
for lang, language := range c.Languages {
|
||||
if language.Disabled {
|
||||
disabledLangs[lang] = true
|
||||
if lang == c.DefaultContentLanguage {
|
||||
return fmt.Errorf("cannot disable default content language %q", lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ignoredErrors := make(map[string]bool)
|
||||
for _, err := range c.IgnoreErrors {
|
||||
@@ -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.
|
||||
|
||||
@@ -150,7 +150,7 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
key: "outputs",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
defaults := createDefaultOutputFormats(p.c.OutputFormats.Config)
|
||||
m := p.p.GetStringMap("outputs")
|
||||
m := maps.CleanConfigStringMap(p.p.GetStringMap("outputs"))
|
||||
p.c.Outputs = make(map[string][]string)
|
||||
for k, v := range m {
|
||||
s := types.ToStringSlicePreserveString(v)
|
||||
|
||||
@@ -41,6 +41,16 @@ func (c ConfigLanguage) LanguagesDefaultFirst() langs.Languages {
|
||||
return c.m.LanguagesDefaultFirst
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) LanguagePrefix() string {
|
||||
if c.DefaultContentLanguageInSubdir() && c.DefaultContentLanguage() == c.Language().Lang {
|
||||
return c.Language().Lang
|
||||
}
|
||||
if !c.IsMultiLingual() || c.DefaultContentLanguage() == c.Language().Lang {
|
||||
return ""
|
||||
}
|
||||
return c.Language().Lang
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) BaseURL() urls.BaseURL {
|
||||
return c.config.C.BaseURL
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -42,7 +42,7 @@ var DefaultConfig = Config{
|
||||
),
|
||||
// These have been tested to work with Hugo's external programs
|
||||
// on Windows, Linux and MacOS.
|
||||
OsEnv: MustNewWhitelist(`(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+)$`),
|
||||
OsEnv: MustNewWhitelist(`(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG)$`),
|
||||
},
|
||||
Funcs: Funcs{
|
||||
Getenv: MustNewWhitelist("^HUGO_", "^CI$"),
|
||||
|
||||
@@ -140,7 +140,7 @@ func TestToTOML(t *testing.T) {
|
||||
got := DefaultConfig.ToTOML()
|
||||
|
||||
c.Assert(got, qt.Equals,
|
||||
"[security]\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^npx$', '^postcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.goTemplates]\n AllowActionJSTmpl = false\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
|
||||
"[security]\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^npx$', '^postcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.goTemplates]\n AllowActionJSTmpl = false\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -154,9 +154,6 @@ func TestDecodeConfigDefault(t *testing.T) {
|
||||
c.Assert(pc.Exec.Allow.Accept("a"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.Allow.Accept("npx"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.Allow.Accept("Npx"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("a"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
||||
|
||||
c.Assert(pc.HTTP.URLs.Accept("https://example.org"), qt.IsTrue)
|
||||
c.Assert(pc.HTTP.Methods.Accept("POST"), qt.IsTrue)
|
||||
@@ -167,6 +164,10 @@ func TestDecodeConfigDefault(t *testing.T) {
|
||||
|
||||
c.Assert(pc.Exec.OsEnv.Accept("PATH"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("GOROOT"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("HOME"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("SSH_AUTH_SOCK"), qt.IsTrue)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("a"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("e"), qt.IsFalse)
|
||||
c.Assert(pc.Exec.OsEnv.Accept("MYSECRET"), qt.IsFalse)
|
||||
|
||||
}
|
||||
|
||||
@@ -623,7 +623,13 @@ status = 404
|
||||
|
||||
Set `titleCaseStyle` to specify the title style used by the [title](/functions/title/) template function and the automatic section titles in Hugo.
|
||||
|
||||
By default, Hugo adheres to the capitalization rules in the [Associated Press (AP) Stylebook]. Set `titleCaseStyle` to `chicago` if you would prefer to follow the [Chicago Manual of Style], or set if to `go` to use Go's convention of capitalizing every word.
|
||||
Can be one of:
|
||||
|
||||
* `ap` (default), the capitalization rules in the [Associated Press (AP) Stylebook]
|
||||
* `chicago`, the [Chicago Manual of Style]
|
||||
* `go`, Go's convention of capitalizing every word.
|
||||
* `firstupper`, capitalize the first letter of the first word.
|
||||
* `none`, no capitalization.
|
||||
|
||||
[Associated Press (AP) Stylebook]: https://www.apstylebook.com/
|
||||
[Chicago Manual of Style]: https://www.chicagomanualofstyle.org/home.html
|
||||
|
||||
+45
-2
@@ -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,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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -205,6 +205,8 @@ func ReaderContains(r io.Reader, subslice []byte) bool {
|
||||
// - "Go" (strings.Title)
|
||||
// - "AP" (see https://www.apstylebook.com/)
|
||||
// - "Chicago" (see http://www.chicagomanualofstyle.org/home.html)
|
||||
// - "FirstUpper" (only the first character is upper case)
|
||||
// - "None" (no transformation)
|
||||
//
|
||||
// If an unknown or empty style is provided, AP style is what you get.
|
||||
func GetTitleFunc(style string) func(s string) string {
|
||||
@@ -214,6 +216,10 @@ func GetTitleFunc(style string) func(s string) string {
|
||||
case "chicago":
|
||||
tc := transform.NewTitleConverter(transform.ChicagoStyle)
|
||||
return tc.Title
|
||||
case "none":
|
||||
return func(s string) string { return s }
|
||||
case "firstupper":
|
||||
return FirstUpper
|
||||
default:
|
||||
tc := transform.NewTitleConverter(transform.APStyle)
|
||||
return tc.Title
|
||||
|
||||
@@ -203,7 +203,7 @@ func TestReaderContains(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetTitleFunc(t *testing.T) {
|
||||
title := "somewhere over the rainbow"
|
||||
title := "somewhere over the Rainbow"
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(helpers.GetTitleFunc("go")(title), qt.Equals, "Somewhere Over The Rainbow")
|
||||
@@ -213,6 +213,8 @@ func TestGetTitleFunc(t *testing.T) {
|
||||
c.Assert(helpers.GetTitleFunc("ap")(title), qt.Equals, "Somewhere Over the Rainbow")
|
||||
c.Assert(helpers.GetTitleFunc("")(title), qt.Equals, "Somewhere Over the Rainbow")
|
||||
c.Assert(helpers.GetTitleFunc("unknown")(title), qt.Equals, "Somewhere Over the Rainbow")
|
||||
c.Assert(helpers.GetTitleFunc("none")(title), qt.Equals, title)
|
||||
c.Assert(helpers.GetTitleFunc("firstupper")(title), qt.Equals, "Somewhere over the Rainbow")
|
||||
}
|
||||
|
||||
func BenchmarkReaderContains(b *testing.B) {
|
||||
|
||||
+39
-18
@@ -109,8 +109,16 @@ func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
|
||||
"weight": 10,
|
||||
},
|
||||
})
|
||||
v.Set("defaultContentLanguage", "en")
|
||||
} else {
|
||||
v.Set("defaultContentLanguage", lang)
|
||||
v.Set("languages", map[string]any{
|
||||
lang: map[string]interface{}{
|
||||
"weight": 10,
|
||||
},
|
||||
})
|
||||
}
|
||||
v.Set("defaultContentLanguage", "en")
|
||||
|
||||
v.Set("defaultContentLanguageInSubdir", defaultInSubDir)
|
||||
v.Set("baseURL", test.baseURL)
|
||||
|
||||
@@ -118,16 +126,21 @@ func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
|
||||
if multilingual {
|
||||
configLang = lang
|
||||
}
|
||||
defaultContentLanguage := lang
|
||||
if multilingual {
|
||||
defaultContentLanguage = "en"
|
||||
}
|
||||
|
||||
p := newTestPathSpecFromCfgAndLang(v, configLang)
|
||||
|
||||
output := p.AbsURL(test.input, addLanguage)
|
||||
expected := test.expected
|
||||
if multilingual && addLanguage {
|
||||
if !defaultInSubDir && lang == "en" {
|
||||
expected = strings.Replace(expected, "MULTI", "", 1)
|
||||
} else {
|
||||
expected = strings.Replace(expected, "MULTI", lang+"/", 1)
|
||||
}
|
||||
if addLanguage {
|
||||
addLanguage = defaultInSubDir && lang == defaultContentLanguage
|
||||
addLanguage = addLanguage || (lang != defaultContentLanguage && multilingual)
|
||||
}
|
||||
if addLanguage {
|
||||
expected = strings.Replace(expected, "MULTI", lang+"/", 1)
|
||||
} else {
|
||||
expected = strings.Replace(expected, "MULTI", "", 1)
|
||||
}
|
||||
@@ -162,8 +175,16 @@ func doTestRelURL(t testing.TB, defaultInSubDir, addLanguage, multilingual bool,
|
||||
"weight": 10,
|
||||
},
|
||||
})
|
||||
v.Set("defaultContentLanguage", "en")
|
||||
} else {
|
||||
v.Set("defaultContentLanguage", lang)
|
||||
v.Set("languages", map[string]any{
|
||||
lang: map[string]interface{}{
|
||||
"weight": 10,
|
||||
},
|
||||
})
|
||||
}
|
||||
v.Set("defaultContentLanguage", "en")
|
||||
|
||||
v.Set("defaultContentLanguageInSubdir", defaultInSubDir)
|
||||
|
||||
tests := []struct {
|
||||
@@ -209,25 +230,25 @@ func doTestRelURL(t testing.TB, defaultInSubDir, addLanguage, multilingual bool,
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
c.Run(fmt.Sprintf("%v/%t%t%t/%s", test, defaultInSubDir, addLanguage, multilingual, lang), func(c *qt.C) {
|
||||
c.Run(fmt.Sprintf("%v/defaultInSubDir=%t;addLanguage=%t;multilingual=%t/%s", test, defaultInSubDir, addLanguage, multilingual, lang), func(c *qt.C) {
|
||||
|
||||
v.Set("baseURL", test.baseURL)
|
||||
v.Set("canonifyURLs", test.canonify)
|
||||
var configLang string
|
||||
defaultContentLanguage := lang
|
||||
if multilingual {
|
||||
configLang = lang
|
||||
defaultContentLanguage = "en"
|
||||
}
|
||||
p := newTestPathSpecFromCfgAndLang(v, configLang)
|
||||
p := newTestPathSpecFromCfgAndLang(v, lang)
|
||||
|
||||
output := p.RelURL(test.input, addLanguage)
|
||||
|
||||
expected := test.expected
|
||||
if multilingual && addLanguage {
|
||||
if !defaultInSubDir && lang == "en" {
|
||||
expected = strings.Replace(expected, "MULTI", "", 1)
|
||||
} else {
|
||||
expected = strings.Replace(expected, "MULTI", "/"+lang, 1)
|
||||
}
|
||||
if addLanguage {
|
||||
addLanguage = defaultInSubDir && lang == defaultContentLanguage
|
||||
addLanguage = addLanguage || (lang != defaultContentLanguage && multilingual)
|
||||
}
|
||||
if addLanguage {
|
||||
expected = strings.Replace(expected, "MULTI", "/"+lang, 1)
|
||||
} else {
|
||||
expected = strings.Replace(expected, "MULTI", "", 1)
|
||||
}
|
||||
|
||||
@@ -1295,3 +1295,242 @@ Home.
|
||||
b.Assert(b.H.Configs.LanguageConfigSlice[0].Module.Mounts, qt.HasLen, 7)
|
||||
|
||||
}
|
||||
|
||||
func TestDefaultContentLanguageInSubdirOnlyOneLanguage(t *testing.T) {
|
||||
|
||||
t.Run("One language, default in sub dir", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
disableKinds = ["taxonomy", "term", "page", "section"]
|
||||
-- content/foo/bar.txt --
|
||||
Foo.
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
`
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/en/index.html", "Home.")
|
||||
b.AssertFileContent("public/en/foo/bar.txt", "Foo.")
|
||||
b.AssertFileContent("public/index.html", "refresh")
|
||||
b.AssertFileContent("public/sitemap.xml", "sitemapindex")
|
||||
b.AssertFileContent("public/en/sitemap.xml", "urlset")
|
||||
})
|
||||
|
||||
t.Run("Two languages, default in sub dir", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
disableKinds = ["taxonomy", "term", "page", "section"]
|
||||
[languages]
|
||||
[languages.en]
|
||||
title = "English Title"
|
||||
[languages.sv]
|
||||
title = "Swedish Title"
|
||||
-- content/foo/bar.txt --
|
||||
Foo.
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
`
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/en/index.html", "Home.")
|
||||
b.AssertFileContent("public/en/foo/bar.txt", "Foo.")
|
||||
b.AssertFileContent("public/index.html", "refresh")
|
||||
b.AssertFileContent("public/sitemap.xml", "sitemapindex")
|
||||
b.AssertFileContent("public/en/sitemap.xml", "urlset")
|
||||
})
|
||||
|
||||
t.Run("Two languages, default in root", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = false
|
||||
disableKinds = ["taxonomy", "term", "page", "section"]
|
||||
[languages]
|
||||
[languages.en]
|
||||
title = "English Title"
|
||||
[languages.sv]
|
||||
title = "Swedish Title"
|
||||
-- content/foo/bar.txt --
|
||||
Foo.
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
`
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", "Home.")
|
||||
b.AssertFileContent("public/foo/bar.txt", "Foo.")
|
||||
b.AssertFileContent("public/sitemap.xml", "sitemapindex")
|
||||
b.AssertFileContent("public/en/sitemap.xml", "urlset")
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestLanguagesDisabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[languages]
|
||||
[languages.en]
|
||||
title = "English Title"
|
||||
[languages.sv]
|
||||
title = "Swedish Title"
|
||||
disabled = true
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
|
||||
|
||||
`
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.Assert(len(b.H.Sites), qt.Equals, 1)
|
||||
|
||||
}
|
||||
|
||||
func TestLoadConfigYamlEnvVar(t *testing.T) {
|
||||
|
||||
defaultEnv := []string{`HUGO_OUTPUTS=home: ['json']`}
|
||||
|
||||
runVariant := func(t testing.TB, files string, env []string) *IntegrationTestBuilder {
|
||||
if env == nil {
|
||||
env = defaultEnv
|
||||
}
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
Environ: env,
|
||||
BuildCfg: BuildCfg{SkipRender: true},
|
||||
},
|
||||
).Build()
|
||||
|
||||
outputs := b.H.Configs.Base.Outputs
|
||||
if env == nil {
|
||||
home := outputs["home"]
|
||||
b.Assert(home, qt.Not(qt.IsNil))
|
||||
b.Assert(home, qt.DeepEquals, []string{"json"})
|
||||
}
|
||||
|
||||
return b
|
||||
|
||||
}
|
||||
|
||||
t.Run("with empty slice", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
|
||||
[outputs]
|
||||
home = ["html"]
|
||||
|
||||
`
|
||||
b := runVariant(t, files, []string{`HUGO_OUTPUTS=section: []`})
|
||||
outputs := b.H.Configs.Base.Outputs
|
||||
b.Assert(outputs, qt.DeepEquals, map[string][]string{
|
||||
"home": {"html"},
|
||||
"page": {"html"},
|
||||
"rss": {"rss"},
|
||||
"section": nil,
|
||||
"taxonomy": {"html", "rss"},
|
||||
"term": {"html", "rss"},
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
t.Run("with existing outputs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
|
||||
[outputs]
|
||||
home = ["html"]
|
||||
|
||||
`
|
||||
|
||||
runVariant(t, files, nil)
|
||||
|
||||
})
|
||||
|
||||
{
|
||||
t.Run("with existing outputs direct", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
|
||||
[outputs]
|
||||
home = ["html"]
|
||||
|
||||
`
|
||||
runVariant(t, files, []string{"HUGO_OUTPUTS_HOME=json"})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("without existing outputs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
|
||||
|
||||
`
|
||||
|
||||
runVariant(t, files, nil)
|
||||
|
||||
})
|
||||
|
||||
t.Run("without existing outputs direct", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
|
||||
`
|
||||
|
||||
runVariant(t, files, []string{"HUGO_OUTPUTS_HOME=json"})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@@ -1178,3 +1178,32 @@ target = "content/resources-b"
|
||||
b.AssertFileContent("public/resources-a/subdir/about/index.html", "Single")
|
||||
b.AssertFileContent("public/resources-b/subdir/about/index.html", "Single")
|
||||
}
|
||||
|
||||
func TestMountData(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = 'https://example.org/'
|
||||
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
|
||||
|
||||
[[module.mounts]]
|
||||
source = "data"
|
||||
target = "data"
|
||||
|
||||
[[module.mounts]]
|
||||
source = "extra-data"
|
||||
target = "data/extra"
|
||||
-- extra-data/test.yaml --
|
||||
message: Hugo Rocks
|
||||
-- layouts/index.html --
|
||||
{{ site.Data.extra.test.message }}
|
||||
`
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", "Hugo Rocks")
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,6 +557,9 @@ func (p *pageContentOutput) RenderWithTemplateInfo(ctx context.Context, info tpl
|
||||
}
|
||||
|
||||
func (p *pageContentOutput) Render(ctx context.Context, layout ...string) (template.HTML, error) {
|
||||
if len(layout) == 0 {
|
||||
return "", errors.New("no layout given")
|
||||
}
|
||||
templ, found, err := p.p.resolveTemplate(layout...)
|
||||
if err != nil {
|
||||
return "", p.p.wrapError(err)
|
||||
|
||||
+25
-3
@@ -22,7 +22,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bep/clock"
|
||||
"github.com/bep/clocks"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/markup/asciidocext"
|
||||
"github.com/gohugoio/hugo/markup/rst"
|
||||
@@ -1502,8 +1502,8 @@ func TestShouldBuild(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShouldBuildWithClock(t *testing.T) {
|
||||
htime.Clock = clock.Start(time.Date(2021, 11, 17, 20, 34, 58, 651387237, time.UTC))
|
||||
t.Cleanup(func() { htime.Clock = clock.System() })
|
||||
htime.Clock = clocks.Start(time.Date(2021, 11, 17, 20, 34, 58, 651387237, time.UTC))
|
||||
t.Cleanup(func() { htime.Clock = clocks.System() })
|
||||
past := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
|
||||
future := time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC)
|
||||
zero := time.Time{}
|
||||
@@ -1983,3 +1983,25 @@ title: "p2"
|
||||
b.Assert(identity.HashString(p1), qt.Not(qt.Equals), identity.HashString(p2))
|
||||
b.Assert(identity.HashString(sites[0]), qt.Not(qt.Equals), identity.HashString(sites[1]))
|
||||
}
|
||||
|
||||
// Issue #11243
|
||||
func TestRenderWithoutArgument(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
-- layouts/index.html --
|
||||
{{ .Render }}
|
||||
`
|
||||
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
Running: true,
|
||||
},
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ func TestPageBundlerSiteRegular(t *testing.T) {
|
||||
fs, cfg := newTestBundleSources(c)
|
||||
cfg.Set("baseURL", baseURL)
|
||||
cfg.Set("canonifyURLs", canonify)
|
||||
cfg.Set("defaultContentLanguageInSubdir", false)
|
||||
|
||||
cfg.Set("permalinks", map[string]string{
|
||||
"a": ":sections/:filename",
|
||||
|
||||
+2
-35
@@ -103,48 +103,15 @@ func (p *Paths) Lang() string {
|
||||
}
|
||||
|
||||
func (p *Paths) GetTargetLanguageBasePath() string {
|
||||
if len(p.Cfg.Languages()) > 1 {
|
||||
if p.Cfg.IsMultihost() {
|
||||
// In a multihost configuration all assets will be published below the language code.
|
||||
return p.Lang()
|
||||
}
|
||||
return p.GetLanguagePrefix()
|
||||
}
|
||||
|
||||
func (p *Paths) GetURLLanguageBasePath() string {
|
||||
if len(p.Cfg.Languages()) > 1 {
|
||||
return ""
|
||||
}
|
||||
return p.GetLanguagePrefix()
|
||||
}
|
||||
|
||||
func (p *Paths) GetLanguagePrefix() string {
|
||||
if len(p.Cfg.Languages()) < 2 {
|
||||
return ""
|
||||
}
|
||||
defaultLang := p.Cfg.DefaultContentLanguage()
|
||||
defaultInSubDir := p.Cfg.DefaultContentLanguageInSubdir()
|
||||
currentLang := p.Cfg.Language().Lang
|
||||
if currentLang == "" || (currentLang == defaultLang && !defaultInSubDir) {
|
||||
return ""
|
||||
}
|
||||
return currentLang
|
||||
}
|
||||
|
||||
// GetLangSubDir returns the given language's subdir if needed.
|
||||
func (p *Paths) GetLangSubDir(lang string) string {
|
||||
if len(p.Cfg.Languages()) < 2 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if p.Cfg.IsMultihost() {
|
||||
return ""
|
||||
}
|
||||
|
||||
if lang == "" || (lang == p.Cfg.DefaultContentLanguage() && !p.Cfg.DefaultContentLanguageInSubdir()) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return lang
|
||||
return p.Cfg.LanguagePrefix()
|
||||
}
|
||||
|
||||
// AbsPathify creates an absolute path if given a relative path. If already
|
||||
|
||||
+3
-8
@@ -887,21 +887,16 @@ func (s *Site) getLanguageTargetPathLang(alwaysInSubDir bool) string {
|
||||
|
||||
// get any language code to prefix the relative permalink with.
|
||||
func (s *Site) getLanguagePermalinkLang(alwaysInSubDir bool) string {
|
||||
if !s.h.isMultiLingual() || s.h.Conf.IsMultihost() {
|
||||
if s.h.Conf.IsMultihost() {
|
||||
return ""
|
||||
}
|
||||
|
||||
if alwaysInSubDir {
|
||||
if s.h.Conf.IsMultiLingual() && alwaysInSubDir {
|
||||
return s.Language().Lang
|
||||
}
|
||||
|
||||
isDefault := s.Language().Lang == s.conf.DefaultContentLanguage
|
||||
return s.GetLanguagePrefix()
|
||||
|
||||
if !isDefault || s.conf.DefaultContentLanguageInSubdir {
|
||||
return s.Language().Lang
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Site) getTaxonomyKey(key string) string {
|
||||
|
||||
+3
-8
@@ -470,16 +470,11 @@ func (s *Site) IsMultiLingual() bool {
|
||||
}
|
||||
|
||||
func (s *Site) LanguagePrefix() string {
|
||||
conf := s.s.Conf
|
||||
if !conf.IsMultiLingual() {
|
||||
prefix := s.GetLanguagePrefix()
|
||||
if prefix == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if !conf.DefaultContentLanguageInSubdir() && s.language.Lang == conf.DefaultContentLanguage() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return "/" + s.language.Lang
|
||||
return "/" + prefix
|
||||
}
|
||||
|
||||
// Returns the identity of this site.
|
||||
|
||||
@@ -384,7 +384,7 @@ func (s *Site) renderAliases() error {
|
||||
// renderMainLanguageRedirect creates a redirect to the main language home,
|
||||
// depending on if it lives in sub folder (e.g. /en) or not.
|
||||
func (s *Site) renderMainLanguageRedirect() error {
|
||||
if !s.h.isMultiLingual() || s.h.Conf.IsMultihost() {
|
||||
if s.h.Conf.IsMultihost() || !(s.h.Conf.DefaultContentLanguageInSubdir() || s.h.Conf.IsMultiLingual()) {
|
||||
// No need for a redirect
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -38,35 +38,6 @@ var urlFakeSource = [][2]string{
|
||||
{filepath.FromSlash("content/blue/doc2.md"), slugDoc2},
|
||||
}
|
||||
|
||||
func TestPageCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
cfg, fs := newTestCfg()
|
||||
cfg.Set("uglyURLs", false)
|
||||
cfg.Set("paginate", 10)
|
||||
configs, err := loadTestConfigFromProvider(cfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
writeSourcesToSource(t, "", fs, urlFakeSource...)
|
||||
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Configs: configs}, BuildCfg{})
|
||||
|
||||
_, err = s.Fs.WorkingDirReadOnly.Open("public/blue")
|
||||
if err != nil {
|
||||
t.Errorf("No indexed rendered.")
|
||||
}
|
||||
|
||||
for _, pth := range []string{
|
||||
"public/sd1/foo/index.html",
|
||||
"public/sd2/index.html",
|
||||
"public/sd3/index.html",
|
||||
"public/sd4.html",
|
||||
} {
|
||||
if _, err := s.Fs.WorkingDirReadOnly.Open(filepath.FromSlash(pth)); err != nil {
|
||||
t.Errorf("No alias rendered: %s", pth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUglyURLsPerSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ func doTestSitemapOutput(t *testing.T, internal bool) {
|
||||
c := qt.New(t)
|
||||
cfg, fs := newTestCfg()
|
||||
cfg.Set("baseURL", "http://auth/bub/")
|
||||
cfg.Set("defaultContentLanguageInSubdir", false)
|
||||
configs, err := loadTestConfigFromProvider(cfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
writeSource(t, fs, "layouts/sitemap.xml", sitemapTemplate)
|
||||
|
||||
@@ -937,8 +937,7 @@ func newTestCfgBasic() (config.Provider, *hugofs.Fs) {
|
||||
func newTestCfg(withConfig ...func(cfg config.Provider) error) (config.Provider, *hugofs.Fs) {
|
||||
mm := afero.NewMemMapFs()
|
||||
cfg := config.New()
|
||||
// Default is false, but true is easier to use as default in tests
|
||||
cfg.Set("defaultContentLanguageInSubdir", true)
|
||||
cfg.Set("defaultContentLanguageInSubdir", false)
|
||||
cfg.Set("publishDir", "public")
|
||||
|
||||
fs := hugofs.NewFromOld(hugofs.NewBaseFileDecorator(mm), cfg)
|
||||
|
||||
+6
-2
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ func TestUnfinished(t *testing.T) {
|
||||
|
||||
p := commonTestScriptsParam
|
||||
p.Dir = "testscripts/unfinished"
|
||||
//p.UpdateScripts = true
|
||||
|
||||
testscript.Run(t, p)
|
||||
}
|
||||
|
||||
+3
-1
@@ -292,8 +292,10 @@ func (c *Client) Vendor() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Also include any theme.toml or config.* files in the root.
|
||||
// Also include any theme.toml or config.* or hugo.* files in the root.
|
||||
configFiles, _ := afero.Glob(c.fs, filepath.Join(dir, "config.*"))
|
||||
configFiles2, _ := afero.Glob(c.fs, filepath.Join(dir, "hugo.*"))
|
||||
configFiles = append(configFiles, configFiles2...)
|
||||
configFiles = append(configFiles, filepath.Join(dir, "theme.toml"))
|
||||
for _, configFile := range configFiles {
|
||||
if err := hugio.CopyFile(c.fs, configFile, filepath.Join(vendorDir, t.Path(), filepath.Base(configFile))); err != nil {
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/niklasfasching/go-org/org"
|
||||
|
||||
xml "github.com/clbanning/mxj/v2"
|
||||
@@ -90,7 +91,7 @@ func (d Decoder) UnmarshalStringTo(data string, typ any) (any, error) {
|
||||
switch typ.(type) {
|
||||
case string:
|
||||
return data, nil
|
||||
case map[string]any:
|
||||
case map[string]any, maps.Params:
|
||||
format := d.FormatFromContentString(data)
|
||||
return d.UnmarshalToMap([]byte(data), format)
|
||||
case []any:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
dostounix golden/vendor.txt
|
||||
|
||||
hugo mod vendor
|
||||
cmp _vendor/modules.txt golden/vendor.txt
|
||||
ls _vendor/github.com/gohugoio/hugo-mod-integrationtests/withconfigtoml
|
||||
stdout 'config.toml'
|
||||
ls _vendor/github.com/gohugoio/hugo-mod-integrationtests/withhugotoml
|
||||
stdout 'hugo.toml'
|
||||
|
||||
|
||||
-- hugo.toml --
|
||||
title = "Hugo Modules Test"
|
||||
[module]
|
||||
[[module.imports]]
|
||||
path="github.com/gohugoio/hugo-mod-integrationtests/withconfigtoml"
|
||||
[[module.imports]]
|
||||
path="github.com/gohugoio/hugo-mod-integrationtests/withhugotoml"
|
||||
-- go.mod --
|
||||
go 1.19
|
||||
|
||||
module github.com/gohugoio/testmod
|
||||
-- golden/vendor.txt --
|
||||
# github.com/gohugoio/hugo-mod-integrationtests/withconfigtoml v1.0.0
|
||||
# github.com/gohugoio/hugo-mod-integrationtests/withhugotoml v1.0.0
|
||||
@@ -16,6 +16,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math/rand"
|
||||
@@ -99,7 +100,7 @@ func (ns *Namespace) After(n any, l any) (any, error) {
|
||||
|
||||
// Delimit takes a given list l and returns a string delimited by sep.
|
||||
// If last is passed to the function, it will be used as the final delimiter.
|
||||
func (ns *Namespace) Delimit(l, sep any, last ...any) (template.HTML, error) {
|
||||
func (ns *Namespace) Delimit(ctx context.Context, l, sep any, last ...any) (template.HTML, error) {
|
||||
d, err := cast.ToStringE(sep)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -125,7 +126,7 @@ func (ns *Namespace) Delimit(l, sep any, last ...any) (template.HTML, error) {
|
||||
var str string
|
||||
switch lv.Kind() {
|
||||
case reflect.Map:
|
||||
sortSeq, err := ns.Sort(l)
|
||||
sortSeq, err := ns.Sort(ctx, l)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
@@ -166,9 +167,9 @@ func TestDelimit(t *testing.T) {
|
||||
var err error
|
||||
|
||||
if test.last == nil {
|
||||
result, err = ns.Delimit(test.seq, test.delimiter)
|
||||
result, err = ns.Delimit(context.Background(), test.seq, test.delimiter)
|
||||
} else {
|
||||
result, err = ns.Delimit(test.seq, test.delimiter, test.last)
|
||||
result, err = ns.Delimit(context.Background(), test.seq, test.delimiter, test.last)
|
||||
}
|
||||
|
||||
c.Assert(err, qt.IsNil, errMsg)
|
||||
|
||||
@@ -155,3 +155,44 @@ func TestAppendNilsToSliceWithNils(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Issue 11234.
|
||||
func TestWhereWithWordCount(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
baseURL = 'http://example.com/'
|
||||
-- layouts/index.html --
|
||||
Home: {{ range where site.RegularPages "WordCount" "gt" 50 }}{{ .Title }}|{{ end }}
|
||||
-- layouts/shortcodes/lorem.html --
|
||||
{{ "ipsum " | strings.Repeat (.Get 0 | int) }}
|
||||
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
{{< lorem 100 >}}
|
||||
-- content/p2.md --
|
||||
---
|
||||
title: "p2"
|
||||
---
|
||||
{{< lorem 20 >}}
|
||||
-- content/p3.md --
|
||||
---
|
||||
title: "p3"
|
||||
---
|
||||
{{< lorem 60 >}}
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", `
|
||||
Home: p1|p3|
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"sort"
|
||||
@@ -26,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
// Sort returns a sorted copy of the list l.
|
||||
func (ns *Namespace) Sort(l any, args ...any) (any, error) {
|
||||
func (ns *Namespace) Sort(ctx context.Context, l any, args ...any) (any, error) {
|
||||
if l == nil {
|
||||
return nil, errors.New("sequence must be provided")
|
||||
}
|
||||
@@ -36,6 +37,8 @@ func (ns *Namespace) Sort(l any, args ...any) (any, error) {
|
||||
return nil, errors.New("can't iterate over a nil value")
|
||||
}
|
||||
|
||||
ctxv := reflect.ValueOf(ctx)
|
||||
|
||||
var sliceType reflect.Type
|
||||
switch seqv.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
@@ -78,7 +81,7 @@ func (ns *Namespace) Sort(l any, args ...any) (any, error) {
|
||||
v := p.Pairs[i].Value
|
||||
var err error
|
||||
for i, elemName := range path {
|
||||
v, err = evaluateSubElem(v, elemName)
|
||||
v, err = evaluateSubElem(ctxv, v, elemName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -108,7 +111,7 @@ func (ns *Namespace) Sort(l any, args ...any) (any, error) {
|
||||
v := p.Pairs[i].Value
|
||||
var err error
|
||||
for i, elemName := range path {
|
||||
v, err = evaluateSubElem(v, elemName)
|
||||
v, err = evaluateSubElem(ctxv, v, elemName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -240,9 +241,9 @@ func TestSort(t *testing.T) {
|
||||
var result any
|
||||
var err error
|
||||
if test.sortByField == nil {
|
||||
result, err = ns.Sort(test.seq)
|
||||
result, err = ns.Sort(context.Background(), test.seq)
|
||||
} else {
|
||||
result, err = ns.Sort(test.seq, test.sortByField, test.sortAsc)
|
||||
result, err = ns.Sort(context.Background(), test.seq, test.sortByField, test.sortAsc)
|
||||
}
|
||||
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
|
||||
+23
-12
@@ -14,6 +14,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -24,7 +25,7 @@ import (
|
||||
)
|
||||
|
||||
// Where returns a filtered subset of collection c.
|
||||
func (ns *Namespace) Where(c, key any, args ...any) (any, error) {
|
||||
func (ns *Namespace) Where(ctx context.Context, c, key any, args ...any) (any, error) {
|
||||
seqv, isNil := indirect(reflect.ValueOf(c))
|
||||
if isNil {
|
||||
return nil, errors.New("can't iterate over a nil value of type " + reflect.ValueOf(c).Type().String())
|
||||
@@ -35,6 +36,8 @@ func (ns *Namespace) Where(c, key any, args ...any) (any, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctxv := reflect.ValueOf(ctx)
|
||||
|
||||
var path []string
|
||||
kv := reflect.ValueOf(key)
|
||||
if kv.Kind() == reflect.String {
|
||||
@@ -43,9 +46,9 @@ func (ns *Namespace) Where(c, key any, args ...any) (any, error) {
|
||||
|
||||
switch seqv.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
return ns.checkWhereArray(seqv, kv, mv, path, op)
|
||||
return ns.checkWhereArray(ctxv, seqv, kv, mv, path, op)
|
||||
case reflect.Map:
|
||||
return ns.checkWhereMap(seqv, kv, mv, path, op)
|
||||
return ns.checkWhereMap(ctxv, seqv, kv, mv, path, op)
|
||||
default:
|
||||
return nil, fmt.Errorf("can't iterate over %v", c)
|
||||
}
|
||||
@@ -275,7 +278,7 @@ func (ns *Namespace) checkCondition(v, mv reflect.Value, op string) (bool, error
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error) {
|
||||
func evaluateSubElem(ctx, obj reflect.Value, elemName string) (reflect.Value, error) {
|
||||
if !obj.IsValid() {
|
||||
return zero, errors.New("can't evaluate an invalid value")
|
||||
}
|
||||
@@ -301,12 +304,20 @@ func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error)
|
||||
|
||||
index := hreflect.GetMethodIndexByName(objPtr.Type(), elemName)
|
||||
if index != -1 {
|
||||
var args []reflect.Value
|
||||
mt := objPtr.Type().Method(index)
|
||||
num := mt.Type.NumIn()
|
||||
maxNumIn := 1
|
||||
if num > 1 && mt.Type.In(1).Implements(hreflect.ContextInterface) {
|
||||
args = []reflect.Value{ctx}
|
||||
maxNumIn = 2
|
||||
}
|
||||
|
||||
switch {
|
||||
case mt.PkgPath != "":
|
||||
return zero, fmt.Errorf("%s is an unexported method of type %s", elemName, typ)
|
||||
case mt.Type.NumIn() > 1:
|
||||
return zero, fmt.Errorf("%s is a method of type %s but requires more than 1 parameter", elemName, typ)
|
||||
case mt.Type.NumIn() > maxNumIn:
|
||||
return zero, fmt.Errorf("%s is a method of type %s but requires more than %d parameter", elemName, typ, maxNumIn)
|
||||
case mt.Type.NumOut() == 0:
|
||||
return zero, fmt.Errorf("%s is a method of type %s but returns no output", elemName, typ)
|
||||
case mt.Type.NumOut() > 2:
|
||||
@@ -316,7 +327,7 @@ func evaluateSubElem(obj reflect.Value, elemName string) (reflect.Value, error)
|
||||
case mt.Type.NumOut() == 2 && !mt.Type.Out(1).Implements(errorType):
|
||||
return zero, fmt.Errorf("%s is a method of type %s returning two values but the second value is not an error type", elemName, typ)
|
||||
}
|
||||
res := objPtr.Method(mt.Index).Call([]reflect.Value{})
|
||||
res := objPtr.Method(mt.Index).Call(args)
|
||||
if len(res) == 2 && !res[1].IsNil() {
|
||||
return zero, fmt.Errorf("error at calling a method %s of type %s: %s", elemName, typ, res[1].Interface().(error))
|
||||
}
|
||||
@@ -371,7 +382,7 @@ func parseWhereArgs(args ...any) (mv reflect.Value, op string, err error) {
|
||||
|
||||
// checkWhereArray handles the where-matching logic when the seqv value is an
|
||||
// Array or Slice.
|
||||
func (ns *Namespace) checkWhereArray(seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
|
||||
func (ns *Namespace) checkWhereArray(ctxv, seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
|
||||
rv := reflect.MakeSlice(seqv.Type(), 0, 0)
|
||||
|
||||
for i := 0; i < seqv.Len(); i++ {
|
||||
@@ -385,7 +396,7 @@ func (ns *Namespace) checkWhereArray(seqv, kv, mv reflect.Value, path []string,
|
||||
vvv = rvv
|
||||
for i, elemName := range path {
|
||||
var err error
|
||||
vvv, err = evaluateSubElem(vvv, elemName)
|
||||
vvv, err = evaluateSubElem(ctxv, vvv, elemName)
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -417,14 +428,14 @@ func (ns *Namespace) checkWhereArray(seqv, kv, mv reflect.Value, path []string,
|
||||
}
|
||||
|
||||
// checkWhereMap handles the where-matching logic when the seqv value is a Map.
|
||||
func (ns *Namespace) checkWhereMap(seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
|
||||
func (ns *Namespace) checkWhereMap(ctxv, seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
|
||||
rv := reflect.MakeMap(seqv.Type())
|
||||
keys := seqv.MapKeys()
|
||||
for _, k := range keys {
|
||||
elemv := seqv.MapIndex(k)
|
||||
switch elemv.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
r, err := ns.checkWhereArray(elemv, kv, mv, path, op)
|
||||
r, err := ns.checkWhereArray(ctxv, elemv, kv, mv, path, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -443,7 +454,7 @@ func (ns *Namespace) checkWhereMap(seqv, kv, mv reflect.Value, path []string, op
|
||||
|
||||
switch elemvv.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
r, err := ns.checkWhereArray(elemvv, kv, mv, path, op)
|
||||
r, err := ns.checkWhereArray(ctxv, elemvv, kv, mv, path, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"reflect"
|
||||
@@ -641,9 +642,9 @@ func TestWhere(t *testing.T) {
|
||||
var err error
|
||||
|
||||
if len(test.op) > 0 {
|
||||
results, err = ns.Where(test.seq, test.key, test.op, test.match)
|
||||
results, err = ns.Where(context.Background(), test.seq, test.key, test.op, test.match)
|
||||
} else {
|
||||
results, err = ns.Where(test.seq, test.key, test.match)
|
||||
results, err = ns.Where(context.Background(), test.seq, test.key, test.match)
|
||||
}
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
if err == nil {
|
||||
@@ -662,17 +663,17 @@ func TestWhere(t *testing.T) {
|
||||
}
|
||||
|
||||
var err error
|
||||
_, err = ns.Where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1)
|
||||
_, err = ns.Where(context.Background(), map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1)
|
||||
if err == nil {
|
||||
t.Errorf("Where called with none string op value didn't return an expected error")
|
||||
}
|
||||
|
||||
_, err = ns.Where(map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1, 2)
|
||||
_, err = ns.Where(context.Background(), map[string]int{"a": 1, "b": 2}, "a", []byte("="), 1, 2)
|
||||
if err == nil {
|
||||
t.Errorf("Where called with more than two variable arguments didn't return an expected error")
|
||||
}
|
||||
|
||||
_, err = ns.Where(map[string]int{"a": 1, "b": 2}, "a")
|
||||
_, err = ns.Where(context.Background(), map[string]int{"a": 1, "b": 2}, "a")
|
||||
if err == nil {
|
||||
t.Errorf("Where called with no variable arguments didn't return an expected error")
|
||||
}
|
||||
@@ -842,7 +843,7 @@ func TestEvaluateSubElem(t *testing.T) {
|
||||
{reflect.ValueOf(map[int]string{1: "foo", 2: "bar"}), "1", false},
|
||||
{reflect.ValueOf([]string{"foo", "bar"}), "1", false},
|
||||
} {
|
||||
result, err := evaluateSubElem(test.value, test.key)
|
||||
result, err := evaluateSubElem(reflect.ValueOf(context.Background()), test.value, test.key)
|
||||
if b, ok := test.expect.(bool); ok && !b {
|
||||
if err == nil {
|
||||
t.Errorf("[%d] evaluateSubElem didn't return an expected error", i)
|
||||
|
||||
+23
-7
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/bep/overlayfs"
|
||||
"github.com/gohugoio/hugo/resources/images"
|
||||
|
||||
// Importing image codecs for image.DecodeConfig
|
||||
@@ -31,23 +32,38 @@ import (
|
||||
_ "golang.org/x/image/webp"
|
||||
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// New returns a new instance of the images-namespaced template functions.
|
||||
func New(deps *deps.Deps) *Namespace {
|
||||
func New(d *deps.Deps) *Namespace {
|
||||
var readFileFs afero.Fs
|
||||
|
||||
// The docshelper script does not have or need all the dependencies set up.
|
||||
if d.PathSpec != nil {
|
||||
readFileFs = overlayfs.New(overlayfs.Options{
|
||||
Fss: []afero.Fs{
|
||||
d.PathSpec.BaseFs.Work,
|
||||
d.PathSpec.BaseFs.Content.Fs,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return &Namespace{
|
||||
Filters: &images.Filters{},
|
||||
cache: map[string]image.Config{},
|
||||
deps: deps,
|
||||
readFileFs: readFileFs,
|
||||
Filters: &images.Filters{},
|
||||
cache: map[string]image.Config{},
|
||||
deps: d,
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace provides template functions for the "images" namespace.
|
||||
type Namespace struct {
|
||||
*images.Filters
|
||||
cacheMu sync.RWMutex
|
||||
cache map[string]image.Config
|
||||
readFileFs afero.Fs
|
||||
cacheMu sync.RWMutex
|
||||
cache map[string]image.Config
|
||||
|
||||
deps *deps.Deps
|
||||
}
|
||||
@@ -73,7 +89,7 @@ func (ns *Namespace) Config(path any) (image.Config, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
f, err := ns.deps.Fs.WorkingDirReadOnly.Open(filename)
|
||||
f, err := ns.readFileFs.Open(filename)
|
||||
if err != nil {
|
||||
return image.Config{}, err
|
||||
}
|
||||
|
||||
@@ -24,8 +24,6 @@ import (
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/testconfig"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
@@ -86,11 +84,10 @@ func TestNSConfig(t *testing.T) {
|
||||
afs := afero.NewMemMapFs()
|
||||
v := config.New()
|
||||
v.Set("workingDir", "/a/b")
|
||||
conf := testconfig.GetTestConfig(afs, v)
|
||||
bcfg := conf.BaseConfig()
|
||||
fs := hugofs.NewFrom(afs, bcfg)
|
||||
d := testconfig.GetTestDeps(afs, v)
|
||||
bcfg := d.Conf
|
||||
|
||||
ns := New(&deps.Deps{Fs: fs, Conf: conf})
|
||||
ns := New(d)
|
||||
|
||||
for _, test := range configTests {
|
||||
|
||||
@@ -104,7 +101,7 @@ func TestNSConfig(t *testing.T) {
|
||||
// cast path to string for afero.WriteFile
|
||||
sp, err := cast.ToStringE(test.path)
|
||||
c.Assert(err, qt.IsNil)
|
||||
afero.WriteFile(ns.deps.Fs.Source, filepath.Join(bcfg.WorkingDir, sp), test.input, 0755)
|
||||
afero.WriteFile(ns.deps.Fs.Source, filepath.Join(bcfg.WorkingDir(), sp), test.input, 0755)
|
||||
|
||||
result, err := ns.Config(test.path)
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2023 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package images_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestImageConfigFromModule(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = 'http://example.com/'
|
||||
theme = ["mytheme"]
|
||||
-- static/images/pixel1.png --
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
-- themes/mytheme/static/images/pixel2.png --
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
-- layouts/index.html --
|
||||
{{ $path := "static/images/pixel1.png" }}
|
||||
fileExists OK: {{ fileExists $path }}|
|
||||
imageConfig OK: {{ (imageConfig $path).Width }}|
|
||||
{{ $path2 := "static/images/pixel2.png" }}
|
||||
fileExists2 OK: {{ fileExists $path2 }}|
|
||||
imageConfig2 OK: {{ (imageConfig $path2).Width }}|
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.NewIntegrationTestBuilder(
|
||||
hugolib.IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", `
|
||||
fileExists OK: true|
|
||||
imageConfig OK: 1|
|
||||
fileExists2 OK: true|
|
||||
imageConfig2 OK: 1|
|
||||
`)
|
||||
}
|
||||
Reference in New Issue
Block a user