Compare commits

..

1 Commits

Author SHA1 Message Date
Bjørn Erik Pedersen 2a663b1938 Fix TailwindCSS related server rebuild issue
Fixes #13316
2025-01-29 15:48:19 +01:00
1946 changed files with 34395 additions and 26222 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ parameters:
defaults: &defaults
resource_class: large
docker:
- image: bepsays/ci-hugoreleaser:1.22400.20000
- image: bepsays/ci-hugoreleaser:1.22301.20401
environment: &buildenv
GOMODCACHE: /root/project/gomodcache
version: 2
@@ -58,7 +58,7 @@ jobs:
environment:
<<: [*buildenv]
docker:
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22400.20000
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22301.20401
steps:
- *restore-cache
- &attach-workspace
-1
View File
@@ -46,4 +46,3 @@ jobs:
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: HUGO_BUILD_TAGS=extended,withdeploy
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.23.x, 1.24.x]
go-version: [1.22.x, 1.23.x]
os: [ubuntu-latest, windows-latest] # macos disabled for now because of disk space issues.
runs-on: ${{ matrix.os }}
steps:
+1 -2
View File
@@ -1,4 +1,4 @@
>**Note:** We would appreciate if you hold on with any big refactoring (like renaming deprecated Go packages), mainly because of potential for extra merge work for future coming in in the near future.
>**Note:** We would apprecitate if you hold on with any big refactorings (like renaming deprecated Go packages), mainly because of potential for extra merge work for future coming in in the near future.
# Contributing to Hugo
@@ -93,7 +93,6 @@ Most title/subjects should have a lower-cased prefix with a colon and one whites
* If this commit touches many packages without a common functional topic, prefix with `all:` (e.g. `all: Reformat Go code`)
* If this is a documentation update, prefix with `docs:`.
* If nothing of the above applies, just leave the prefix out.
* Note that the above excludes nouns seen in other repositories, e.g. "chore:".
Also, if your commit references one or more GitHub issues, always end your commit message body with *See #1234* or *Fixes #1234*.
Replace *1234* with the GitHub issue ID. The last example will close the issue when the commit is merged into *master*.
+32 -62
View File
@@ -34,7 +34,6 @@ A fast and flexible static site generator built with love by [bep], [spf13], and
[![Go Report Card](https://goreportcard.com/badge/github.com/gohugoio/hugo)](https://goreportcard.com/report/github.com/gohugoio/hugo)
[Website] | [Installation] | [Documentation] | [Support] | [Contributing] | <a rel="me" href="https://fosstodon.org/@gohugoio">Mastodon</a>
## Overview
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.
@@ -70,23 +69,6 @@ See the [features] section of the documentation for a comprehensive summary of H
<a href="https://www.jetbrains.com/go/?utm_source=OSS&utm_medium=referral&utm_campaign=hugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/goland.svg" width="200" alt="The complete IDE crafted for professional Go developers."></a>
</p>
## Editions
Hugo is available in three editions: standard, extended, and extended/deploy. While the standard edition provides core functionality, the extended and extended/deploy editions offer advanced features.
Feature|extended edition|extended/deploy edition
:--|:-:|:-:
Encode to the WebP format when [processing images]. You can decode WebP images with any edition.|:heavy_check_mark:|:heavy_check_mark:
[Transpile Sass to CSS] using the embedded LibSass transpiler. You can use the [Dart Sass] transpiler with any edition.|:heavy_check_mark:|:heavy_check_mark:
Deploy your site directly to a Google Cloud Storage bucket, an AWS S3 bucket, or an Azure Storage container. See&nbsp;[details].|:x:|:heavy_check_mark:
[dart sass]: https://gohugo.io/functions/css/sass/#dart-sass
[processing images]: https://gohugo.io/content-management/image-processing/
[transpile sass to css]: https://gohugo.io/functions/css/sass/
[details]: https://gohugo.io/hosting-and-deployment/hugo-deploy/
Unless your specific deployment needs require the extended/deploy edition, we recommend the extended edition.
## Installation
Install Hugo from a [prebuilt binary], package manager, or package repository. Please see the installation instructions for your operating system:
@@ -98,11 +80,15 @@ Install Hugo from a [prebuilt binary], package manager, or package repository. P
## Build from source
Hugo is available in two editions: standard and extended. With the extended edition you can:
- Encode to the WebP format when processing images. You can decode WebP images with either edition.
- Transpile Sass to CSS using the embedded LibSass transpiler. The extended edition is not required to use the Dart Sass transpiler.
Prerequisites to build Hugo from source:
- Standard edition: Go 1.23.0 or later
- Extended edition: Go 1.23.0 or later, and GCC
- Extended/deploy edition: Go 1.23.0 or later, and GCC
- Standard edition: Go 1.20 or later
- Extended edition: Go 1.20 or later, and GCC
Build the standard edition:
@@ -116,12 +102,6 @@ Build the extended edition:
CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest
```
Build the extended/deploy edition:
```text
CGO_ENABLED=1 go install -tags extended,withdeploy github.com/gohugoio/hugo@latest
```
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=gohugoio/hugo&type=Timeline)](https://star-history.com/#gohugoio/hugo&Timeline)
@@ -169,9 +149,7 @@ Hugo stands on the shoulders of great open source libraries. Run `hugo env --log
```text
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
github.com/PuerkitoBio/goquery="v1.10.1"
github.com/alecthomas/chroma/v2="v2.15.0"
github.com/andybalholm/cascadia="v1.3.3"
github.com/alecthomas/chroma/v2="v2.14.0"
github.com/armon/go-radix="v1.0.1-0.20221118154546-54df44f2176c"
github.com/bep/clocks="v0.5.0"
github.com/bep/debounce="v1.2.0"
@@ -180,77 +158,73 @@ github.com/bep/goat="v0.5.0"
github.com/bep/godartsass/v2="v2.3.2"
github.com/bep/golibsass="v1.2.0"
github.com/bep/gowebp="v0.3.0"
github.com/bep/imagemeta="v0.8.4"
github.com/bep/imagemeta="v0.8.3"
github.com/bep/lazycache="v0.7.0"
github.com/bep/logg="v0.4.0"
github.com/bep/mclib="v1.20400.20402"
github.com/bep/overlayfs="v0.9.2"
github.com/bep/simplecobra="v0.5.0"
github.com/bep/simplecobra="v0.4.0"
github.com/bep/tmc="v0.5.1"
github.com/cespare/xxhash/v2="v2.3.0"
github.com/clbanning/mxj/v2="v2.7.0"
github.com/cli/safeexec="v1.0.1"
github.com/cpuguy83/go-md2man/v2="v2.0.4"
github.com/disintegration/gift="v1.2.1"
github.com/dlclark/regexp2="v1.11.5"
github.com/dop251/goja="v0.0.0-20250125213203-5ef83b82af17"
github.com/evanw/esbuild="v0.24.2"
github.com/dlclark/regexp2="v1.11.0"
github.com/evanw/esbuild="v0.24.0"
github.com/fatih/color="v1.18.0"
github.com/frankban/quicktest="v1.14.6"
github.com/fsnotify/fsnotify="v1.8.0"
github.com/getkin/kin-openapi="v0.129.0"
github.com/getkin/kin-openapi="v0.123.0"
github.com/ghodss/yaml="v1.0.0"
github.com/go-openapi/jsonpointer="v0.21.0"
github.com/go-openapi/swag="v0.23.0"
github.com/go-sourcemap/sourcemap="v2.1.4+incompatible"
github.com/go-openapi/jsonpointer="v0.20.2"
github.com/go-openapi/swag="v0.22.8"
github.com/gobuffalo/flect="v1.0.3"
github.com/gobwas/glob="v0.2.3"
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20230805085216-e63c13218d0e"
github.com/gohugoio/hashstructure="v0.5.0"
github.com/gohugoio/hashstructure="v0.1.0"
github.com/gohugoio/httpcache="v0.7.0"
github.com/gohugoio/hugo-goldmark-extensions/extras="v0.2.0"
github.com/gohugoio/hugo-goldmark-extensions/passthrough="v0.3.0"
github.com/gohugoio/locales="v0.14.0"
github.com/gohugoio/localescompressed="v1.0.1"
github.com/golang/freetype="v0.0.0-20170609003504-e2365dfdc4a0"
github.com/google/go-cmp="v0.6.0"
github.com/google/pprof="v0.0.0-20250208200701-d0013a598941"
github.com/gorilla/websocket="v1.5.3"
github.com/hairyhenderson/go-codeowners="v0.7.0"
github.com/hashicorp/golang-lru/v2="v2.0.7"
github.com/invopop/yaml="v0.2.0"
github.com/jdkato/prose="v1.2.1"
github.com/josharian/intern="v1.0.0"
github.com/kr/pretty="v0.3.1"
github.com/kr/text="v0.2.0"
github.com/kyokomi/emoji/v2="v2.2.13"
github.com/lucasb-eyer/go-colorful="v1.2.0"
github.com/mailru/easyjson="v0.7.7"
github.com/makeworld-the-better-one/dither/v2="v2.4.0"
github.com/marekm4/color-extractor="v1.2.1"
github.com/mattn/go-colorable="v0.1.13"
github.com/mattn/go-isatty="v0.0.20"
github.com/mattn/go-runewidth="v0.0.9"
github.com/mazznoer/csscolorparser="v0.1.5"
github.com/mitchellh/mapstructure="v1.5.1-0.20231216201459-8508981c8b6c"
github.com/mohae/deepcopy="v0.0.0-20170929034955-c48cc78d4826"
github.com/muesli/smartcrop="v0.3.0"
github.com/niklasfasching/go-org="v1.7.0"
github.com/oasdiff/yaml3="v0.0.0-20241210130736-a94c01f36349"
github.com/oasdiff/yaml="v0.0.0-20241210131133-6b86fb107d80"
github.com/olekukonko/tablewriter="v0.0.5"
github.com/pbnjay/memory="v0.0.0-20210728143218-7b4eea64cf58"
github.com/pelletier/go-toml/v2="v2.2.3"
github.com/perimeterx/marshmallow="v1.1.5"
github.com/pkg/browser="v0.0.0-20240102092130-5ac0b6a4141c"
github.com/pkg/errors="v0.9.1"
github.com/rivo/uniseg="v0.4.7"
github.com/rogpeppe/go-internal="v1.13.1"
github.com/russross/blackfriday/v2="v2.1.0"
github.com/sass/dart-sass/compiler="1.81.0"
github.com/sass/dart-sass/implementation="1.81.0"
github.com/sass/dart-sass/protocol="3.1.0"
github.com/sass/libsass="3.6.6"
github.com/spf13/afero="v1.11.0"
github.com/spf13/cast="v1.7.1"
github.com/spf13/cobra="v1.8.1"
github.com/spf13/fsync="v0.10.1"
github.com/spf13/pflag="v1.0.6"
github.com/spf13/pflag="v1.0.5"
github.com/tdewolff/minify/v2="v2.20.37"
github.com/tdewolff/parse/v2="v2.7.15"
github.com/tetratelabs/wazero="v1.8.2"
@@ -258,22 +232,18 @@ github.com/webmproject/libwebp="v1.3.2"
github.com/yuin/goldmark-emoji="v1.0.4"
github.com/yuin/goldmark="v1.7.8"
go.uber.org/automaxprocs="v1.5.3"
golang.org/x/crypto="v0.33.0"
golang.org/x/exp="v0.0.0-20250210185358-939b2ce775ac"
golang.org/x/image="v0.24.0"
golang.org/x/mod="v0.23.0"
golang.org/x/net="v0.35.0"
golang.org/x/sync="v0.11.0"
golang.org/x/sys="v0.30.0"
golang.org/x/text="v0.22.0"
golang.org/x/tools="v0.30.0"
golang.org/x/xerrors="v0.0.0-20240903120638-7835f813f4da"
gonum.org/v1/plot="v0.15.0"
google.golang.org/protobuf="v1.36.5"
golang.org/x/crypto="v0.31.0"
golang.org/x/exp="v0.0.0-20221031165847-c99f073a8326"
golang.org/x/image="v0.22.0"
golang.org/x/mod="v0.22.0"
golang.org/x/net="v0.33.0"
golang.org/x/sync="v0.10.0"
golang.org/x/sys="v0.28.0"
golang.org/x/text="v0.21.0"
golang.org/x/tools="v0.28.0"
google.golang.org/protobuf="v1.35.2"
gopkg.in/yaml.v2="v2.4.0"
gopkg.in/yaml.v3="v3.0.1"
oss.terrastruct.com/d2="v0.6.9"
oss.terrastruct.com/util-go="v0.0.0-20241005222610-44c011a04896"
rsc.io/qr="v0.2.0"
software.sslmate.com/src/go-pkcs12="v0.2.0"
```
+5 -11
View File
@@ -176,12 +176,11 @@ func (c *Cache) ClearMatching(predicatePartition func(k string, p PartitionManag
}
// ClearOnRebuild prepares the cache for a new rebuild taking the given changeset into account.
// predicate is optional and will clear any entry for which it returns true.
func (c *Cache) ClearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity) {
func (c *Cache) ClearOnRebuild(changeset ...identity.Identity) {
g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
NumWorkers: len(c.partitions),
Handle: func(ctx context.Context, partition PartitionManager) error {
partition.clearOnRebuild(predicate, changeset...)
partition.clearOnRebuild(changeset...)
return nil
},
})
@@ -480,12 +479,7 @@ func (p *Partition[K, V]) clearMatching(predicate func(k, v any) bool) {
})
}
func (p *Partition[K, V]) clearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity) {
if predicate == nil {
predicate = func(k, v any) bool {
return false
}
}
func (p *Partition[K, V]) clearOnRebuild(changeset ...identity.Identity) {
opts := p.getOptions()
if opts.ClearWhen == ClearNever {
return
@@ -531,7 +525,7 @@ func (p *Partition[K, V]) clearOnRebuild(predicate func(k, v any) bool, changese
// Second pass needs to be done in a separate loop to catch any
// elements marked as stale in the other partitions.
p.c.DeleteFunc(func(key K, v V) bool {
if predicate(key, v) || shouldDelete(key, v) {
if shouldDelete(key, v) {
p.trace.Log(
logg.StringFunc(
func() string {
@@ -607,7 +601,7 @@ type PartitionManager interface {
adjustMaxSize(addend int) int
getMaxSize() int
getOptions() OptionsPartition
clearOnRebuild(predicate func(k, v any) bool, changeset ...identity.Identity)
clearOnRebuild(changeset ...identity.Identity)
clearMatching(predicate func(k, v any) bool)
clearStale()
}
+6 -6
View File
@@ -147,13 +147,13 @@ func TestClear(t *testing.T) {
c.Assert(cache.Keys(predicateAll), qt.HasLen, 4)
cache.ClearOnRebuild(nil)
cache.ClearOnRebuild()
// Stale items are always cleared.
c.Assert(cache.Keys(predicateAll), qt.HasLen, 2)
cache = newTestCache(t)
cache.ClearOnRebuild(nil, identity.StringIdentity("changed"))
cache.ClearOnRebuild(identity.StringIdentity("changed"))
c.Assert(cache.Keys(nil), qt.HasLen, 1)
@@ -191,16 +191,16 @@ func TestPanicInCreate(t *testing.T) {
return err
}
for i := range 3 {
for range 3 {
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
c.Assert(willPanic(i), qt.PanicMatches, fmt.Sprintf("panic-%d", i))
c.Assert(willErr(i), qt.ErrorMatches, fmt.Sprintf("error-%d", i))
}
}
// Test the same keys again without the panic.
for i := range 3 {
for range 3 {
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
v, err := p1.GetOrCreate(fmt.Sprintf("panic-%d", i), func(key string) (testItem, error) {
return testItem{
name: key,
+3 -3
View File
@@ -59,7 +59,7 @@ dir = ":resourceDir/_gen"
caches, err := filecache.NewCaches(p)
c.Assert(err, qt.IsNil)
cache := caches[name]
for i := range 10 {
for i := 0; i < 10; i++ {
id := fmt.Sprintf("i%d", i)
cache.GetOrCreateBytes(id, func() ([]byte, error) {
return []byte("abc"), nil
@@ -74,7 +74,7 @@ dir = ":resourceDir/_gen"
c.Assert(err, qt.IsNil)
c.Assert(count, qt.Equals, 5, msg)
for i := range 10 {
for i := 0; i < 10; i++ {
id := fmt.Sprintf("i%d", i)
v := cache.GetString(id)
if i < 5 {
@@ -97,7 +97,7 @@ dir = ":resourceDir/_gen"
c.Assert(count, qt.Equals, 4)
// Now only the i5 should be left.
for i := range 10 {
for i := 0; i < 10; i++ {
id := fmt.Sprintf("i%d", i)
v := cache.GetString(id)
if i != 5 {
+3 -3
View File
@@ -105,7 +105,7 @@ dir = ":cacheDir/c"
}
for _, ca := range []*filecache.Cache{caches.ImageCache(), caches.AssetsCache(), caches.GetJSONCache(), caches.GetCSVCache()} {
for range 2 {
for i := 0; i < 2; i++ {
info, r, err := ca.GetOrCreate("a", rf("abc"))
c.Assert(err, qt.IsNil)
c.Assert(r, qt.Not(qt.IsNil))
@@ -193,11 +193,11 @@ dir = "/cache/c"
var wg sync.WaitGroup
for i := range 50 {
for i := 0; i < 50; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
for range 20 {
for j := 0; j < 20; j++ {
ca := caches.Get(cacheName)
c.Assert(ca, qt.Not(qt.IsNil))
filename, data := filenameData(i)
+3 -25
View File
@@ -42,7 +42,7 @@ var DefaultConfig = Config{
// Config holds the configuration for the HTTP cache.
type Config struct {
// Configures the HTTP cache behavior (RFC 9111).
// Configures the HTTP cache behaviour (RFC 9111).
// When this is not enabled for a resource, Hugo will go straight to the file cache.
Cache Cache
@@ -52,7 +52,7 @@ type Config struct {
}
type Cache struct {
// Enable HTTP cache behavior (RFC 9111) for these resources.
// Enable HTTP cache behaviour (RFC 9111) for these rsources.
For GlobMatcher
}
@@ -122,10 +122,6 @@ type GlobMatcher struct {
Includes []string
}
func (gm GlobMatcher) IsZero() bool {
return len(gm.Includes) == 0 && len(gm.Excludes) == 0
}
type ConfigCompiled struct {
For predicate.P[string]
PollConfigs []PollConfigCompiled
@@ -159,9 +155,6 @@ func (p PollConfigCompiled) IsZero() bool {
}
func (gm *GlobMatcher) CompilePredicate() (func(string) bool, error) {
if gm.IsZero() {
panic("no includes or excludes")
}
var p predicate.P[string]
for _, include := range gm.Includes {
g, err := glob.Compile(include, '/')
@@ -188,7 +181,7 @@ func (gm *GlobMatcher) CompilePredicate() (func(string) bool, error) {
return p, nil
}
func DecodeConfig(_ config.BaseConfig, m map[string]any) (Config, error) {
func DecodeConfig(bcfg config.BaseConfig, m map[string]any) (Config, error) {
if len(m) == 0 {
return DefaultConfig, nil
}
@@ -210,20 +203,5 @@ func DecodeConfig(_ config.BaseConfig, m map[string]any) (Config, error) {
return c, err
}
if c.Cache.For.IsZero() {
c.Cache.For = DefaultConfig.Cache.For
}
for pci := range c.Polls {
if c.Polls[pci].For.IsZero() {
c.Polls[pci].For = DefaultConfig.Cache.For
c.Polls[pci].Disable = true
}
}
if len(c.Polls) == 0 {
c.Polls = DefaultConfig.Polls
}
return c, nil
}
-31
View File
@@ -22,8 +22,6 @@ import (
)
func TestConfigCustom(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
[httpcache]
@@ -53,8 +51,6 @@ includes = ["**gohugo.io**"]
}
func TestConfigDefault(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
`
@@ -66,30 +62,3 @@ func TestConfigDefault(t *testing.T) {
b.Assert(compiled.For("https://gohugo.io/foo.jpg"), qt.IsFalse)
b.Assert(compiled.PollConfigFor("https://gohugo.io/foo.jpg").Config.Disable, qt.IsTrue)
}
func TestConfigPollsOnly(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
[httpcache]
[[httpcache.polls]]
low = "5s"
high = "32s"
[httpcache.polls.for]
includes = ["**gohugo.io**"]
`
b := hugolib.Test(t, files)
compiled := b.H.Configs.Base.C.HTTPCache
b.Assert(compiled.For("https://gohugo.io/posts.json"), qt.IsFalse)
b.Assert(compiled.For("https://gohugo.io/foo.jpg"), qt.IsFalse)
pc := compiled.PollConfigFor("https://gohugo.io/foo.jpg")
b.Assert(pc.Config.Low, qt.Equals, 5*time.Second)
b.Assert(pc.Config.High, qt.Equals, 32*time.Second)
b.Assert(compiled.PollConfigFor("https://example.com/foo.jpg").IsZero(), qt.IsTrue)
}
-31
View File
@@ -17,7 +17,6 @@ import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config"
)
func TestGlobMatcher(t *testing.T) {
@@ -41,33 +40,3 @@ func TestGlobMatcher(t *testing.T) {
c.Assert(p("foo/bar/foo.css"), qt.IsFalse)
c.Assert(p("foo/bar/foo.xml"), qt.IsTrue)
}
func TestDefaultConfig(t *testing.T) {
c := qt.New(t)
_, err := DefaultConfig.Compile()
c.Assert(err, qt.IsNil)
}
func TestDecodeConfigInjectsDefaultAndCompiles(t *testing.T) {
c := qt.New(t)
cfg, err := DecodeConfig(config.BaseConfig{}, map[string]interface{}{})
c.Assert(err, qt.IsNil)
c.Assert(cfg, qt.DeepEquals, DefaultConfig)
_, err = cfg.Compile()
c.Assert(err, qt.IsNil)
cfg, err = DecodeConfig(config.BaseConfig{}, map[string]any{
"cache": map[string]any{
"polls": []map[string]any{
{"disable": true},
},
},
})
c.Assert(err, qt.IsNil)
_, err = cfg.Compile()
c.Assert(err, qt.IsNil)
}
+7 -8
View File
@@ -26,7 +26,6 @@ import (
"path/filepath"
"reflect"
"regexp"
"slices"
"sort"
"strings"
"sync"
@@ -103,7 +102,7 @@ func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.T
}
for _, t := range include {
for i := range t.NumMethod() {
for i := 0; i < t.NumMethod(); i++ {
m := t.Method(i)
if excludes[m.Name] || seen[m.Name] {
@@ -123,7 +122,7 @@ func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.T
method := Method{Owner: t, OwnerName: ownerName, Name: m.Name}
for i := range numIn {
for i := 0; i < numIn; i++ {
in := m.Type.In(i)
name, pkg := nameAndPackage(in)
@@ -138,7 +137,7 @@ func (c *Inspector) MethodsFromTypes(include []reflect.Type, exclude []reflect.T
numOut := m.Type.NumOut()
if numOut > 0 {
for i := range numOut {
for i := 0; i < numOut; i++ {
out := m.Type.Out(i)
name, pkg := nameAndPackage(out)
@@ -305,7 +304,7 @@ func (m Method) inOutStr() string {
}
args := make([]string, len(m.In))
for i := range args {
for i := 0; i < len(args); i++ {
args[i] = fmt.Sprintf("arg%d", i)
}
return "(" + strings.Join(args, ", ") + ")"
@@ -317,7 +316,7 @@ func (m Method) inStr() string {
}
args := make([]string, len(m.In))
for i := range args {
for i := 0; i < len(args); i++ {
args[i] = fmt.Sprintf("arg%d %s", i, m.In[i])
}
return "(" + strings.Join(args, ", ") + ")"
@@ -340,7 +339,7 @@ func (m Method) outStrNamed() string {
}
outs := make([]string, len(m.Out))
for i := range outs {
for i := 0; i < len(outs); i++ {
outs[i] = fmt.Sprintf("o%d %s", i, m.Out[i])
}
@@ -436,7 +435,7 @@ func (m Methods) ToMarshalJSON(receiver, pkgPath string, excludes ...string) (st
// Exclude self
for i, pkgImp := range pkgImports {
if pkgImp == pkgPath {
pkgImports = slices.Delete(pkgImports, i, i+1)
pkgImports = append(pkgImports[:i], pkgImports[i+1:]...)
}
}
}
+5 -5
View File
@@ -101,8 +101,8 @@ type configKey struct {
// This is the root command.
type rootCommand struct {
Printf func(format string, v ...any)
Println func(a ...any)
Printf func(format string, v ...interface{})
Println func(a ...interface{})
StdOut io.Writer
StdErr io.Writer
@@ -431,12 +431,12 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
// Used by mkcert (server).
log.SetOutput(r.StdOut)
r.Printf = func(format string, v ...any) {
r.Printf = func(format string, v ...interface{}) {
if !r.quiet {
fmt.Fprintf(r.StdOut, format, v...)
}
}
r.Println = func(a ...any) {
r.Println = func(a ...interface{}) {
if !r.quiet {
fmt.Fprintln(r.StdOut, a...)
}
@@ -448,7 +448,7 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
return err
}
// Set up the global logger early to allow info deprecations during config load.
loggers.SetGlobalLogger(r.logger)
loggers.InitGlobalLogger(r.logger.Level(), false)
r.changesFromBuild = make(chan []identity.Identity, 10)
+1 -1
View File
@@ -90,7 +90,7 @@ func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg
os.Stdout.Write(buf.Bytes())
default:
// Decode the JSON to a map[string]interface{} and then unmarshal it again to the correct format.
var m map[string]any
var m map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &m); err != nil {
return err
}
+1
View File
@@ -12,6 +12,7 @@
// limitations under the License.
//go:build withdeploy
// +build withdeploy
package commands
+1
View File
@@ -12,6 +12,7 @@
// limitations under the License.
//go:build !withdeploy
// +build !withdeploy
// Copyright 2024 The Hugo Authors. All rights reserved.
//
+6 -21
View File
@@ -21,7 +21,6 @@ import (
"os"
"path"
"path/filepath"
"slices"
"strings"
"github.com/alecthomas/chroma/v2"
@@ -50,7 +49,6 @@ func newGenCommand() *genCommand {
highlightStyle string
lineNumbersInlineStyle string
lineNumbersTableStyle string
omitEmpty bool
)
newChromaStyles := func() simplecobra.Commander {
@@ -62,10 +60,6 @@ func newGenCommand() *genCommand {
See https://xyproto.github.io/splash/docs/all.html for a preview of the available styles`,
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
style = strings.ToLower(style)
if !slices.Contains(styles.Names(), style) {
return fmt.Errorf("invalid style: %s", style)
}
builder := styles.Get(style).Builder()
if highlightStyle != "" {
builder.Add(chroma.LineHighlight, highlightStyle)
@@ -80,14 +74,7 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
if err != nil {
return err
}
var formatter *html.Formatter
if omitEmpty {
formatter = html.New(html.WithClasses(true))
} else {
formatter = html.New(html.WithAllClasses(true))
}
formatter := html.New(html.WithAllClasses(true))
w := os.Stdout
fmt.Fprintf(w, "/* Generated using: hugo %s */\n\n", strings.Join(os.Args[1:], " "))
formatter.WriteCSS(w, style)
@@ -103,8 +90,6 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
_ = cmd.RegisterFlagCompletionFunc("lineNumbersInlineStyle", cobra.NoFileCompletions)
cmd.PersistentFlags().StringVar(&lineNumbersTableStyle, "lineNumbersTableStyle", "", `foreground and background colors for table line numbers, e.g. --lineNumbersTableStyle "#fff000 bg:#000fff"`)
_ = cmd.RegisterFlagCompletionFunc("lineNumbersTableStyle", cobra.NoFileCompletions)
cmd.PersistentFlags().BoolVar(&omitEmpty, "omitEmpty", false, `omit empty CSS rules`)
_ = cmd.RegisterFlagCompletionFunc("omitEmpty", cobra.NoFileCompletions)
},
}
}
@@ -159,7 +144,7 @@ url: %s
return &simpleCommand{
name: "doc",
short: "Generate Markdown documentation for the Hugo CLI",
short: "Generate Markdown documentation for the Hugo CLI.",
long: `Generate Markdown documentation for the Hugo CLI.
This command is, mostly, used to create up-to-date documentation
of Hugo's command-line interface for https://gohugo.io/.
@@ -184,13 +169,13 @@ url: %s
prepender := func(filename string) string {
name := filepath.Base(filename)
base := strings.TrimSuffix(name, path.Ext(name))
url := "/docs/reference/commands/" + strings.ToLower(base) + "/"
url := "/commands/" + strings.ToLower(base) + "/"
return fmt.Sprintf(gendocFrontmatterTemplate, strings.Replace(base, "_", " ", -1), base, url)
}
linkHandler := func(name string) string {
base := strings.TrimSuffix(name, path.Ext(name))
return "/docs/reference/commands/" + strings.ToLower(base) + "/"
return "/commands/" + strings.ToLower(base) + "/"
}
r.Println("Generating Hugo command-line documentation in", gendocdir, "...")
doc.GenMarkdownTreeCustom(cd.CobraCommand.Root(), gendocdir, prepender, linkHandler)
@@ -211,7 +196,7 @@ url: %s
newDocsHelper := func() simplecobra.Commander {
return &simpleCommand{
name: "docshelper",
short: "Generate some data files for the Hugo docs",
short: "Generate some data files for the Hugo docs.",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
r.Println("Generate docs data to", docsHelperTarget)
@@ -232,7 +217,7 @@ url: %s
}
// Decode the JSON to a map[string]interface{} and then unmarshal it again to the correct format.
var m map[string]any
var m map[string]interface{}
if err := json.Unmarshal(buf.Bytes(), &m); err != nil {
return err
}
+16 -14
View File
@@ -663,20 +663,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
var n int
for _, ev := range evs {
keep := true
// Write and rename operations are often followed by CHMOD.
// There may be valid use cases for rebuilding the site on CHMOD,
// but that will require more complex logic than this simple conditional.
// On OS X this seems to be related to Spotlight, see:
// https://github.com/go-fsnotify/fsnotify/issues/15
// A workaround is to put your site(s) on the Spotlight exception list,
// but that may be a little mysterious for most end users.
// So, for now, we skip reload on CHMOD.
// We do have to check for WRITE though. On slower laptops a Chmod
// could be aggregated with other important events, and we still want
// to rebuild on those
if ev.Op == fsnotify.Chmod {
keep = false
} else if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Write) {
if ev.Has(fsnotify.Create) || ev.Has(fsnotify.Write) {
if _, err := os.Stat(ev.Name); err != nil {
keep = false
}
@@ -818,6 +805,21 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
continue
}
// Write and rename operations are often followed by CHMOD.
// There may be valid use cases for rebuilding the site on CHMOD,
// but that will require more complex logic than this simple conditional.
// On OS X this seems to be related to Spotlight, see:
// https://github.com/go-fsnotify/fsnotify/issues/15
// A workaround is to put your site(s) on the Spotlight exception list,
// but that may be a little mysterious for most end users.
// So, for now, we skip reload on CHMOD.
// We do have to check for WRITE though. On slower laptops a Chmod
// could be aggregated with other important events, and we still want
// to rebuild on those
if ev.Op&(fsnotify.Chmod|fsnotify.Write|fsnotify.Create) == fsnotify.Chmod {
continue
}
walkAdder := func(path string, f hugofs.FileMetaInfo) error {
if f.IsDir() {
c.r.logger.Println("adding created directory to watchlist", path)
+14 -14
View File
@@ -44,16 +44,16 @@ func newModCommands() *modCommands {
npmCommand := &simpleCommand{
name: "npm",
short: "Various npm helpers",
short: "Various npm helpers.",
long: `Various npm (Node package manager) helpers.`,
commands: []simplecobra.Commander{
&simpleCommand{
name: "pack",
short: "Experimental: Prepares and writes a composite package.json file for your project",
short: "Experimental: Prepares and writes a composite package.json file for your project.",
long: `Prepares and writes a composite package.json file for your project.
On first run it creates a "package.hugo.json" in the project root if not already there. This file will be used as a template file
with the base dependency set.
with the base dependency set.
This set will be merged with all "package.hugo.json" files found in the dependency tree, picking the version closest to the project.
@@ -80,12 +80,12 @@ so this may/will change in future versions of Hugo.
commands: []simplecobra.Commander{
&simpleCommand{
name: "init",
short: "Initialize this project as a Hugo Module",
short: "Initialize this project as a Hugo Module.",
long: `Initialize this project as a Hugo Module.
It will try to guess the module path, but you may help by passing it as an argument, e.g:
hugo mod init github.com/gohugoio/testshortcodes
Note that Hugo Modules supports multi-module projects, so you can initialize a Hugo Module
inside a subfolder on GitHub, as one example.
`,
@@ -111,7 +111,7 @@ so this may/will change in future versions of Hugo.
},
&simpleCommand{
name: "verify",
short: "Verify dependencies",
short: "Verify dependencies.",
long: `Verify checks that the dependencies of the current module, which are stored in a local downloaded source cache, have not been modified since being downloaded.`,
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
@@ -129,7 +129,7 @@ so this may/will change in future versions of Hugo.
},
&simpleCommand{
name: "graph",
short: "Print a module dependency graph",
short: "Print a module dependency graph.",
long: `Print a module dependency graph with information about module status (disabled, vendored).
Note that for vendored modules, that is the version listed and not the one from go.mod.
`,
@@ -149,7 +149,7 @@ Note that for vendored modules, that is the version listed and not the one from
},
&simpleCommand{
name: "clean",
short: "Delete the Hugo Module cache for the current project",
short: "Delete the Hugo Module cache for the current project.",
long: `Delete the Hugo Module cache for the current project.`,
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
@@ -175,7 +175,7 @@ Note that for vendored modules, that is the version listed and not the one from
},
&simpleCommand{
name: "tidy",
short: "Remove unused entries in go.mod and go.sum",
short: "Remove unused entries in go.mod and go.sum.",
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = cobra.NoFileCompletions
applyLocalFlagsBuildConfig(cmd, r)
@@ -190,7 +190,7 @@ Note that for vendored modules, that is the version listed and not the one from
},
&simpleCommand{
name: "vendor",
short: "Vendor all module dependencies into the _vendor directory",
short: "Vendor all module dependencies into the _vendor directory.",
long: `Vendor all module dependencies into the _vendor directory.
If a module is vendored, that is where Hugo will look for it's dependencies.
`,
@@ -209,16 +209,16 @@ Note that for vendored modules, that is the version listed and not the one from
&simpleCommand{
name: "get",
short: "Resolves dependencies in your current Hugo project",
short: "Resolves dependencies in your current Hugo Project.",
long: `
Resolves dependencies in your current Hugo project.
Resolves dependencies in your current Hugo Project.
Some examples:
Install the latest version possible for a given module:
hugo mod get github.com/gohugoio/testshortcodes
Install a specific version:
hugo mod get github.com/gohugoio/testshortcodes@v0.3.0
+2 -9
View File
@@ -144,7 +144,7 @@ according to your needs.`,
createpath := paths.AbsPathify(conf.configs.Base.WorkingDir, filepath.Join(conf.configs.Base.ThemesDir, args[0]))
r.Println("Creating new theme in", createpath)
err = skeletons.CreateTheme(createpath, sourceFs, format)
err = skeletons.CreateTheme(createpath, sourceFs)
if err != nil {
return err
}
@@ -152,14 +152,7 @@ according to your needs.`,
return nil
},
withc: func(cmd *cobra.Command, r *rootCommand) {
cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return []string{}, cobra.ShellCompDirectiveNoFileComp
}
return []string{}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveFilterDirs
}
cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)")
_ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp))
cmd.ValidArgsFunction = cobra.NoFileCompletions
},
},
},
+1 -1
View File
@@ -32,7 +32,7 @@ func newReleaseCommand() simplecobra.Commander {
return &simpleCommand{
name: "release",
short: "Release a new version of Hugo",
short: "Release a new version of Hugo.",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
rel, err := releaser.New(skipPush, try, step)
if err != nil {
+64 -86
View File
@@ -23,7 +23,6 @@ import (
"errors"
"fmt"
"io"
"maps"
"net"
"net/http"
_ "net/http/pprof"
@@ -49,7 +48,6 @@ import (
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/tpl/tplimpl"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/common/urls"
@@ -59,6 +57,7 @@ import (
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/hugolib/filesystems"
"github.com/gohugoio/hugo/livereload"
"github.com/gohugoio/hugo/tpl"
"github.com/gohugoio/hugo/transform"
"github.com/gohugoio/hugo/transform/livereloadinject"
"github.com/spf13/afero"
@@ -85,10 +84,6 @@ const (
configChangeGoWork = "go work file"
)
const (
hugoHeaderRedirect = "X-Hugo-Redirect"
)
func newHugoBuilder(r *rootCommand, s *serverCommand, onConfigLoaded ...func(reloaded bool) error) *hugoBuilder {
var visitedURLs *types.EvictingQueue[string]
if s != nil && !s.disableFastRender {
@@ -120,7 +115,7 @@ func newServerCommand() *serverCommand {
commands: []simplecobra.Commander{
&simpleCommand{
name: "trust",
short: "Install the local CA in the system trust store",
short: "Install the local CA in the system trust store.",
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
action := "-install"
if uninstall {
@@ -196,7 +191,9 @@ func (f *fileChangeDetector) PrepareNew() {
}
f.prev = make(map[string]uint64)
maps.Copy(f.prev, f.current)
for k, v := range f.current {
f.prev[k] = v
}
f.current = make(map[string]uint64)
}
@@ -310,65 +307,67 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
w.Header().Set(header.Key, header.Value)
}
if canRedirect(requestURI, r) {
if redirect := serverConfig.MatchRedirect(requestURI, r.Header); !redirect.IsZero() {
doRedirect := true
// This matches Netlify's behavior and is needed for SPA behavior.
// See https://docs.netlify.com/routing/redirects/rewrites-proxies/
if !redirect.Force {
path := filepath.Clean(strings.TrimPrefix(requestURI, baseURL.Path()))
if root != "" {
path = filepath.Join(root, path)
}
var fs afero.Fs
f.c.withConf(func(conf *commonConfig) {
fs = conf.fs.PublishDirServer
})
fi, err := fs.Stat(path)
if err == nil {
if fi.IsDir() {
// There will be overlapping directories, so we
// need to check for a file.
_, err = fs.Stat(filepath.Join(path, "index.html"))
doRedirect = err != nil
} else {
doRedirect = false
}
}
if redirect := serverConfig.MatchRedirect(requestURI); !redirect.IsZero() {
// fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
doRedirect := true
// This matches Netlify's behavior and is needed for SPA behavior.
// See https://docs.netlify.com/routing/redirects/rewrites-proxies/
if !redirect.Force {
path := filepath.Clean(strings.TrimPrefix(requestURI, baseURL.Path()))
if root != "" {
path = filepath.Join(root, path)
}
var fs afero.Fs
f.c.withConf(func(conf *commonConfig) {
fs = conf.fs.PublishDirServer
})
if doRedirect {
w.Header().Set(hugoHeaderRedirect, "true")
switch redirect.Status {
case 404:
w.WriteHeader(404)
file, err := fs.Open(strings.TrimPrefix(redirect.To, baseURL.Path()))
if err == nil {
defer file.Close()
io.Copy(w, file)
} else {
fmt.Fprintln(w, "<h1>Page Not Found</h1>")
}
return
case 200:
if r2 := f.rewriteRequest(r, strings.TrimPrefix(redirect.To, baseURL.Path())); r2 != nil {
requestURI = redirect.To
r = r2
}
default:
w.Header().Set("Content-Type", "")
http.Redirect(w, r, redirect.To, redirect.Status)
return
fi, err := fs.Stat(path)
if err == nil {
if fi.IsDir() {
// There will be overlapping directories, so we
// need to check for a file.
_, err = fs.Stat(filepath.Join(path, "index.html"))
doRedirect = err != nil
} else {
doRedirect = false
}
}
}
if doRedirect {
switch redirect.Status {
case 404:
w.WriteHeader(404)
file, err := fs.Open(strings.TrimPrefix(redirect.To, baseURL.Path()))
if err == nil {
defer file.Close()
io.Copy(w, file)
} else {
fmt.Fprintln(w, "<h1>Page Not Found</h1>")
}
return
case 200:
if r2 := f.rewriteRequest(r, strings.TrimPrefix(redirect.To, baseURL.Path())); r2 != nil {
requestURI = redirect.To
r = r2
}
default:
w.Header().Set("Content-Type", "")
http.Redirect(w, r, redirect.To, redirect.Status)
return
}
}
}
if f.c.fastRenderMode && f.c.errState.buildErr() == nil {
if isNavigation(requestURI, r) {
// Sec-Fetch-Mode should be sent by all recent browser versions, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode#navigate
// Fall back to the file extension if not set.
// The main take here is that we don't want to have CSS/JS files etc. partake in this logic.
if r.Header.Get("Sec-Fetch-Mode") == "navigate" || strings.HasSuffix(requestURI, "/") || strings.HasSuffix(requestURI, "html") || strings.HasSuffix(requestURI, "htm") {
if !f.c.visitedURLs.Contains(requestURI) {
// If not already on stack, re-render that single page.
if err := f.c.partialReRender(requestURI); err != nil {
@@ -758,7 +757,7 @@ func (c *serverCommand) createServerPorts(cd *simplecobra.Commandeer) error {
c.serverPorts = make([]serverPortListener, len(conf.configs.Languages))
}
currentServerPort := c.serverPort
for i := range c.serverPorts {
for i := 0; i < len(c.serverPorts); i++ {
l, err := net.Listen("tcp", net.JoinHostPort(c.serverInterface, strconv.Itoa(currentServerPort)))
if err == nil {
c.serverPorts[i] = serverPortListener{ln: l, p: currentServerPort}
@@ -897,16 +896,16 @@ func (c *serverCommand) serve() error {
// To allow the en user to change the error template while the server is running, we use
// the freshest template we can provide.
var (
errTempl *tplimpl.TemplInfo
templHandler *tplimpl.TemplateStore
errTempl tpl.Template
templHandler tpl.TemplateHandler
)
getErrorTemplateAndHandler := func(h *hugolib.HugoSites) (*tplimpl.TemplInfo, *tplimpl.TemplateStore) {
getErrorTemplateAndHandler := func(h *hugolib.HugoSites) (tpl.Template, tpl.TemplateHandler) {
if h == nil {
return errTempl, templHandler
}
templHandler := h.GetTemplateStore()
errTempl := templHandler.LookupByPath("/_server/error.html")
if errTempl == nil {
templHandler := h.Tmpl()
errTempl, found := templHandler.Lookup("_server/error.html")
if !found {
panic("template server/error.html not found")
}
return errTempl, templHandler
@@ -1234,24 +1233,3 @@ func formatByteCount(b uint64) string {
return fmt.Sprintf("%.1f %cB",
float64(b)/float64(div), "kMGTPE"[exp])
}
func canRedirect(requestURIWithoutQuery string, r *http.Request) bool {
if r.Header.Get(hugoHeaderRedirect) != "" {
return false
}
return isNavigation(requestURIWithoutQuery, r)
}
// Sec-Fetch-Mode should be sent by all recent browser versions, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode#navigate
// Fall back to the file extension if not set.
// The main take here is that we don't want to have CSS/JS files etc. partake in this logic.
func isNavigation(requestURIWithoutQuery string, r *http.Request) bool {
return r.Header.Get("Sec-Fetch-Mode") == "navigate" || isPropablyHTMLRequest(requestURIWithoutQuery)
}
func isPropablyHTMLRequest(requestURIWithoutQuery string) bool {
if strings.HasSuffix(requestURIWithoutQuery, "/") || strings.HasSuffix(requestURIWithoutQuery, "html") || strings.HasSuffix(requestURIWithoutQuery, "htm") {
return true
}
return !strings.Contains(requestURIWithoutQuery, ".")
}
+2 -2
View File
@@ -117,7 +117,7 @@ func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]any, erro
tos = append(tos, nil)
continue
}
for i := range slice.Len() {
for i := 0; i < slice.Len(); i++ {
tos = append(tos, slice.Index(i).Interface())
}
}
@@ -128,7 +128,7 @@ func appendToInterfaceSliceFromValues(slice1, slice2 reflect.Value) ([]any, erro
func appendToInterfaceSlice(tov reflect.Value, from ...any) ([]any, error) {
var tos []any
for i := range tov.Len() {
for i := 0; i < tov.Len(); i++ {
tos = append(tos, tov.Index(i).Interface())
}
+1 -3
View File
@@ -13,8 +13,6 @@
package collections
import "slices"
import "sync"
// Stack is a simple LIFO stack that is safe for concurrent use.
@@ -75,7 +73,7 @@ func (s *Stack[T]) DrainMatching(predicate func(T) bool) []T {
for i := len(s.items) - 1; i >= 0; i-- {
if predicate(s.items[i]) {
items = append(items, s.items[i])
s.items = slices.Delete(s.items, i, i+1)
s.items = append(s.items[:i], s.items[i+1:]...)
}
}
return items
-1
View File
@@ -23,7 +23,6 @@ const (
WarnFrontMatterParamsOverrides = "warning-frontmatter-params-overrides"
WarnRenderShortcodesInHTML = "warning-rendershortcodes-in-html"
WarnGoldmarkRawHTML = "warning-goldmark-raw-html"
WarnPartialSuperfluousPrefix = "warning-partial-superfluous-prefix"
)
// Field/method names with special meaning.
+4 -4
View File
@@ -37,12 +37,12 @@ func TestXxHashFromReaderPara(t *testing.T) {
c := qt.New(t)
var wg sync.WaitGroup
for i := range 10 {
for i := 0; i < 10; i++ {
i := i
wg.Add(1)
go func() {
defer wg.Done()
for j := range 100 {
for j := 0; j < 100; j++ {
s := strings.Repeat("Hello ", i+j+1*42)
r := strings.NewReader(s)
got, size, err := XXHashFromReader(r)
@@ -144,8 +144,8 @@ func BenchmarkHashString(b *testing.B) {
}
func BenchmarkHashMap(b *testing.B) {
m := map[string]any{}
for i := range 1000 {
m := map[string]interface{}{}
for i := 0; i < 1000; i++ {
m[fmt.Sprintf("key%d", i)] = i
}
+8 -2
View File
@@ -152,7 +152,10 @@ func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext
}
if ectx.Position.LineNumber > 0 {
low := max(ectx.Position.LineNumber-3, 0)
low := ectx.Position.LineNumber - 3
if low < 0 {
low = 0
}
if ectx.Position.LineNumber > 2 {
ectx.LinesPos = 2
@@ -160,7 +163,10 @@ func locateError(r io.Reader, le FileError, matches LineMatcherFn) *ErrorContext
ectx.LinesPos = ectx.Position.LineNumber - 1
}
high := min(ectx.Position.LineNumber+2, len(lines))
high := ectx.Position.LineNumber + 2
if high > len(lines) {
high = len(lines)
}
ectx.Lines = lines[low:high]
+18 -5
View File
@@ -27,6 +27,7 @@ import (
"sync"
"github.com/bep/logg"
"github.com/cli/safeexec"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
@@ -112,6 +113,18 @@ func IsNotFound(err error) bool {
return errors.As(err, &notFoundErr)
}
// SafeCommand is a wrapper around os/exec Command which uses a LookPath
// implementation that does not search in current directory before looking in PATH.
// See https://github.com/cli/safeexec and the linked issues.
func SafeCommand(name string, arg ...string) (*exec.Cmd, error) {
bin, err := safeexec.LookPath(name)
if err != nil {
return nil, err
}
return exec.Command(bin, arg...), nil
}
// Exec enforces a security policy for commands run via os/exec.
type Exec struct {
sc security.Config
@@ -184,7 +197,7 @@ func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
tryFuncs := map[binaryLocation]tryFunc{
binaryLocationNodeModules: func() func(...any) (Runner, error) {
nodeBinFilename := filepath.Join(e.workingDir, nodeModulesBinPath, name)
_, err := exec.LookPath(nodeBinFilename)
_, err := safeexec.LookPath(nodeBinFilename)
if err != nil {
return nil
}
@@ -202,7 +215,7 @@ func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
}
},
binaryLocationPath: func() func(...any) (Runner, error) {
if _, err := exec.LookPath(name); err != nil {
if _, err := safeexec.LookPath(name); err != nil {
return nil
}
return func(arg2 ...any) (Runner, error) {
@@ -333,7 +346,7 @@ func (c *commandeer) command(arg ...any) (*cmdWrapper, error) {
bin = c.fullyQualifiedName
} else {
var err error
bin, err = exec.LookPath(c.name)
bin, err = safeexec.LookPath(c.name)
if err != nil {
return nil, &NotFoundError{
name: c.name,
@@ -371,7 +384,7 @@ func InPath(binaryName string) bool {
if strings.Contains(binaryName, "/") {
panic("binary name should not contain any slash")
}
_, err := exec.LookPath(binaryName)
_, err := safeexec.LookPath(binaryName)
return err == nil
}
@@ -381,7 +394,7 @@ func LookPath(binaryName string) string {
if strings.Contains(binaryName, "/") {
panic("binary name should not contain any slash")
}
s, err := exec.LookPath(binaryName)
s, err := safeexec.LookPath(binaryName)
if err != nil {
return ""
}
+17 -16
View File
@@ -74,16 +74,6 @@ func IsTruthful(in any) bool {
}
}
// IsMap reports whether v is a map.
func IsMap(v any) bool {
return reflect.ValueOf(v).Kind() == reflect.Map
}
// IsSlice reports whether v is a slice.
func IsSlice(v any) bool {
return reflect.ValueOf(v).Kind() == reflect.Slice
}
var zeroType = reflect.TypeOf((*types.Zeroer)(nil)).Elem()
// IsTruthfulValue returns whether the given value has a meaningful truth value.
@@ -134,7 +124,12 @@ type methodKey struct {
name string
}
var methodCache sync.Map
type methods struct {
sync.RWMutex
cache map[methodKey]int
}
var methodCache = &methods{cache: make(map[methodKey]int)}
// GetMethodByName is the same as reflect.Value.MethodByName, but it caches the
// type lookup.
@@ -152,16 +147,22 @@ func GetMethodByName(v reflect.Value, name string) reflect.Value {
// -1 if no such method exists.
func GetMethodIndexByName(tp reflect.Type, name string) int {
k := methodKey{tp, name}
v, found := methodCache.Load(k)
methodCache.RLock()
index, found := methodCache.cache[k]
methodCache.RUnlock()
if found {
return v.(int)
return index
}
methodCache.Lock()
defer methodCache.Unlock()
m, ok := tp.MethodByName(name)
index := m.Index
index = m.Index
if !ok {
index = -1
}
methodCache.Store(k, index)
methodCache.cache[k] = index
if !ok {
return -1
@@ -234,7 +235,7 @@ func ToSliceAny(v any) ([]any, bool) {
vvv := reflect.ValueOf(v)
if vvv.Kind() == reflect.Slice {
out := make([]any, vvv.Len())
for i := range vvv.Len() {
for i := 0; i < vvv.Len(); i++ {
out[i] = vvv.Index(i).Interface()
}
return out, true
-14
View File
@@ -134,17 +134,3 @@ func BenchmarkGetMethodByName(b *testing.B) {
}
}
}
func BenchmarkGetMethodByNamePara(b *testing.B) {
v := reflect.ValueOf(&testStruct{})
methods := []string{"Method1", "Method2", "Method3", "Method4", "Method5"}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
for _, method := range methods {
_ = GetMethodByName(v, method)
}
}
})
}
+16 -7
View File
@@ -16,7 +16,6 @@ package hstrings
import (
"fmt"
"regexp"
"slices"
"strings"
"sync"
@@ -51,7 +50,12 @@ func (s StringEqualFold) Eq(s2 any) bool {
// EqualAny returns whether a string is equal to any of the given strings.
func EqualAny(a string, b ...string) bool {
return slices.Contains(b, a)
for _, s := range b {
if a == s {
return true
}
}
return false
}
// regexpCache represents a cache of regexp objects protected by a mutex.
@@ -99,7 +103,12 @@ func GetOrCompileRegexp(pattern string) (re *regexp.Regexp, err error) {
// InSlice checks if a string is an element of a slice of strings
// and returns a boolean value.
func InSlice(arr []string, el string) bool {
return slices.Contains(arr, el)
for _, v := range arr {
if v == el {
return true
}
}
return false
}
// InSlicEqualFold checks if a string is an element of a slice of strings
@@ -128,7 +137,7 @@ func ToString(v any) (string, bool) {
return "", false
}
type (
Strings2 [2]string
Strings3 [3]string
)
type Tuple struct {
First string
Second string
}
+1 -1
View File
@@ -46,7 +46,7 @@ func TestHasBytesWriter(t *testing.T) {
return strings.Repeat("ab cfo", r.Intn(33))
}
for range 22 {
for i := 0; i < 22; i++ {
h, w := neww()
fmt.Fprint(w, rndStr()+"abc __foobar"+rndStr())
c.Assert(h.Patterns[0].Match, qt.Equals, true)
+10 -31
View File
@@ -139,13 +139,9 @@ func (i HugoInfo) IsMultilingual() bool {
return i.conf.IsMultilingual()
}
type contextKey uint8
type contextKey string
const (
contextKeyMarkupScope contextKey = iota
)
var markupScope = hcontext.NewContextDispatcher[string](contextKeyMarkupScope)
var markupScope = hcontext.NewContextDispatcher[string](contextKey("markupScope"))
type Context struct{}
@@ -415,36 +411,19 @@ func IsDartSassGeV2() bool {
// 2. Their theme to work for at least the last few Hugo versions.
func Deprecate(item, alternative string, version string) {
level := deprecationLogLevelFromVersion(version)
deprecateLevel(item, alternative, version, level)
}
// See Deprecate for details.
func DeprecateWithLogger(item, alternative string, version string, log logg.Logger) {
level := deprecationLogLevelFromVersion(version)
deprecateLevelWithLogger(item, alternative, version, level, log)
}
// DeprecateLevelMin informs about a deprecation starting at the given version, but with a minimum log level.
func DeprecateLevelMin(item, alternative string, version string, minLevel logg.Level) {
level := max(deprecationLogLevelFromVersion(version), minLevel)
deprecateLevel(item, alternative, version, level)
}
// deprecateLevel informs about a deprecation logging at the given level.
func deprecateLevel(item, alternative, version string, level logg.Level) {
deprecateLevelWithLogger(item, alternative, version, level, loggers.Log().Logger())
DeprecateLevel(item, alternative, version, level)
}
// DeprecateLevel informs about a deprecation logging at the given level.
func deprecateLevelWithLogger(item, alternative, version string, level logg.Level, log logg.Logger) {
func DeprecateLevel(item, alternative, version string, level logg.Level) {
var msg string
if level == logg.LevelError {
msg = fmt.Sprintf("%s was deprecated in Hugo %s and subsequently removed. %s", item, version, alternative)
msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in Hugo %s. %s", item, version, CurrentVersion.Next().ReleaseVersion(), alternative)
} else {
msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in a future release. %s", item, version, alternative)
}
log.WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf("%s", msg)
loggers.Log().Logger().WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf("%s", msg)
}
// We usually do about one minor version a month.
@@ -455,11 +434,11 @@ func deprecationLogLevelFromVersion(ver string) logg.Level {
to := CurrentVersion
minorDiff := to.Minor - from.Minor
switch {
case minorDiff >= 15:
// Start failing the build after about 15 months.
case minorDiff >= 12:
// Start failing the build after about a year.
return logg.LevelError
case minorDiff >= 3:
// Start printing warnings after about 3 months.
case minorDiff >= 6:
// Start printing warnings after about six months.
return logg.LevelWarn
default:
return logg.LevelInfo
+4 -4
View File
@@ -57,11 +57,11 @@ func TestDeprecationLogLevelFromVersion(t *testing.T) {
c.Assert(deprecationLogLevelFromVersion("0.55.0"), qt.Equals, logg.LevelError)
ver := CurrentVersion
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelInfo)
ver.Minor -= 3
ver.Minor -= 1
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelInfo)
ver.Minor -= 6
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelWarn)
ver.Minor -= 4
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelWarn)
ver.Minor -= 13
ver.Minor -= 6
c.Assert(deprecationLogLevelFromVersion(ver.String()), qt.Equals, logg.LevelError)
// Added just to find the threshold for where we can remove deprecated items.
+1
View File
@@ -12,6 +12,7 @@
// limitations under the License.
//go:build extended
// +build extended
package hugo
+1
View File
@@ -12,6 +12,7 @@
// limitations under the License.
//go:build !extended
// +build !extended
package hugo
+1
View File
@@ -12,6 +12,7 @@
// limitations under the License.
//go:build withdeploy
// +build withdeploy
package hugo
+1
View File
@@ -12,6 +12,7 @@
// limitations under the License.
//go:build !withdeploy
// +build !withdeploy
package hugo
+2 -2
View File
@@ -17,7 +17,7 @@ package hugo
// This should be the only one.
var CurrentVersion = Version{
Major: 0,
Minor: 146,
Minor: 143,
PatchLevel: 0,
Suffix: "",
Suffix: "-DEV",
}
+2 -2
View File
@@ -37,7 +37,7 @@ func TestLogDistinct(t *testing.T) {
l := loggers.New(opts)
for range 10 {
for i := 0; i < 10; i++ {
l.Errorln("error 1")
l.Errorln("error 2")
l.Warnln("warn 1")
@@ -137,7 +137,7 @@ func TestReset(t *testing.T) {
l := loggers.New(opts)
for range 3 {
for i := 0; i < 3; i++ {
l.Errorln("error 1")
l.Errorln("error 2")
l.Errorln("error 1")
+2 -10
View File
@@ -21,15 +21,7 @@ import (
"github.com/bep/logg"
)
// SetGlobalLogger sets the global logger.
// This is used in a few places in Hugo, e.g. deprecated functions.
func SetGlobalLogger(logger Logger) {
logMu.Lock()
defer logMu.Unlock()
log = logger
}
func initGlobalLogger(level logg.Level, panicOnWarnings bool) {
func InitGlobalLogger(level logg.Level, panicOnWarnings bool) {
logMu.Lock()
defer logMu.Unlock()
var logHookLast func(e *logg.Entry) error
@@ -58,5 +50,5 @@ func Log() Logger {
var log Logger
func init() {
initGlobalLogger(logg.LevelWarn, false)
InitGlobalLogger(logg.LevelWarn, false)
}
+1 -20
View File
@@ -69,14 +69,6 @@ func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
return v, nil
}
// Contains returns whether the given key exists in the cache.
func (c *Cache[K, T]) Contains(key K) bool {
c.RLock()
_, found := c.m[key]
c.RUnlock()
return found
}
// InitAndGet initializes the cache if not already done and returns the value for the given key.
// The init state will be reset on Reset or Drain.
func (c *Cache[K, T]) InitAndGet(key K, init func(get func(key K) (T, bool), set func(key K, value T)) error) (T, error) {
@@ -116,17 +108,6 @@ func (c *Cache[K, T]) Set(key K, value T) {
c.Unlock()
}
// SetIfAbsent sets the given key to the given value if the key does not already exist in the cache.
func (c *Cache[K, T]) SetIfAbsent(key K, value T) {
c.RLock()
if _, found := c.get(key); !found {
c.RUnlock()
c.Set(key, value)
} else {
c.RUnlock()
}
}
func (c *Cache[K, T]) set(key K, value T) {
c.m[key] = value
}
@@ -160,7 +141,7 @@ func (c *Cache[K, T]) Len() int {
func (c *Cache[K, T]) Reset() {
c.Lock()
clear(c.m)
c.m = make(map[K]T)
c.hasBeenInitialized = false
c.Unlock()
}
-4
View File
@@ -73,14 +73,10 @@ func TestPrepareParams(t *testing.T) {
for i, test := range tests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
// PrepareParams modifies input.
prepareClone := PrepareParamsClone(test.input)
PrepareParams(test.input)
if !reflect.DeepEqual(test.expected, test.input) {
t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)
}
if !reflect.DeepEqual(test.expected, prepareClone) {
t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, prepareClone)
}
})
}
}
+1 -12
View File
@@ -14,8 +14,6 @@
package maps
import (
"slices"
"github.com/gohugoio/hugo/common/hashing"
)
@@ -58,15 +56,6 @@ func (m *Ordered[K, T]) Get(key K) (T, bool) {
return value, found
}
// Has returns whether the given key exists in the map.
func (m *Ordered[K, T]) Has(key K) bool {
if m == nil {
return false
}
_, found := m.values[key]
return found
}
// Delete deletes the value for the given key.
func (m *Ordered[K, T]) Delete(key K) {
if m == nil {
@@ -75,7 +64,7 @@ func (m *Ordered[K, T]) Delete(key K) {
delete(m.values, key)
for i, k := range m.keys {
if k == key {
m.keys = slices.Delete(m.keys, i, i+1)
m.keys = append(m.keys[:i], m.keys[i+1:]...)
break
}
}
+1 -40
View File
@@ -303,7 +303,7 @@ func toMergeStrategy(v any) ParamsMergeStrategy {
}
// PrepareParams
// * makes all the keys in the given map lower cased and will do so recursively.
// * makes all the keys in the given map lower cased and will do so
// * This will modify the map given.
// * Any nested map[interface{}]interface{}, map[string]interface{},map[string]string will be converted to Params.
// * Any _merge value will be converted to proper type and value.
@@ -343,42 +343,3 @@ func PrepareParams(m Params) {
}
}
}
// PrepareParamsClone is like PrepareParams, but it does not modify the input.
func PrepareParamsClone(m Params) Params {
m2 := make(Params)
for k, v := range m {
var retyped bool
lKey := strings.ToLower(k)
if lKey == MergeStrategyKey {
v = toMergeStrategy(v)
retyped = true
} else {
switch vv := v.(type) {
case map[any]any:
var p Params = cast.ToStringMap(v)
v = PrepareParamsClone(p)
retyped = true
case map[string]any:
var p Params = v.(map[string]any)
v = PrepareParamsClone(p)
retyped = true
case map[string]string:
p := make(Params)
for k, v := range vv {
p[k] = v
}
v = p
PrepareParams(p)
retyped = true
}
}
if retyped || k != lKey {
m2[lKey] = v
} else {
m2[k] = v
}
}
return m2
}
+1 -1
View File
@@ -140,7 +140,7 @@ func TestScratchInParallel(t *testing.T) {
for i := 1; i <= 10; i++ {
wg.Add(1)
go func(j int) {
for k := range 10 {
for k := 0; k < 10; k++ {
newVal := int64(k + j)
_, err := scratch.Add(key, newVal)
+3 -3
View File
@@ -42,7 +42,7 @@ func TestPara(t *testing.T) {
c.Run("Order", func(c *qt.C) {
n := 500
ints := make([]int, n)
for i := range n {
for i := 0; i < n; i++ {
ints[i] = i
}
@@ -51,7 +51,7 @@ func TestPara(t *testing.T) {
var result []int
var mu sync.Mutex
for i := range n {
for i := 0; i < n; i++ {
i := i
r.Run(func() error {
mu.Lock()
@@ -78,7 +78,7 @@ func TestPara(t *testing.T) {
var counter int64
for range n {
for i := 0; i < n; i++ {
r.Run(func() error {
atomic.AddInt64(&counter, 1)
time.Sleep(1 * time.Millisecond)
+84 -266
View File
@@ -23,11 +23,6 @@ import (
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources/kinds"
)
const (
identifierBaseof = "baseof"
)
// PathParser parses a path into a Path.
@@ -38,10 +33,6 @@ type PathParser struct {
// Reports whether the given language is disabled.
IsLangDisabled func(string) bool
// IsOutputFormat reports whether the given name is a valid output format.
// The second argument is optional.
IsOutputFormat func(name, ext string) bool
// Reports whether the given ext is a content file.
IsContentExt func(string) bool
}
@@ -92,10 +83,13 @@ func (pp *PathParser) Parse(c, s string) *Path {
}
func (pp *PathParser) newPath(component string) *Path {
p := &Path{}
p.reset()
p.component = component
return p
return &Path{
component: component,
posContainerLow: -1,
posContainerHigh: -1,
posSectionHigh: -1,
posIdentifierLanguage: -1,
}
}
func (pp *PathParser) parse(component, s string) (*Path, error) {
@@ -120,91 +114,10 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
return p, nil
}
func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot int) {
if p.posContainerHigh != -1 {
return
}
mayHaveLang := pp.LanguageIndex != nil
mayHaveLang = mayHaveLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
mayHaveOutputFormat := component == files.ComponentFolderLayouts
mayHaveKind := mayHaveOutputFormat
var found bool
var high int
if len(p.identifiers) > 0 {
high = lastDot
} else {
high = len(p.s)
}
id := types.LowHigh[string]{Low: i + 1, High: high}
sid := p.s[id.Low:id.High]
if len(p.identifiers) == 0 {
// The first is always the extension.
p.identifiers = append(p.identifiers, id)
found = true
// May also be the output format.
if mayHaveOutputFormat && pp.IsOutputFormat(sid, "") {
p.posIdentifierOutputFormat = 0
}
} else {
var langFound bool
if mayHaveLang {
var disabled bool
_, langFound = pp.LanguageIndex[sid]
if !langFound {
disabled = pp.IsLangDisabled != nil && pp.IsLangDisabled(sid)
if disabled {
p.disabled = true
langFound = true
}
}
found = langFound
if langFound {
p.identifiers = append(p.identifiers, id)
p.posIdentifierLanguage = len(p.identifiers) - 1
}
}
if !found && mayHaveOutputFormat {
// At this point we may already have resolved an output format,
// but we need to keep looking for a more specific one, e.g. amp before html.
// Use both name and extension to prevent
// false positives on the form css.html.
if pp.IsOutputFormat(sid, p.Ext()) {
found = true
p.identifiers = append(p.identifiers, id)
p.posIdentifierOutputFormat = len(p.identifiers) - 1
}
}
if !found && mayHaveKind {
if kinds.GetKindMain(sid) != "" {
found = true
p.identifiers = append(p.identifiers, id)
p.posIdentifierKind = len(p.identifiers) - 1
}
}
if !found && sid == identifierBaseof {
found = true
p.identifiers = append(p.identifiers, id)
p.posIdentifierBaseof = len(p.identifiers) - 1
}
if !found {
p.identifiers = append(p.identifiers, id)
p.identifiersUnknown = append(p.identifiersUnknown, len(p.identifiers)-1)
}
}
}
func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
hasLang := pp.LanguageIndex != nil
hasLang = hasLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
if runtime.GOOS == "windows" {
s = path.Clean(filepath.ToSlash(s))
if s == "." {
@@ -227,21 +140,46 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
p.s = s
slashCount := 0
lastDot := 0
for i := len(s) - 1; i >= 0; i-- {
c := s[i]
switch c {
case '.':
pp.parseIdentifier(component, s, p, i, lastDot)
lastDot = i
if p.posContainerHigh == -1 {
var high int
if len(p.identifiers) > 0 {
high = p.identifiers[len(p.identifiers)-1].Low - 1
} else {
high = len(p.s)
}
id := types.LowHigh[string]{Low: i + 1, High: high}
if len(p.identifiers) == 0 {
p.identifiers = append(p.identifiers, id)
} else if len(p.identifiers) == 1 {
// Check for a valid language.
s := p.s[id.Low:id.High]
if hasLang {
var disabled bool
_, langFound := pp.LanguageIndex[s]
if !langFound {
disabled = pp.IsLangDisabled != nil && pp.IsLangDisabled(s)
if disabled {
p.disabled = true
langFound = true
}
}
if langFound {
p.posIdentifierLanguage = 1
p.identifiers = append(p.identifiers, id)
}
}
}
}
case '/':
slashCount++
if p.posContainerHigh == -1 {
if lastDot > 0 {
pp.parseIdentifier(component, s, p, i, lastDot)
}
p.posContainerHigh = i + 1
} else if p.posContainerLow == -1 {
p.posContainerLow = i + 1
@@ -256,41 +194,22 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
isContent := isContentComponent && pp.IsContentExt(p.Ext())
id := p.identifiers[len(p.identifiers)-1]
if id.High > p.posContainerHigh {
b := p.s[p.posContainerHigh:id.High]
if isContent {
switch b {
case "index":
p.pathType = TypeLeaf
case "_index":
p.pathType = TypeBranch
default:
p.pathType = TypeContentSingle
}
if slashCount == 2 && p.IsLeafBundle() {
p.posSectionHigh = 0
}
} else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
p.pathType = TypeContentData
b := p.s[p.posContainerHigh : id.Low-1]
if isContent {
switch b {
case "index":
p.bundleType = PathTypeLeaf
case "_index":
p.bundleType = PathTypeBranch
default:
p.bundleType = PathTypeContentSingle
}
}
}
if component == files.ComponentFolderLayouts {
if p.posIdentifierBaseof != -1 {
p.pathType = TypeBaseof
} else {
pth := p.Path()
if strings.Contains(pth, "/_shortcodes/") {
p.pathType = TypeShortcode
} else if strings.Contains(pth, "/_markup/") {
p.pathType = TypeMarkup
} else if strings.HasPrefix(pth, "/_partials/") {
p.pathType = TypePartial
if slashCount == 2 && p.IsLeafBundle() {
p.posSectionHigh = 0
}
} else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
p.bundleType = PathTypeContentData
}
}
@@ -299,44 +218,35 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
func ModifyPathBundleTypeResource(p *Path) {
if p.IsContent() {
p.pathType = TypeContentResource
p.bundleType = PathTypeContentResource
} else {
p.pathType = TypeFile
p.bundleType = PathTypeFile
}
}
//go:generate stringer -type Type
type Type int
type PathType int
const (
// A generic resource, e.g. a JSON file.
TypeFile Type = iota
PathTypeFile PathType = iota
// All below are content files.
// A resource of a content type with front matter.
TypeContentResource
PathTypeContentResource
// E.g. /blog/my-post.md
TypeContentSingle
PathTypeContentSingle
// All below are bundled content files.
// Leaf bundles, e.g. /blog/my-post/index.md
TypeLeaf
PathTypeLeaf
// Branch bundles, e.g. /blog/_index.md
TypeBranch
PathTypeBranch
// Content data file, _content.gotmpl.
TypeContentData
// Layout types.
TypeMarkup
TypeShortcode
TypePartial
TypeBaseof
PathTypeContentData
)
type Path struct {
@@ -347,17 +257,13 @@ type Path struct {
posContainerHigh int
posSectionHigh int
component string
pathType Type
component string
bundleType PathType
identifiers []types.LowHigh[string]
posIdentifierLanguage int
posIdentifierOutputFormat int
posIdentifierKind int
posIdentifierBaseof int
identifiersUnknown []int
disabled bool
posIdentifierLanguage int
disabled bool
trimLeadingSlash bool
@@ -387,12 +293,9 @@ func (p *Path) reset() {
p.posContainerHigh = -1
p.posSectionHigh = -1
p.component = ""
p.pathType = 0
p.bundleType = 0
p.identifiers = p.identifiers[:0]
p.posIdentifierLanguage = -1
p.posIdentifierOutputFormat = -1
p.posIdentifierKind = -1
p.posIdentifierBaseof = -1
p.disabled = false
p.trimLeadingSlash = false
p.unnormalized = nil
@@ -413,9 +316,6 @@ func (p *Path) norm(s string) string {
// IdentifierBase satisfies identity.Identity.
func (p *Path) IdentifierBase() string {
if p.Component() == files.ComponentFolderLayouts {
return p.Path()
}
return p.Base()
}
@@ -432,13 +332,6 @@ func (p *Path) Container() string {
return p.norm(p.s[p.posContainerLow : p.posContainerHigh-1])
}
func (p *Path) String() string {
if p == nil {
return "<nil>"
}
return p.Path()
}
// ContainerDir returns the container directory for this path.
// For content bundles this will be the parent directory.
func (p *Path) ContainerDir() string {
@@ -459,13 +352,13 @@ func (p *Path) Section() string {
// IsContent returns true if the path is a content file (e.g. mypost.md).
// Note that this will also return true for content files in a bundle.
func (p *Path) IsContent() bool {
return p.Type() >= TypeContentResource && p.Type() <= TypeContentData
return p.BundleType() >= PathTypeContentResource
}
// isContentPage returns true if the path is a content file (e.g. mypost.md),
// but nof if inside a leaf bundle.
func (p *Path) isContentPage() bool {
return p.Type() >= TypeContentSingle && p.Type() <= TypeContentData
return p.BundleType() >= PathTypeContentSingle
}
// Name returns the last element of path.
@@ -505,26 +398,10 @@ func (p *Path) BaseNameNoIdentifier() string {
// NameNoIdentifier returns the last element of path without any identifier (e.g. no extension).
func (p *Path) NameNoIdentifier() string {
lowHigh := p.nameLowHigh()
return p.s[lowHigh.Low:lowHigh.High]
}
func (p *Path) nameLowHigh() types.LowHigh[string] {
if len(p.identifiers) > 0 {
lastID := p.identifiers[len(p.identifiers)-1]
if p.posContainerHigh == lastID.Low {
// The last identifier is the name.
return lastID
}
return types.LowHigh[string]{
Low: p.posContainerHigh,
High: p.identifiers[len(p.identifiers)-1].Low - 1,
}
}
return types.LowHigh[string]{
Low: p.posContainerHigh,
High: len(p.s),
return p.s[p.posContainerHigh : p.identifiers[len(p.identifiers)-1].Low-1]
}
return p.s[p.posContainerHigh:]
}
// Dir returns all but the last element of path, typically the path's directory.
@@ -544,11 +421,6 @@ func (p *Path) Path() (d string) {
return p.norm(p.s)
}
// PathNoLeadingSlash returns the full path without the leading slash.
func (p *Path) PathNoLeadingSlash() string {
return p.Path()[1:]
}
// Unnormalized returns the Path with the original case preserved.
func (p *Path) Unnormalized() *Path {
return p.unnormalized
@@ -564,28 +436,6 @@ func (p *Path) PathNoIdentifier() string {
return p.base(false, false)
}
// PathBeforeLangAndOutputFormatAndExt returns the path up to the first identifier that is not a language or output format.
func (p *Path) PathBeforeLangAndOutputFormatAndExt() string {
if len(p.identifiers) == 0 {
return p.norm(p.s)
}
i := p.identifierIndex(0)
if j := p.posIdentifierOutputFormat; i == -1 || (j != -1 && j < i) {
i = j
}
if j := p.posIdentifierLanguage; i == -1 || (j != -1 && j < i) {
i = j
}
if i == -1 {
return p.norm(p.s)
}
id := p.identifiers[i]
return p.norm(p.s[:id.Low-1])
}
// PathRel returns the path relative to the given owner.
func (p *Path) PathRel(owner *Path) string {
ob := owner.Base()
@@ -612,21 +462,6 @@ func (p *Path) Base() string {
return p.base(!p.isContentPage(), p.IsBundle())
}
// Used in template lookups.
// For pages with Type set, we treat that as the section.
func (p *Path) BaseReTyped(typ string) (d string) {
base := p.Base()
if typ == "" || p.Section() == typ {
return base
}
d = "/" + typ
if p.posSectionHigh != -1 {
d += base[p.posSectionHigh:]
}
d = p.norm(d)
return
}
// BaseNoLeadingSlash returns the base path without the leading slash.
func (p *Path) BaseNoLeadingSlash() string {
return p.Base()[1:]
@@ -642,12 +477,11 @@ func (p *Path) base(preserveExt, isBundle bool) string {
return p.norm(p.s)
}
var high int
id := p.identifiers[len(p.identifiers)-1]
high := id.Low - 1
if isBundle {
high = p.posContainerHigh - 1
} else {
high = p.nameLowHigh().High
}
if high == 0 {
@@ -659,7 +493,7 @@ func (p *Path) base(preserveExt, isBundle bool) string {
}
// For txt files etc. we want to preserve the extension.
id := p.identifiers[0]
id = p.identifiers[0]
return p.norm(p.s[:high] + p.s[id.Low-1:id.High])
}
@@ -668,16 +502,8 @@ func (p *Path) Ext() string {
return p.identifierAsString(0)
}
func (p *Path) OutputFormat() string {
return p.identifierAsString(p.posIdentifierOutputFormat)
}
func (p *Path) Kind() string {
return p.identifierAsString(p.posIdentifierKind)
}
func (p *Path) Lang() string {
return p.identifierAsString(p.posIdentifierLanguage)
return p.identifierAsString(1)
}
func (p *Path) Identifier(i int) string {
@@ -696,36 +522,28 @@ func (p *Path) Identifiers() []string {
return ids
}
func (p *Path) IdentifiersUnknown() []string {
ids := make([]string, len(p.identifiersUnknown))
for i, id := range p.identifiersUnknown {
ids[i] = p.s[p.identifiers[id].Low:p.identifiers[id].High]
}
return ids
}
func (p *Path) Type() Type {
return p.pathType
func (p *Path) BundleType() PathType {
return p.bundleType
}
func (p *Path) IsBundle() bool {
return p.pathType >= TypeLeaf && p.pathType <= TypeContentData
return p.bundleType >= PathTypeLeaf
}
func (p *Path) IsBranchBundle() bool {
return p.pathType == TypeBranch
return p.bundleType == PathTypeBranch
}
func (p *Path) IsLeafBundle() bool {
return p.pathType == TypeLeaf
return p.bundleType == PathTypeLeaf
}
func (p *Path) IsContentData() bool {
return p.pathType == TypeContentData
return p.bundleType == PathTypeContentData
}
func (p Path) ForBundleType(t Type) *Path {
p.pathType = t
func (p Path) ForBundleType(t PathType) *Path {
p.bundleType = t
return &p
}
+12 -158
View File
@@ -18,7 +18,6 @@ import (
"testing"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/resources/kinds"
qt "github.com/frankban/quicktest"
)
@@ -27,18 +26,10 @@ var testParser = &PathParser{
LanguageIndex: map[string]int{
"no": 0,
"en": 1,
"fr": 2,
},
IsContentExt: func(ext string) bool {
return ext == "md"
},
IsOutputFormat: func(name, ext string) bool {
switch name {
case "html", "amp", "csv", "rss":
return true
}
return false
},
}
func TestParse(t *testing.T) {
@@ -114,19 +105,17 @@ func TestParse(t *testing.T) {
"Basic Markdown file",
"/a/b/c.md",
func(c *qt.C, p *Path) {
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Type(), qt.Equals, TypeContentSingle)
c.Assert(p.IsContent(), qt.IsTrue)
c.Assert(p.IsLeafBundle(), qt.IsFalse)
c.Assert(p.Name(), qt.Equals, "c.md")
c.Assert(p.Base(), qt.Equals, "/a/b/c")
c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo/b/c")
c.Assert(p.Section(), qt.Equals, "a")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "c")
c.Assert(p.Path(), qt.Equals, "/a/b/c.md")
c.Assert(p.Dir(), qt.Equals, "/a/b")
c.Assert(p.Container(), qt.Equals, "b")
c.Assert(p.ContainerDir(), qt.Equals, "/a/b")
c.Assert(p.Ext(), qt.Equals, "md")
},
},
{
@@ -141,7 +130,7 @@ func TestParse(t *testing.T) {
// Reclassify it as a content resource.
ModifyPathBundleTypeResource(p)
c.Assert(p.Type(), qt.Equals, TypeContentResource)
c.Assert(p.BundleType(), qt.Equals, PathTypeContentResource)
c.Assert(p.IsContent(), qt.IsTrue)
c.Assert(p.Name(), qt.Equals, "b.md")
c.Assert(p.Base(), qt.Equals, "/a/b.md")
@@ -171,16 +160,14 @@ func TestParse(t *testing.T) {
"/a/b.a.b.no.txt",
func(c *qt.C, p *Path) {
c.Assert(p.Name(), qt.Equals, "b.a.b.no.txt")
c.Assert(p.NameNoIdentifier(), qt.Equals, "b")
c.Assert(p.NameNoIdentifier(), qt.Equals, "b.a.b")
c.Assert(p.NameNoLang(), qt.Equals, "b.a.b.txt")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no", "b", "a", "b"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"b", "a", "b"})
c.Assert(p.Base(), qt.Equals, "/a/b.txt")
c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b.txt")
c.Assert(p.Path(), qt.Equals, "/a/b.a.b.no.txt")
c.Assert(p.PathNoLang(), qt.Equals, "/a/b.txt")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
c.Assert(p.Base(), qt.Equals, "/a/b.a.b.txt")
c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b.a.b.txt")
c.Assert(p.PathNoLang(), qt.Equals, "/a/b.a.b.txt")
c.Assert(p.Ext(), qt.Equals, "txt")
c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b")
c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b.a.b")
},
},
{
@@ -188,7 +175,6 @@ func TestParse(t *testing.T) {
"/_index.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/")
c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo")
c.Assert(p.Path(), qt.Equals, "/_index.md")
c.Assert(p.Container(), qt.Equals, "")
c.Assert(p.ContainerDir(), qt.Equals, "/")
@@ -199,14 +185,13 @@ func TestParse(t *testing.T) {
"/a/index.md",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a")
c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo/a")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "a")
c.Assert(p.Container(), qt.Equals, "a")
c.Assert(p.Container(), qt.Equals, "a")
c.Assert(p.ContainerDir(), qt.Equals, "")
c.Assert(p.Dir(), qt.Equals, "/a")
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "index"})
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
c.Assert(p.IsBranchBundle(), qt.IsFalse)
c.Assert(p.IsBundle(), qt.IsTrue)
c.Assert(p.IsLeafBundle(), qt.IsTrue)
@@ -223,12 +208,11 @@ func TestParse(t *testing.T) {
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b")
c.Assert(p.BaseNameNoIdentifier(), qt.Equals, "b")
c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo/b")
c.Assert(p.Container(), qt.Equals, "b")
c.Assert(p.ContainerDir(), qt.Equals, "/a")
c.Assert(p.Dir(), qt.Equals, "/a/b")
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no", "index"})
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no"})
c.Assert(p.IsBranchBundle(), qt.IsFalse)
c.Assert(p.IsBundle(), qt.IsTrue)
c.Assert(p.IsLeafBundle(), qt.IsTrue)
@@ -236,7 +220,6 @@ func TestParse(t *testing.T) {
c.Assert(p.NameNoExt(), qt.Equals, "index.no")
c.Assert(p.NameNoIdentifier(), qt.Equals, "index")
c.Assert(p.NameNoLang(), qt.Equals, "index.md")
c.Assert(p.Path(), qt.Equals, "/a/b/index.no.md")
c.Assert(p.PathNoLang(), qt.Equals, "/a/b/index.md")
c.Assert(p.Section(), qt.Equals, "a")
},
@@ -250,7 +233,7 @@ func TestParse(t *testing.T) {
c.Assert(p.Container(), qt.Equals, "b")
c.Assert(p.ContainerDir(), qt.Equals, "/a")
c.Assert(p.Ext(), qt.Equals, "md")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no", "_index"})
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no"})
c.Assert(p.IsBranchBundle(), qt.IsTrue)
c.Assert(p.IsBundle(), qt.IsTrue)
c.Assert(p.IsLeafBundle(), qt.IsFalse)
@@ -289,7 +272,7 @@ func TestParse(t *testing.T) {
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/a/b/index.txt")
c.Assert(p.Ext(), qt.Equals, "txt")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no", "index"})
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
c.Assert(p.IsLeafBundle(), qt.IsFalse)
c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b/index")
},
@@ -372,140 +355,11 @@ func TestParse(t *testing.T) {
}
for _, test := range tests {
c.Run(test.name, func(c *qt.C) {
if test.name != "Basic Markdown file" {
// return
}
test.assert(c, testParser.Parse(files.ComponentFolderContent, test.path))
})
}
}
func TestParseLayouts(t *testing.T) {
c := qt.New(t)
tests := []struct {
name string
path string
assert func(c *qt.C, p *Path)
}{
{
"Basic",
"/list.html",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/list.html")
c.Assert(p.OutputFormat(), qt.Equals, "html")
},
},
{
"Lang",
"/list.no.html",
func(c *qt.C, p *Path) {
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "list"})
c.Assert(p.Base(), qt.Equals, "/list.html")
c.Assert(p.Lang(), qt.Equals, "no")
},
},
{
"Lang and output format",
"/list.no.amp.not.html",
func(c *qt.C, p *Path) {
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "not", "amp", "no", "list"})
c.Assert(p.OutputFormat(), qt.Equals, "amp")
c.Assert(p.Ext(), qt.Equals, "html")
c.Assert(p.Lang(), qt.Equals, "no")
c.Assert(p.Base(), qt.Equals, "/list.html")
},
},
{
"Term",
"/term.html",
func(c *qt.C, p *Path) {
c.Assert(p.Base(), qt.Equals, "/term.html")
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "term"})
c.Assert(p.PathNoIdentifier(), qt.Equals, "/term")
c.Assert(p.PathBeforeLangAndOutputFormatAndExt(), qt.Equals, "/term")
c.Assert(p.Lang(), qt.Equals, "")
c.Assert(p.Kind(), qt.Equals, "term")
c.Assert(p.OutputFormat(), qt.Equals, "html")
},
},
{
"Sub dir",
"/pages/home.html",
func(c *qt.C, p *Path) {
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "home"})
c.Assert(p.Lang(), qt.Equals, "")
c.Assert(p.Kind(), qt.Equals, "home")
c.Assert(p.OutputFormat(), qt.Equals, "html")
c.Assert(p.Dir(), qt.Equals, "/pages")
},
},
{
"Baseof",
"/pages/baseof.list.section.fr.amp.html",
func(c *qt.C, p *Path) {
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "amp", "fr", "section", "list", "baseof"})
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"list"})
c.Assert(p.Kind(), qt.Equals, kinds.KindSection)
c.Assert(p.Lang(), qt.Equals, "fr")
c.Assert(p.OutputFormat(), qt.Equals, "amp")
c.Assert(p.Dir(), qt.Equals, "/pages")
c.Assert(p.NameNoIdentifier(), qt.Equals, "baseof")
c.Assert(p.Type(), qt.Equals, TypeBaseof)
c.Assert(p.IdentifierBase(), qt.Equals, "/pages/baseof.list.section.fr.amp.html")
},
},
{
"Markup",
"/_markup/render-link.html",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypeMarkup)
},
},
{
"Markup nested",
"/foo/_markup/render-link.html",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypeMarkup)
},
},
{
"Shortcode",
"/_shortcodes/myshortcode.html",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypeShortcode)
},
},
{
"Shortcode nested",
"/foo/_shortcodes/myshortcode.html",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypeShortcode)
},
},
{
"Shortcode nested sub",
"/foo/_shortcodes/foo/myshortcode.html",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypeShortcode)
},
},
{
"Partials",
"/_partials/foo.bar",
func(c *qt.C, p *Path) {
c.Assert(p.Type(), qt.Equals, TypePartial)
},
},
}
for _, test := range tests {
c.Run(test.name, func(c *qt.C) {
test.assert(c, testParser.Parse(files.ComponentFolderLayouts, test.path))
})
}
}
func TestHasExt(t *testing.T) {
c := qt.New(t)
+27
View File
@@ -0,0 +1,27 @@
// Code generated by "stringer -type=PathType"; DO NOT EDIT.
package paths
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[PathTypeFile-0]
_ = x[PathTypeContentResource-1]
_ = x[PathTypeContentSingle-2]
_ = x[PathTypeLeaf-3]
_ = x[PathTypeBranch-4]
}
const _PathType_name = "PathTypeFilePathTypeContentResourcePathTypeContentSinglePathTypeLeafPathTypeBranch"
var _PathType_index = [...]uint8{0, 12, 35, 56, 68, 82}
func (i PathType) String() string {
if i < 0 || i >= PathType(len(_PathType_index)-1) {
return "PathType(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _PathType_name[_PathType_index[i]:_PathType_index[i+1]]
}
-32
View File
@@ -1,32 +0,0 @@
// Code generated by "stringer -type Type"; DO NOT EDIT.
package paths
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[TypeFile-0]
_ = x[TypeContentResource-1]
_ = x[TypeContentSingle-2]
_ = x[TypeLeaf-3]
_ = x[TypeBranch-4]
_ = x[TypeContentData-5]
_ = x[TypeMarkup-6]
_ = x[TypeShortcode-7]
_ = x[TypePartial-8]
_ = x[TypeBaseof-9]
}
const _Type_name = "TypeFileTypeContentResourceTypeContentSingleTypeLeafTypeBranchTypeContentDataTypeMarkupTypeShortcodeTypePartialTypeBaseof"
var _Type_index = [...]uint8{0, 8, 27, 44, 52, 62, 77, 87, 100, 111, 121}
func (i Type) String() string {
if i < 0 || i >= Type(len(_Type_index)-1) {
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Type_name[_Type_index[i]:_Type_index[i+1]]
}
+1 -1
View File
@@ -51,7 +51,7 @@ func Run[T any](ctx context.Context, cfg Config[T]) Group[T] {
// Buffered for performance.
ch := make(chan T, cfg.NumWorkers)
for range cfg.NumWorkers {
for i := 0; i < cfg.NumWorkers; i++ {
g.Go(func() error {
for {
select {
+4 -1
View File
@@ -103,7 +103,10 @@ func (r *RunEvery) Add(name string, f Func) {
f.IntervalHigh = 20 * time.Second
}
start := max(f.IntervalHigh/3, f.IntervalLow)
start := f.IntervalHigh / 3
if start < f.IntervalLow {
start = f.IntervalLow
}
f.interval = start
f.last = time.Now()
+1 -1
View File
@@ -69,7 +69,7 @@ func ToStringSlicePreserveStringE(v any) ([]string, error) {
switch vv.Kind() {
case reflect.Slice, reflect.Array:
result = make([]string, vv.Len())
for i := range vv.Len() {
for i := 0; i < vv.Len(); i++ {
s, err := cast.ToStringE(vv.Index(i).Interface())
if err != nil {
return nil, err
+1 -2
View File
@@ -15,7 +15,6 @@
package types
import (
"slices"
"sync"
)
@@ -46,7 +45,7 @@ func (q *EvictingQueue[T]) Add(v T) *EvictingQueue[T] {
if len(q.set) == q.size {
// Full
delete(q.set, q.vals[0])
q.vals = slices.Delete(q.vals, 0, 1)
q.vals = append(q.vals[:0], q.vals[1:]...)
}
q.set[v] = true
q.vals = append(q.vals, v)
+1 -1
View File
@@ -55,7 +55,7 @@ func TestEvictingStringQueueConcurrent(t *testing.T) {
queue := NewEvictingQueue[string](3)
for range 100 {
for j := 0; j < 100; j++ {
wg.Add(1)
go func() {
defer wg.Done()
+1 -11
View File
@@ -28,16 +28,6 @@ type RLocker interface {
RUnlock()
}
type Locker interface {
Lock()
Unlock()
}
type RWLocker interface {
RLocker
Locker
}
// KeyValue is a interface{} tuple.
type KeyValue struct {
Key any
@@ -69,7 +59,7 @@ func (k KeyValues) String() string {
// KeyValues struct.
func NewKeyValuesStrings(key string, values ...string) KeyValues {
iv := make([]any, len(values))
for i := range values {
for i := 0; i < len(values); i++ {
iv[i] = values[i]
}
return KeyValues{Key: key, Values: iv}
+10 -37
View File
@@ -82,7 +82,7 @@ func init() {
}
configLanguageKeys = make(map[string]bool)
addKeys := func(v reflect.Value) {
for i := range v.NumField() {
for i := 0; i < v.NumField(); i++ {
name := strings.ToLower(v.Type().Field(i).Name)
if skip[name] {
continue
@@ -128,9 +128,6 @@ type Config struct {
// <docsmeta>{"identifiers": ["markup"] }</docsmeta>
Markup markup_config.Config `mapstructure:"-"`
// ContentTypes are the media types that's considered content in Hugo.
ContentTypes *config.ConfigNamespace[map[string]media.ContentTypeConfig, media.ContentTypes] `mapstructure:"-"`
// The mediatypes configuration section maps the MIME type (a string) to a configuration object for that type.
// <docsmeta>{"identifiers": ["mediatypes"], "refs": ["types:media:type"] }</docsmeta>
MediaTypes *config.ConfigNamespace[map[string]media.MediaTypeConfig, media.Types] `mapstructure:"-"`
@@ -396,46 +393,37 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
// Legacy paginate values.
if c.Paginate != 0 {
hugo.DeprecateWithLogger("site config key paginate", "Use pagination.pagerSize instead.", "v0.128.0", logger.Logger())
hugo.Deprecate("site config key paginate", "Use pagination.pagerSize instead.", "v0.128.0")
c.Pagination.PagerSize = c.Paginate
}
if c.PaginatePath != "" {
hugo.DeprecateWithLogger("site config key paginatePath", "Use pagination.path instead.", "v0.128.0", logger.Logger())
hugo.Deprecate("site config key paginatePath", "Use pagination.path instead.", "v0.128.0")
c.Pagination.Path = c.PaginatePath
}
// Legacy privacy values.
if c.Privacy.Twitter.Disable {
hugo.DeprecateWithLogger("site config key privacy.twitter.disable", "Use privacy.x.disable instead.", "v0.141.0", logger.Logger())
hugo.Deprecate("site config key privacy.twitter.disable", "Use privacy.x.disable instead.", "v0.141.0")
c.Privacy.X.Disable = c.Privacy.Twitter.Disable
}
if c.Privacy.Twitter.EnableDNT {
hugo.DeprecateWithLogger("site config key privacy.twitter.enableDNT", "Use privacy.x.enableDNT instead.", "v0.141.0", logger.Logger())
hugo.Deprecate("site config key privacy.twitter.enableDNT", "Use privacy.x.enableDNT instead.", "v0.141.0")
c.Privacy.X.EnableDNT = c.Privacy.Twitter.EnableDNT
}
if c.Privacy.Twitter.Simple {
hugo.DeprecateWithLogger("site config key privacy.twitter.simple", "Use privacy.x.simple instead.", "v0.141.0", logger.Logger())
hugo.Deprecate("site config key privacy.twitter.simple", "Use privacy.x.simple instead.", "v0.141.0")
c.Privacy.X.Simple = c.Privacy.Twitter.Simple
}
// Legacy services values.
if c.Services.Twitter.DisableInlineCSS {
hugo.DeprecateWithLogger("site config key services.twitter.disableInlineCSS", "Use services.x.disableInlineCSS instead.", "v0.141.0", logger.Logger())
hugo.Deprecate("site config key services.twitter.disableInlineCSS", "Use services.x.disableInlineCSS instead.", "v0.141.0")
c.Services.X.DisableInlineCSS = c.Services.Twitter.DisableInlineCSS
}
// Legacy permalink tokens
vs := fmt.Sprintf("%v", c.Permalinks)
if strings.Contains(vs, ":filename") {
hugo.DeprecateWithLogger("the \":filename\" permalink token", "Use \":contentbasename\" instead.", "0.144.0", logger.Logger())
}
if strings.Contains(vs, ":slugorfilename") {
hugo.DeprecateWithLogger("the \":slugorfilename\" permalink token", "Use \":slugorcontentbasename\" instead.", "0.144.0", logger.Logger())
}
c.C = &ConfigCompiled{
Timeout: timeout,
BaseURL: baseURL,
@@ -445,6 +433,7 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
IgnoredLogs: ignoredLogIDs,
KindOutputFormats: kindOutputFormats,
DefaultOutputFormat: defaultOutputFormat,
ContentTypes: media.DefaultContentTypes.FromTypes(c.MediaTypes.Config),
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
IsUglyURLSection: isUglyURL,
IgnoreFile: ignoreFile,
@@ -482,6 +471,7 @@ type ConfigCompiled struct {
ServerInterface string
KindOutputFormats map[string]output.Formats
DefaultOutputFormat output.Format
ContentTypes media.ContentTypes
DisabledKinds map[string]bool
DisabledLanguages map[string]bool
IgnoredLogs map[string]bool
@@ -849,24 +839,7 @@ func (c *Configs) Init() error {
c.Languages = languages
c.LanguagesDefaultFirst = languagesDefaultFirst
c.ContentPathParser = &paths.PathParser{
LanguageIndex: languagesDefaultFirst.AsIndexSet(),
IsLangDisabled: c.Base.IsLangDisabled,
IsContentExt: c.Base.ContentTypes.Config.IsContentSuffix,
IsOutputFormat: func(name, ext string) bool {
if name == "" {
return false
}
if of, ok := c.Base.OutputFormats.Config.GetByName(name); ok {
if ext != "" && !of.MediaType.HasSuffix(ext) {
return false
}
return true
}
return false
},
}
c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled, IsContentExt: c.Base.C.ContentTypes.IsContentSuffix}
c.configLangs = make([]config.AllProvider, len(c.Languages))
for i, l := range c.LanguagesDefaultFirst {
+1 -20
View File
@@ -7,7 +7,6 @@ import (
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/hugolib"
"github.com/gohugoio/hugo/media"
)
func TestDirsMount(t *testing.T) {
@@ -98,7 +97,7 @@ suffixes = ["html", "xhtml"]
b := hugolib.Test(t, files)
conf := b.H.Configs.Base
contentTypes := conf.ContentTypes.Config
contentTypes := conf.C.ContentTypes
b.Assert(contentTypes.HTML.Suffixes(), qt.DeepEquals, []string{"html", "xhtml"})
b.Assert(contentTypes.Markdown.Suffixes(), qt.DeepEquals, []string{"md", "mdown", "markdown"})
@@ -216,21 +215,3 @@ weight = 3
b := hugolib.Test(t, files)
b.Assert(b.H.Configs.LanguageConfigSlice[0].Title, qt.Equals, `TITLE_DE`)
}
func TestContentTypesDefault(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
`
b := hugolib.Test(t, files)
ct := b.H.Configs.Base.ContentTypes
c := ct.Config
s := ct.SourceStructure.(map[string]media.ContentTypeConfig)
b.Assert(c.IsContentFile("foo.md"), qt.Equals, true)
b.Assert(len(s), qt.Equals, 6)
}
-9
View File
@@ -163,15 +163,6 @@ var allDecoderSetups = map[string]decodeWeight{
return err
},
},
"contenttypes": {
key: "contenttypes",
weight: 100, // This needs to be decoded after media types.
decode: func(d decodeWeight, p decodeConfig) error {
var err error
p.c.ContentTypes, err = media.DecodeContentTypes(p.p.GetStringMap(d.key), p.c.MediaTypes.Config)
return err
},
},
"mediatypes": {
key: "mediatypes",
decode: func(d decodeWeight, p decodeConfig) error {
+1 -1
View File
@@ -145,7 +145,7 @@ func (c ConfigLanguage) NewIdentityManager(name string, opts ...identity.Manager
}
func (c ConfigLanguage) ContentTypes() config.ContentTypesProvider {
return c.config.ContentTypes.Config
return c.config.C.ContentTypes
}
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
+2 -2
View File
@@ -91,7 +91,7 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
return nil, fmt.Errorf("failed to init config: %w", err)
}
loggers.SetGlobalLogger(d.Logger)
loggers.InitGlobalLogger(d.Logger.Level(), configs.Base.PanicOnWarning)
return configs, nil
}
@@ -305,7 +305,7 @@ func (l configLoader) applyOsEnvOverrides(environ []string) error {
_, ok := allDecoderSetups[key]
if ok {
// A map.
if v, err := metadecoders.Default.UnmarshalStringTo(env.Value, map[string]any{}); err == nil {
if v, err := metadecoders.Default.UnmarshalStringTo(env.Value, map[string]interface{}{}); err == nil {
val = v
}
}
+24 -96
View File
@@ -15,7 +15,6 @@ package config
import (
"fmt"
"net/http"
"regexp"
"sort"
"strings"
@@ -28,7 +27,6 @@ import (
"github.com/gohugoio/hugo/common/herrors"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cast"
"slices"
)
type BaseConfig struct {
@@ -129,7 +127,7 @@ func (w BuildStats) Enabled() bool {
}
func (b BuildConfig) clone() BuildConfig {
b.CacheBusters = slices.Clone(b.CacheBusters)
b.CacheBusters = append([]CacheBuster{}, b.CacheBusters...)
return b
}
@@ -228,22 +226,7 @@ type Server struct {
Redirects []Redirect
compiledHeaders []glob.Glob
compiledRedirects []redirect
}
type redirect struct {
from glob.Glob
fromRe *regexp.Regexp
headers map[string]glob.Glob
}
func (r redirect) matchHeader(header http.Header) bool {
for k, v := range r.headers {
if !v.Match(header.Get(k)) {
return false
}
}
return true
compiledRedirects []glob.Glob
}
func (s *Server) CompileConfig(logger loggers.Logger) error {
@@ -251,41 +234,10 @@ func (s *Server) CompileConfig(logger loggers.Logger) error {
return nil
}
for _, h := range s.Headers {
g, err := glob.Compile(h.For)
if err != nil {
return fmt.Errorf("failed to compile Headers glob %q: %w", h.For, err)
}
s.compiledHeaders = append(s.compiledHeaders, g)
s.compiledHeaders = append(s.compiledHeaders, glob.MustCompile(h.For))
}
for _, r := range s.Redirects {
if r.From == "" && r.FromRe == "" {
return fmt.Errorf("redirects must have either From or FromRe set")
}
rd := redirect{
headers: make(map[string]glob.Glob),
}
if r.From != "" {
g, err := glob.Compile(r.From)
if err != nil {
return fmt.Errorf("failed to compile Redirect glob %q: %w", r.From, err)
}
rd.from = g
}
if r.FromRe != "" {
re, err := regexp.Compile(r.FromRe)
if err != nil {
return fmt.Errorf("failed to compile Redirect regexp %q: %w", r.FromRe, err)
}
rd.fromRe = re
}
for k, v := range r.FromHeaders {
g, err := glob.Compile(v)
if err != nil {
return fmt.Errorf("failed to compile Redirect header glob %q: %w", v, err)
}
rd.headers[k] = g
}
s.compiledRedirects = append(s.compiledRedirects, rd)
s.compiledRedirects = append(s.compiledRedirects, glob.MustCompile(r.From))
}
return nil
@@ -314,42 +266,22 @@ func (s *Server) MatchHeaders(pattern string) []types.KeyValueStr {
return matches
}
func (s *Server) MatchRedirect(pattern string, header http.Header) Redirect {
func (s *Server) MatchRedirect(pattern string) Redirect {
if s.compiledRedirects == nil {
return Redirect{}
}
pattern = strings.TrimSuffix(pattern, "index.html")
for i, r := range s.compiledRedirects {
for i, g := range s.compiledRedirects {
redir := s.Redirects[i]
var found bool
if r.from != nil {
if r.from.Match(pattern) {
found = header == nil || r.matchHeader(header)
// We need to do regexp group replacements if needed.
}
// No redirect to self.
if redir.To == pattern {
return Redirect{}
}
if r.fromRe != nil {
m := r.fromRe.FindStringSubmatch(pattern)
if m != nil {
if !found {
found = header == nil || r.matchHeader(header)
}
if found {
// Replace $1, $2 etc. in To.
for i, g := range m[1:] {
redir.To = strings.ReplaceAll(redir.To, fmt.Sprintf("$%d", i+1), g)
}
}
}
}
if found {
if g.Match(pattern) {
return redir
}
}
@@ -363,22 +295,8 @@ type Headers struct {
}
type Redirect struct {
// From is the Glob pattern to match.
// One of From or FromRe must be set.
From string
// FromRe is the regexp to match.
// This regexp can contain group matches (e.g. $1) that can be used in the To field.
// One of From or FromRe must be set.
FromRe string
// To is the target URL.
To string
// Headers to match for the redirect.
// This maps the HTTP header name to a Glob pattern with values to match.
// If the map is empty, the redirect will always be triggered.
FromHeaders map[string]string
To string
// HTTP status code to use for the redirect.
// A status code of 200 will trigger a URL rewrite.
@@ -451,7 +369,7 @@ func (c *CacheBuster) CompileConfig(logger loggers.Logger) error {
}
func (r Redirect) IsZero() bool {
return r.From == "" && r.FromRe == ""
return r.From == ""
}
const (
@@ -465,7 +383,17 @@ func DecodeServer(cfg Provider) (Server, error) {
_ = mapstructure.WeakDecode(cfg.GetStringMap("server"), s)
for i, redir := range s.Redirects {
redir.To = strings.TrimSuffix(redir.To, "index.html")
// Get it in line with the Hugo server for OK responses.
// We currently treat the 404 as a special case, they are always "ugly", so keep them as is.
if redir.Status != 404 {
redir.To = strings.TrimSuffix(redir.To, "index.html")
if !strings.HasPrefix(redir.To, "https") && !strings.HasSuffix(redir.To, "/") {
// There are some tricky infinite loop situations when dealing
// when the target does not have a trailing slash.
// This can certainly be handled better, but not time for that now.
return Server{}, fmt.Errorf("unsupported redirect to value %q in server config; currently this must be either a remote destination or a local folder, e.g. \"/blog/\" or \"/blog/index.html\"", redir.To)
}
}
s.Redirects[i] = redir
}
@@ -473,7 +401,7 @@ func DecodeServer(cfg Provider) (Server, error) {
// Set up a default redirect for 404s.
s.Redirects = []Redirect{
{
From: "/**",
From: "**",
To: "/404.html",
Status: 404,
},
+36 -42
View File
@@ -71,28 +71,7 @@ X-Content-Type-Options = "nosniff"
[[server.redirects]]
from = "/foo/**"
to = "/baz/index.html"
status = 200
[[server.redirects]]
from = "/loop/**"
to = "/loop/foo/"
status = 200
[[server.redirects]]
from = "/b/**"
fromRe = "/b/(.*)/"
to = "/baz/$1/"
status = 200
[[server.redirects]]
fromRe = "/c/(.*)/"
to = "/boo/$1/"
status = 200
[[server.redirects]]
fromRe = "/d/(.*)/"
to = "/boo/$1/"
to = "/foo/index.html"
status = 200
[[server.redirects]]
@@ -100,6 +79,11 @@ from = "/google/**"
to = "https://google.com/"
status = 301
[[server.redirects]]
from = "/**"
to = "/default/index.html"
status = 301
`, "toml")
@@ -116,35 +100,45 @@ status = 301
{Key: "X-XSS-Protection", Value: "1; mode=block"},
})
c.Assert(s.MatchRedirect("/foo/bar/baz", nil), qt.DeepEquals, Redirect{
c.Assert(s.MatchRedirect("/foo/bar/baz"), qt.DeepEquals, Redirect{
From: "/foo/**",
To: "/baz/",
To: "/foo/",
Status: 200,
})
c.Assert(s.MatchRedirect("/foo/bar/", nil), qt.DeepEquals, Redirect{
From: "/foo/**",
To: "/baz/",
Status: 200,
c.Assert(s.MatchRedirect("/someother"), qt.DeepEquals, Redirect{
From: "/**",
To: "/default/",
Status: 301,
})
c.Assert(s.MatchRedirect("/b/c/", nil), qt.DeepEquals, Redirect{
From: "/b/**",
FromRe: "/b/(.*)/",
To: "/baz/c/",
Status: 200,
})
c.Assert(s.MatchRedirect("/c/d/", nil).To, qt.Equals, "/boo/d/")
c.Assert(s.MatchRedirect("/c/d/e/", nil).To, qt.Equals, "/boo/d/e/")
c.Assert(s.MatchRedirect("/someother", nil), qt.DeepEquals, Redirect{})
c.Assert(s.MatchRedirect("/google/foo", nil), qt.DeepEquals, Redirect{
c.Assert(s.MatchRedirect("/google/foo"), qt.DeepEquals, Redirect{
From: "/google/**",
To: "https://google.com/",
Status: 301,
})
// No redirect loop, please.
c.Assert(s.MatchRedirect("/default/index.html"), qt.DeepEquals, Redirect{})
c.Assert(s.MatchRedirect("/default/"), qt.DeepEquals, Redirect{})
for _, errorCase := range []string{
`[[server.redirects]]
from = "/**"
to = "/file"
status = 301`,
`[[server.redirects]]
from = "/**"
to = "/foo/file.html"
status = 301`,
} {
cfg, err := FromConfigString(errorCase, "toml")
c.Assert(err, qt.IsNil)
_, err = DecodeServer(cfg)
c.Assert(err, qt.Not(qt.IsNil))
}
}
func TestBuildConfigCacheBusters(t *testing.T) {
@@ -166,7 +160,7 @@ func TestBuildConfigCacheBusters(t *testing.T) {
func TestBuildConfigCacheBusterstTailwindSetup(t *testing.T) {
c := qt.New(t)
cfg := New()
cfg.Set("build", map[string]any{
cfg.Set("build", map[string]interface{}{
"cacheBusters": []map[string]string{
{
"source": "assets/watching/hugo_stats\\.json",
+38 -1
View File
@@ -15,6 +15,7 @@ package config
import (
"fmt"
"sort"
"strings"
"sync"
@@ -25,6 +26,42 @@ import (
"github.com/gohugoio/hugo/common/maps"
)
var (
// ConfigRootKeysSet contains all of the config map root keys.
ConfigRootKeysSet = map[string]bool{
"build": true,
"caches": true,
"cascade": true,
"frontmatter": true,
"languages": true,
"imaging": true,
"markup": true,
"mediatypes": true,
"menus": true,
"minify": true,
"module": true,
"outputformats": true,
"params": true,
"permalinks": true,
"related": true,
"sitemap": true,
"privacy": true,
"security": true,
"taxonomies": true,
}
// ConfigRootKeys is a sorted version of ConfigRootKeysSet.
ConfigRootKeys []string
)
func init() {
for k := range ConfigRootKeysSet {
ConfigRootKeys = append(ConfigRootKeys, k)
}
sort.Strings(ConfigRootKeys)
}
// New creates a Provider backed by an empty maps.Params.
func New() Provider {
return &defaultConfigProvider{
@@ -345,7 +382,7 @@ func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (str
c.keyCache.Store(key, parts)
}
current := c.root
for i := range len(parts) - 1 {
for i := 0; i < len(parts)-1; i++ {
next, found := current[parts[i]]
if !found {
if create {
+1 -1
View File
@@ -332,7 +332,7 @@ func TestDefaultConfigProvider(t *testing.T) {
return nil
}
for i := range 20 {
for i := 0; i < 20; i++ {
i := i
r.Run(func() error {
const v = 42
+2 -2
View File
@@ -29,7 +29,7 @@ func TestNamespace(t *testing.T) {
// ns, err := config.DecodeNamespace[map[string]DocsMediaTypeConfig](in, defaultMediaTypesConfig, buildConfig)
ns, err := DecodeNamespace[[]*tstNsExt](
map[string]any{"foo": "bar"},
map[string]interface{}{"foo": "bar"},
func(v any) (*tstNsExt, any, error) {
t := &tstNsExt{}
m, err := maps.ToStringMapE(v)
@@ -42,7 +42,7 @@ func TestNamespace(t *testing.T) {
c.Assert(err, qt.IsNil)
c.Assert(ns, qt.Not(qt.IsNil))
c.Assert(ns.SourceStructure, qt.DeepEquals, map[string]any{"foo": "bar"})
c.Assert(ns.SourceStructure, qt.DeepEquals, map[string]interface{}{"foo": "bar"})
c.Assert(ns.SourceHash, qt.Equals, "1420f6c7782f7459")
c.Assert(ns.Config, qt.DeepEquals, &tstNsExt{Foo: "bar"})
c.Assert(ns.Signature(), qt.DeepEquals, []*tstNsExt(nil))
+6 -2
View File
@@ -36,6 +36,10 @@ respectDoNotTrack = true
[privacy.instagram]
disable = true
simple = true
[privacy.twitter]
disable = true
enableDNT = true
simple = true
[privacy.x]
disable = true
enableDNT = true
@@ -59,8 +63,8 @@ simple = true
got := []bool{
pc.Disqus.Disable, pc.GoogleAnalytics.Disable,
pc.GoogleAnalytics.RespectDoNotTrack, pc.Instagram.Disable,
pc.Instagram.Simple,
pc.Vimeo.Disable, pc.Vimeo.EnableDNT, pc.Vimeo.Simple,
pc.Instagram.Simple, pc.Twitter.Disable, pc.Twitter.EnableDNT,
pc.Twitter.Simple, pc.Vimeo.Disable, pc.Vimeo.EnableDNT, pc.Vimeo.Simple,
pc.YouTube.PrivacyEnhanced, pc.YouTube.Disable, pc.X.Disable, pc.X.EnableDNT,
pc.X.Simple,
}
+1 -1
View File
@@ -73,7 +73,7 @@ func NewWhitelist(patterns ...string) (Whitelist, error) {
var patternsr []*regexp.Regexp
for i := range patterns {
for i := 0; i < len(patterns); i++ {
p := strings.TrimSpace(patterns[i])
if p == "" {
continue
+2 -2
View File
@@ -291,7 +291,7 @@ func (b *contentBuilder) applyArcheType(contentFilename string, archetypeFi hugo
func (b *contentBuilder) mapArcheTypeDir() error {
var m archetypeMap
seen := map[hstrings.Strings2]bool{}
seen := map[hstrings.Tuple]bool{}
walkFn := func(path string, fim hugofs.FileMetaInfo) error {
if fim.IsDir() {
@@ -301,7 +301,7 @@ func (b *contentBuilder) mapArcheTypeDir() error {
pi := fim.Meta().PathInfo
if pi.IsContent() {
pathLang := hstrings.Strings2{pi.PathBeforeLangAndOutputFormatAndExt(), fim.Meta().Lang}
pathLang := hstrings.Tuple{First: pi.PathNoIdentifier(), Second: fim.Meta().Lang}
if seen[pathLang] {
// Duplicate content file, e.g. page.md and page.html.
// In the regular build, we will filter out the duplicates, but
+1 -1
View File
@@ -129,7 +129,7 @@ site RegularPages: {{ len site.RegularPages }}
`
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), fmt.Appendf(nil, contentFile, "index.md"), 0o755), qt.IsNil)
c.Assert(afero.WriteFile(mm, filepath.Join(archetypeDir, "index.md"), []byte(fmt.Sprintf(contentFile, "index.md")), 0o755), qt.IsNil)
c.Assert(afero.WriteFile(mm, filepath.Join(defaultArchetypeDir, "index.md"), []byte("default archetype index.md"), 0o755), qt.IsNil)
c.Assert(initFs(mm), qt.IsNil)
+17 -67
View File
@@ -34,60 +34,10 @@ var siteFs embed.FS
var themeFs embed.FS
// CreateTheme creates a theme skeleton.
func CreateTheme(createpath string, sourceFs afero.Fs, format string) error {
func CreateTheme(createpath string, sourceFs afero.Fs) error {
if exists, _ := helpers.Exists(createpath, sourceFs); exists {
return errors.New(createpath + " already exists")
}
format = strings.ToLower(format)
siteConfig := map[string]any{
"baseURL": "https://example.org/",
"languageCode": "en-US",
"title": "My New Hugo Site",
"menus": map[string]any{
"main": []any{
map[string]any{
"name": "Home",
"pageRef": "/",
"weight": 10,
},
map[string]any{
"name": "Posts",
"pageRef": "/posts",
"weight": 20,
},
map[string]any{
"name": "Tags",
"pageRef": "/tags",
"weight": 30,
},
},
},
"module": map[string]any{
"hugoVersion": map[string]any{
"extended": false,
"min": "0.146.0",
},
},
}
err := createSiteConfig(sourceFs, createpath, siteConfig, format)
if err != nil {
return err
}
defaultArchetype := map[string]any{
"title": "{{ replace .File.ContentBaseName \"-\" \" \" | title }}",
"date": "{{ .Date }}",
"draft": true,
}
err = createDefaultArchetype(sourceFs, createpath, defaultArchetype, format)
if err != nil {
return err
}
return copyFiles(createpath, sourceFs, themeFs)
}
@@ -121,24 +71,12 @@ func CreateSite(createpath string, sourceFs afero.Fs, force bool, format string)
}
}
siteConfig := map[string]any{
"baseURL": "https://example.org/",
"title": "My New Hugo Site",
"languageCode": "en-us",
}
err := createSiteConfig(sourceFs, createpath, siteConfig, format)
err := newSiteCreateConfig(sourceFs, createpath, format)
if err != nil {
return err
}
defaultArchetype := map[string]any{
"title": "{{ replace .File.ContentBaseName \"-\" \" \" | title }}",
"date": "{{ .Date }}",
"draft": true,
}
err = createDefaultArchetype(sourceFs, createpath, defaultArchetype, format)
err = newSiteCreateArchetype(sourceFs, createpath, format)
if err != nil {
return err
}
@@ -161,7 +99,13 @@ func copyFiles(createpath string, sourceFs afero.Fs, skeleton embed.FS) error {
})
}
func createSiteConfig(fs afero.Fs, createpath string, in map[string]any, format string) (err error) {
func newSiteCreateConfig(fs afero.Fs, createpath string, format string) (err error) {
in := map[string]string{
"baseURL": "https://example.org/",
"title": "My New Hugo Site",
"languageCode": "en-us",
}
var buf bytes.Buffer
err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(format), &buf)
if err != nil {
@@ -171,7 +115,13 @@ func createSiteConfig(fs afero.Fs, createpath string, in map[string]any, format
return helpers.WriteToDisk(filepath.Join(createpath, "hugo."+format), &buf, fs)
}
func createDefaultArchetype(fs afero.Fs, createpath string, in map[string]any, format string) (err error) {
func newSiteCreateArchetype(fs afero.Fs, createpath string, format string) (err error) {
in := map[string]any{
"title": "{{ replace .File.ContentBaseName \"-\" \" \" | title }}",
"date": "{{ .Date }}",
"draft": true,
}
var buf bytes.Buffer
err = parser.InterfaceToFrontMatter(in, metadecoders.FormatFromString(format), &buf)
if err != nil {
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) [year] [fullname]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+7
View File
@@ -0,0 +1,7 @@
# Theme Name
## Features
## Installation
## Configuration
@@ -0,0 +1,5 @@
+++
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
date = {{ .Date }}
draft = true
+++
+23
View File
@@ -0,0 +1,23 @@
baseURL = 'https://example.org/'
languageCode = 'en-US'
title = 'My New Hugo Site'
[[menus.main]]
name = 'Home'
pageRef = '/'
weight = 10
[[menus.main]]
name = 'Posts'
pageRef = '/posts'
weight = 20
[[menus.main]]
name = 'Tags'
pageRef = '/tags'
weight = 30
[module]
[module.hugoVersion]
extended = false
min = "0.116.0"
@@ -1,16 +0,0 @@
{{- with resources.Get "js/main.js" }}
{{- $opts := dict
"minify" (not hugo.IsDevelopment)
"sourceMap" (cond hugo.IsDevelopment "external" "")
"targetPath" "js/main.js"
}}
{{- with . | js.Build $opts }}
{{- if hugo.IsDevelopment }}
<script src="{{ .RelPermalink }}"></script>
{{- else }}
{{- with . | fingerprint }}
<script src="{{ .RelPermalink }}" integrity="{{ .Data.Integrity }}" crossorigin="anonymous"></script>
{{- end }}
{{- end }}
{{- end }}
{{- end }}
@@ -1,5 +1,5 @@
{{- with resources.Get "css/main.css" }}
{{- if hugo.IsDevelopment }}
{{- if eq hugo.Environment "development" }}
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{- else }}
{{- with . | minify | fingerprint }}
@@ -0,0 +1,12 @@
{{- with resources.Get "js/main.js" }}
{{- if eq hugo.Environment "development" }}
{{- with . | js.Build }}
<script src="{{ .RelPermalink }}"></script>
{{- end }}
{{- else }}
{{- $opts := dict "minify" true }}
{{- with . | js.Build $opts | fingerprint }}
<script src="{{ .RelPermalink }}" integrity="{{- .Data.Integrity }}" crossorigin="anonymous"></script>
{{- end }}
{{- end }}
{{- end }}
@@ -18,7 +18,7 @@ Renders a menu for the given menu ID.
</nav>
{{- end }}
{{- define "_partials/inline/menu/walk.html" }}
{{- define "partials/inline/menu/walk.html" }}
{{- $page := .page }}
{{- range .menuEntries }}
{{- $attrs := dict "href" .URL }}
@@ -1,7 +0,0 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
{{ .Content }}
{{ range .Pages }}
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
{{ end }}
{{ end }}
-7
View File
@@ -1,7 +0,0 @@
{{ define "main" }}
<h1>{{ .Title }}</h1>
{{ .Content }}
{{ range .Pages }}
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
{{ end }}
{{ end }}
+31
View File
@@ -0,0 +1,31 @@
name = 'Theme name'
license = 'MIT'
licenselink = 'https://github.com/owner/repo/LICENSE'
description = 'Theme description'
# The home page of the theme, where the source can be found
homepage = 'https://github.com/owner/repo'
# If you have a running demo of the theme
demosite = 'https://owner.github.io/repo'
# Taxonomy terms
tags = ['blog', 'company']
features = ['some', 'awesome', 'features']
# If the theme has multiple authors
authors = [
{name = 'Name of author', homepage = 'Website of author'},
{name = 'Name of author', homepage = 'Website of author'}
]
# If the theme has a single author
[author]
name = 'Your name'
homepage = 'Your website'
# If porting an existing theme
[original]
author = 'Name of original author'
homepage = 'Website of original author'
repo = 'https://github.com/owner/repo'
+1
View File
@@ -12,6 +12,7 @@
// limitations under the License.
//go:build withdeploy
// +build withdeploy
package deploy
+1
View File
@@ -12,6 +12,7 @@
// limitations under the License.
//go:build withdeploy
// +build withdeploy
package deploy
+1
View File
@@ -12,6 +12,7 @@
// limitations under the License.
//go:build !solaris && withdeploy
// +build !solaris,withdeploy
package deploy
+1
View File
@@ -12,6 +12,7 @@
// limitations under the License.
//go:build withdeploy
// +build withdeploy
package deploy
+1
View File
@@ -12,6 +12,7 @@
// limitations under the License.
//go:build withdeploy
// +build withdeploy
package deployconfig
+1
View File
@@ -12,6 +12,7 @@
// limitations under the License.
//go:build withdeploy
// +build withdeploy
package deploy
+28 -19
View File
@@ -29,7 +29,6 @@ import (
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/postpub"
"github.com/gohugoio/hugo/tpl/tplimpl"
"github.com/gohugoio/hugo/metrics"
"github.com/gohugoio/hugo/resources"
@@ -47,6 +46,12 @@ type Deps struct {
ExecHelper *hexec.Exec
// The templates to use. This will usually implement the full tpl.TemplateManager.
tmplHandlers *tpl.TemplateHandlers
// The template funcs.
TmplFuncMap map[string]any
// The file systems to use.
Fs *hugofs.Fs `json:"-"`
@@ -74,8 +79,7 @@ type Deps struct {
// The site building.
Site page.Site
TemplateStore *tplimpl.TemplateStore
TemplateProvider ResourceProvider
// Used in tests
OverloadedTemplateFuncs map[string]any
@@ -98,9 +102,6 @@ type Deps struct {
// This is common/global for all sites.
BuildState *BuildState
// Misc counters.
Counters *Counters
// Holds RPC dispatchers for Katex etc.
// TODO(bep) rethink this re. a plugin setup, but this will have to do for now.
WasmDispatchers *warpc.Dispatchers
@@ -108,6 +109,9 @@ type Deps struct {
// The JS batcher client.
JSBatcherClient js.BatcherClient
// The JS batcher client.
// JSBatcherClient *esbuild.BatcherClient
isClosed bool
*globalErrHandler
@@ -126,8 +130,8 @@ func (d Deps) Clone(s page.Site, conf config.AllProvider) (*Deps, error) {
return &d, nil
}
func (d *Deps) GetTemplateStore() *tplimpl.TemplateStore {
return d.TemplateStore
func (d *Deps) SetTempl(t *tpl.TemplateHandlers) {
d.tmplHandlers = t
}
func (d *Deps) Init() error {
@@ -149,12 +153,10 @@ func (d *Deps) Init() error {
logger: d.Log,
}
}
if d.BuildState == nil {
d.BuildState = &BuildState{}
}
if d.Counters == nil {
d.Counters = &Counters{}
}
if d.BuildState.DeferredExecutions == nil {
if d.BuildState.DeferredExecutionsGroupedByRenderingContext == nil {
d.BuildState.DeferredExecutionsGroupedByRenderingContext = make(map[tpl.RenderingContext]*DeferredExecutions)
@@ -261,17 +263,22 @@ func (d *Deps) Init() error {
return nil
}
// TODO(bep) rework this to get it in line with how we manage templates.
func (d *Deps) Compile(prototype *Deps) error {
var err error
if prototype == nil {
if err = d.TemplateProvider.NewResource(d); err != nil {
return err
}
if err = d.TranslationProvider.NewResource(d); err != nil {
return err
}
return nil
}
if err = d.TemplateProvider.CloneResource(d, prototype); err != nil {
return err
}
if err = d.TranslationProvider.CloneResource(d, prototype); err != nil {
return err
}
@@ -371,6 +378,14 @@ type ResourceProvider interface {
CloneResource(dst, src *Deps) error
}
func (d *Deps) Tmpl() tpl.TemplateHandler {
return d.tmplHandlers.Tmpl
}
func (d *Deps) TextTmpl() tpl.TemplateParseFinder {
return d.tmplHandlers.TxtTmpl
}
func (d *Deps) Close() error {
if d.isClosed {
return nil
@@ -439,12 +454,6 @@ type BuildState struct {
DeferredExecutionsGroupedByRenderingContext map[tpl.RenderingContext]*DeferredExecutions
}
// Misc counters.
type Counters struct {
// Counter for the math.Counter function.
MathCounter atomic.Uint64
}
type DeferredExecutions struct {
// A set of filenames in /public that
// contains a post-processing prefix.
-13
View File
@@ -1,13 +0,0 @@
# Config file for codespell.
# https://github.com/codespell-project/codespell#using-a-config-file
[codespell]
# Comma separated list of dirs to be skipped.
skip = _vendor,.cspell.json,chroma.css,chroma_dark.css
# Comma separated list of words to be ignored. Words must be lowercased.
ignore-words-list = abl,edn,te,ue,trys,januar,womens,crossreferences
# Check file names as well.
check-filenames = true

Some files were not shown because too many files have changed in this diff Show More