mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-26 16:28:52 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 378762b789 | |||
| 098c68fd18 |
@@ -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
@@ -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
|
||||
|
||||
|
||||
+4
-4
@@ -2,8 +2,8 @@
|
||||
# Twitter: https://twitter.com/gohugoio
|
||||
# Website: https://gohugo.io/
|
||||
|
||||
ARG GO_VERSION="1.24"
|
||||
ARG ALPINE_VERSION="3.22"
|
||||
ARG GO_VERSION="1.23.2"
|
||||
ARG ALPINE_VERSION="3.20"
|
||||
ARG DART_SASS_VERSION="1.79.3"
|
||||
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.5.0 AS xx
|
||||
@@ -19,7 +19,7 @@ RUN apk add clang lld
|
||||
COPY --from=xx / /
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
RUN xx-apk add musl-dev gcc g++
|
||||
RUN xx-apk add musl-dev gcc g++
|
||||
|
||||
# Optionally set HUGO_BUILD_TAGS to "none" or "withdeploy" when building like so:
|
||||
# docker build --build-arg HUGO_BUILD_TAGS=withdeploy .
|
||||
@@ -72,7 +72,7 @@ RUN mkdir -p /var/hugo/bin /cache && \
|
||||
adduser -Sg hugo -u 1000 -h /var/hugo hugo && \
|
||||
chown -R hugo: /var/hugo /cache && \
|
||||
# For the Hugo's Git integration to work.
|
||||
runuser -u hugo -- git config --global --add safe.directory /project && \
|
||||
runuser -u hugo -- git config --global --add safe.directory /project && \
|
||||
# See https://github.com/gohugoio/hugo/issues/9810
|
||||
runuser -u hugo -- git config --global core.quotepath false
|
||||
|
||||
|
||||
@@ -65,11 +65,9 @@ See the [features] section of the documentation for a comprehensive summary of H
|
||||
|
||||
<p> </p>
|
||||
<p float="left">
|
||||
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/hugoDocs/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
|
||||
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
|
||||
|
||||
<a href="https://www.jetbrains.com/go/?utm_source=OSS&utm_medium=referral&utm_campaign=hugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/hugoDocs/master/assets/images/sponsors/goland.svg" width="200" alt="The complete IDE crafted for professional Go developers."></a>
|
||||
|
||||
<a href="https://pinme.eth.limo/?s=hugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/hugoDocs/master/assets/images/sponsors/logo-pinme.svg" width="200" alt="PinMe."></a>
|
||||
<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
|
||||
@@ -82,10 +80,10 @@ Encode to the WebP format when [processing images]. You can decode WebP images w
|
||||
[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 [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/
|
||||
[dart sass]: /functions/css/sass/#dart-sass
|
||||
[processing images]: /content-management/image-processing/
|
||||
[transpile sass to css]: /functions/css/sass/
|
||||
[details]: /hosting-and-deployment/hugo-deploy/
|
||||
|
||||
Unless your specific deployment needs require the extended/deploy edition, we recommend the extended edition.
|
||||
|
||||
@@ -191,6 +189,7 @@ github.com/bep/simplecobra="v0.5.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"
|
||||
|
||||
Vendored
+4
-4
@@ -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
@@ -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 {
|
||||
|
||||
Vendored
+3
-3
@@ -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)
|
||||
|
||||
Vendored
+3
-25
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
Vendored
-31
@@ -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
@@ -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:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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...)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
+4
-14
@@ -50,7 +50,6 @@ func newGenCommand() *genCommand {
|
||||
highlightStyle string
|
||||
lineNumbersInlineStyle string
|
||||
lineNumbersTableStyle string
|
||||
omitEmpty bool
|
||||
)
|
||||
|
||||
newChromaStyles := func() simplecobra.Commander {
|
||||
@@ -80,14 +79,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 +95,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)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -184,13 +174,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)
|
||||
@@ -232,7 +222,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
|
||||
}
|
||||
|
||||
@@ -962,7 +962,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
lrl.Logf("no page to navigate to, force refresh")
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
} else if len(otherChanges) > 0 || len(cssChanges) > 0 {
|
||||
} else if len(otherChanges) > 0 {
|
||||
if len(otherChanges) == 1 {
|
||||
// Allow single changes to be refreshed without a full page reload.
|
||||
pathToRefresh := h.PathSpec.RelURL(paths.ToSlashTrimLeading(otherChanges[0]), false)
|
||||
@@ -972,9 +972,6 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
lrl.Logf("force refresh")
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
} else {
|
||||
lrl.Logf("force refresh")
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
|
||||
if len(cssChanges) > 0 {
|
||||
|
||||
+11
-14
@@ -76,8 +76,10 @@ Ensure you run this within the root directory of your site.`,
|
||||
&simpleCommand{
|
||||
name: "site",
|
||||
use: "site [path]",
|
||||
short: "Create a new site",
|
||||
long: `Create a new site at the specified path.`,
|
||||
short: "Create a new site (skeleton)",
|
||||
long: `Create a new site in the provided directory.
|
||||
The new site will have the correct structure, but no content or theme yet.
|
||||
Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return newUserError("path needs to be provided")
|
||||
@@ -122,9 +124,11 @@ Ensure you run this within the root directory of your site.`,
|
||||
&simpleCommand{
|
||||
name: "theme",
|
||||
use: "theme [name]",
|
||||
short: "Create a new theme",
|
||||
long: `Create a new theme with the specified name in the ./themes directory.
|
||||
This generates a functional theme including template examples and sample content.`,
|
||||
short: "Create a new theme (skeleton)",
|
||||
long: `Create a new theme (skeleton) called [name] in ./themes.
|
||||
New theme is a skeleton. Please add content to the touched files. Add your
|
||||
name to the copyright line in the license and adjust the theme.toml file
|
||||
according to your needs.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return newUserError("theme name needs to be provided")
|
||||
@@ -140,7 +144,7 @@ This generates a functional theme including template examples and sample content
|
||||
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
|
||||
}
|
||||
@@ -148,14 +152,7 @@ This generates a functional theme including template examples and sample content
|
||||
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
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+12
-11
@@ -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"
|
||||
@@ -196,7 +195,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)
|
||||
}
|
||||
|
||||
@@ -627,7 +628,7 @@ func (c *serverCommand) setServerInfoInConfig() error {
|
||||
panic("no server ports set")
|
||||
}
|
||||
return c.withConfE(func(conf *commonConfig) error {
|
||||
for i, language := range conf.configs.LanguagesDefaultFirst {
|
||||
for i, language := range conf.configs.Languages {
|
||||
isMultihost := conf.configs.IsMultihost
|
||||
var serverPort int
|
||||
if isMultihost {
|
||||
@@ -758,7 +759,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 +898,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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ package collections
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
@@ -78,7 +77,6 @@ func TestAppend(t *testing.T) {
|
||||
{[]string{"a", "b"}, []any{nil}, []any{"a", "b", nil}},
|
||||
{[]string{"a", "b"}, []any{nil, "d", nil}, []any{"a", "b", nil, "d", nil}},
|
||||
{[]any{"a", nil, "c"}, []any{"d", nil, "f"}, []any{"a", nil, "c", "d", nil, "f"}},
|
||||
{[]string{"a", "b"}, []any{}, []string{"a", "b"}},
|
||||
} {
|
||||
|
||||
result, err := Append(test.start, test.addend...)
|
||||
@@ -148,66 +146,3 @@ func TestAppendShouldMakeACopyOfTheInputSlice(t *testing.T) {
|
||||
c.Assert(result, qt.DeepEquals, []string{"a", "b", "c"})
|
||||
c.Assert(slice, qt.DeepEquals, []string{"d", "b"})
|
||||
}
|
||||
|
||||
func TestIndirect(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
type testStruct struct {
|
||||
Field string
|
||||
}
|
||||
|
||||
var (
|
||||
nilPtr *testStruct
|
||||
nilIface interface{} = nil
|
||||
nonNilIface interface{} = &testStruct{Field: "hello"}
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input any
|
||||
wantKind reflect.Kind
|
||||
wantNil bool
|
||||
}{
|
||||
{
|
||||
name: "nil pointer",
|
||||
input: nilPtr,
|
||||
wantKind: reflect.Ptr,
|
||||
wantNil: true,
|
||||
},
|
||||
{
|
||||
name: "nil interface",
|
||||
input: nilIface,
|
||||
wantKind: reflect.Invalid,
|
||||
wantNil: false,
|
||||
},
|
||||
{
|
||||
name: "non-nil pointer to struct",
|
||||
input: &testStruct{Field: "abc"},
|
||||
wantKind: reflect.Struct,
|
||||
wantNil: false,
|
||||
},
|
||||
{
|
||||
name: "non-nil interface holding pointer",
|
||||
input: nonNilIface,
|
||||
wantKind: reflect.Struct,
|
||||
wantNil: false,
|
||||
},
|
||||
{
|
||||
name: "plain value",
|
||||
input: testStruct{Field: "xyz"},
|
||||
wantKind: reflect.Struct,
|
||||
wantNil: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := reflect.ValueOf(tt.input)
|
||||
got, isNil := indirect(v)
|
||||
|
||||
c.Assert(got.Kind(), qt.Equals, tt.wantKind)
|
||||
c.Assert(isNil, qt.Equals, tt.wantNil)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,37 +136,3 @@ func TestSortedStringSlice(t *testing.T) {
|
||||
c.Assert(s.Count("z"), qt.Equals, 0)
|
||||
c.Assert(s.Count("a"), qt.Equals, 1)
|
||||
}
|
||||
|
||||
func TestStringSliceToInterfaceSlice(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
in []string
|
||||
want []any
|
||||
}{
|
||||
{
|
||||
name: "empty slice",
|
||||
in: []string{},
|
||||
want: []any{},
|
||||
},
|
||||
{
|
||||
name: "single element",
|
||||
in: []string{"hello"},
|
||||
want: []any{"hello"},
|
||||
},
|
||||
{
|
||||
name: "multiple elements",
|
||||
in: []string{"a", "b", "c"},
|
||||
want: []any{"a", "b", "c"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := StringSliceToInterfaceSlice(tt.in)
|
||||
c.Assert(got, qt.DeepEquals, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,77 +0,0 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestNewStack(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
s := NewStack[int]()
|
||||
|
||||
c.Assert(s, qt.IsNotNil)
|
||||
}
|
||||
|
||||
func TestStackBasic(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
s := NewStack[int]()
|
||||
|
||||
c.Assert(s.Len(), qt.Equals, 0)
|
||||
|
||||
s.Push(1)
|
||||
s.Push(2)
|
||||
s.Push(3)
|
||||
|
||||
c.Assert(s.Len(), qt.Equals, 3)
|
||||
|
||||
top, ok := s.Peek()
|
||||
c.Assert(ok, qt.Equals, true)
|
||||
c.Assert(top, qt.Equals, 3)
|
||||
|
||||
popped, ok := s.Pop()
|
||||
c.Assert(ok, qt.Equals, true)
|
||||
c.Assert(popped, qt.Equals, 3)
|
||||
|
||||
c.Assert(s.Len(), qt.Equals, 2)
|
||||
|
||||
_, _ = s.Pop()
|
||||
_, _ = s.Pop()
|
||||
_, ok = s.Pop()
|
||||
|
||||
c.Assert(ok, qt.Equals, false)
|
||||
}
|
||||
|
||||
func TestStackDrain(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
s := NewStack[string]()
|
||||
s.Push("a")
|
||||
s.Push("b")
|
||||
|
||||
got := s.Drain()
|
||||
|
||||
c.Assert(got, qt.DeepEquals, []string{"a", "b"})
|
||||
c.Assert(s.Len(), qt.Equals, 0)
|
||||
}
|
||||
|
||||
func TestStackDrainMatching(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
s := NewStack[int]()
|
||||
s.Push(1)
|
||||
s.Push(2)
|
||||
s.Push(3)
|
||||
s.Push(4)
|
||||
|
||||
got := s.DrainMatching(func(v int) bool { return v%2 == 0 })
|
||||
|
||||
c.Assert(got, qt.DeepEquals, []int{4, 2})
|
||||
c.Assert(s.Drain(), qt.DeepEquals, []int{1, 3})
|
||||
}
|
||||
@@ -23,8 +23,6 @@ const (
|
||||
WarnFrontMatterParamsOverrides = "warning-frontmatter-params-overrides"
|
||||
WarnRenderShortcodesInHTML = "warning-rendershortcodes-in-html"
|
||||
WarnGoldmarkRawHTML = "warning-goldmark-raw-html"
|
||||
WarnPartialSuperfluousPrefix = "warning-partial-superfluous-prefix"
|
||||
WarnHomePageIsLeafBundle = "warning-home-page-is-leaf-bundle"
|
||||
)
|
||||
|
||||
// Field/method names with special meaning.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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, ¬FoundErr)
|
||||
}
|
||||
|
||||
// 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 ""
|
||||
}
|
||||
|
||||
@@ -134,7 +134,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 +157,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 +245,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
|
||||
|
||||
@@ -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 +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
|
||||
}
|
||||
|
||||
@@ -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
-22
@@ -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,28 +411,20 @@ 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)
|
||||
DeprecateLevel(item, alternative, version, level)
|
||||
}
|
||||
|
||||
// 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())
|
||||
level := deprecationLogLevelFromVersion(version)
|
||||
if level < minLevel {
|
||||
level = minLevel
|
||||
}
|
||||
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)
|
||||
@@ -444,7 +432,7 @@ func deprecateLevelWithLogger(item, alternative, version string, level logg.Leve
|
||||
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.
|
||||
|
||||
@@ -17,7 +17,7 @@ package hugo
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 149,
|
||||
Minor: 145,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
+1
-20
@@ -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()
|
||||
}
|
||||
|
||||
+1
-12
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
+97
-307
@@ -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,101 +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, numDots int, isLast bool) {
|
||||
if p.posContainerHigh != -1 {
|
||||
return
|
||||
}
|
||||
mayHaveLang := numDots > 1 && p.posIdentifierLanguage == -1 && pp.LanguageIndex != nil
|
||||
mayHaveLang = mayHaveLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
|
||||
mayHaveOutputFormat := component == files.ComponentFolderLayouts
|
||||
mayHaveKind := p.posIdentifierKind == -1 && mayHaveOutputFormat
|
||||
var mayHaveLayout bool
|
||||
if p.pathType == TypeShortcode {
|
||||
mayHaveLayout = !isLast && component == files.ComponentFolderLayouts
|
||||
} else {
|
||||
mayHaveLayout = component == files.ComponentFolderLayouts
|
||||
}
|
||||
|
||||
var found bool
|
||||
var high int
|
||||
if len(p.identifiersKnown) > 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.identifiersKnown) == 0 {
|
||||
// The first is always the extension.
|
||||
p.identifiersKnown = append(p.identifiersKnown, 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.identifiersKnown = append(p.identifiersKnown, id)
|
||||
p.posIdentifierLanguage = len(p.identifiersKnown) - 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.identifiersKnown = append(p.identifiersKnown, id)
|
||||
p.posIdentifierOutputFormat = len(p.identifiersKnown) - 1
|
||||
}
|
||||
}
|
||||
|
||||
if !found && mayHaveKind {
|
||||
if kinds.GetKindMain(sid) != "" {
|
||||
found = true
|
||||
p.identifiersKnown = append(p.identifiersKnown, id)
|
||||
p.posIdentifierKind = len(p.identifiersKnown) - 1
|
||||
}
|
||||
}
|
||||
|
||||
if !found && sid == identifierBaseof {
|
||||
found = true
|
||||
p.identifiersKnown = append(p.identifiersKnown, id)
|
||||
p.posIdentifierBaseof = len(p.identifiersKnown) - 1
|
||||
}
|
||||
|
||||
if !found && mayHaveLayout {
|
||||
p.identifiersKnown = append(p.identifiersKnown, id)
|
||||
p.posIdentifierLayout = len(p.identifiersKnown) - 1
|
||||
found = true
|
||||
}
|
||||
|
||||
if !found {
|
||||
p.identifiersUnknown = append(p.identifiersUnknown, id)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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 == "." {
|
||||
@@ -237,26 +140,46 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
|
||||
|
||||
p.s = s
|
||||
slashCount := 0
|
||||
lastDot := 0
|
||||
lastSlashIdx := strings.LastIndex(s, "/")
|
||||
numDots := strings.Count(s[lastSlashIdx+1:], ".")
|
||||
if strings.Contains(s, "/_shortcodes/") {
|
||||
p.pathType = TypeShortcode
|
||||
}
|
||||
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
c := s[i]
|
||||
|
||||
switch c {
|
||||
case '.':
|
||||
pp.parseIdentifier(component, s, p, i, lastDot, numDots, false)
|
||||
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, numDots, true)
|
||||
}
|
||||
p.posContainerHigh = i + 1
|
||||
} else if p.posContainerLow == -1 {
|
||||
p.posContainerLow = i + 1
|
||||
@@ -267,52 +190,26 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.identifiersKnown) > 0 {
|
||||
if len(p.identifiers) > 0 {
|
||||
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
|
||||
isContent := isContentComponent && pp.IsContentExt(p.Ext())
|
||||
id := p.identifiersKnown[len(p.identifiersKnown)-1]
|
||||
|
||||
if id.Low > p.posContainerHigh {
|
||||
b := p.s[p.posContainerHigh : id.Low-1]
|
||||
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
|
||||
id := p.identifiers[len(p.identifiers)-1]
|
||||
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 p.pathType < TypeMarkup && 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.pathType == TypeShortcode && p.posIdentifierLayout != -1 {
|
||||
id := p.identifiersKnown[p.posIdentifierLayout]
|
||||
if id.Low == p.posContainerHigh {
|
||||
// First identifier is shortcode name.
|
||||
p.posIdentifierLayout = -1
|
||||
} else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
|
||||
p.bundleType = PathTypeContentData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,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 {
|
||||
@@ -369,18 +257,13 @@ type Path struct {
|
||||
posContainerHigh int
|
||||
posSectionHigh int
|
||||
|
||||
component string
|
||||
pathType Type
|
||||
component string
|
||||
bundleType PathType
|
||||
|
||||
identifiersKnown []types.LowHigh[string]
|
||||
identifiersUnknown []types.LowHigh[string]
|
||||
identifiers []types.LowHigh[string]
|
||||
|
||||
posIdentifierLanguage int
|
||||
posIdentifierOutputFormat int
|
||||
posIdentifierKind int
|
||||
posIdentifierLayout int
|
||||
posIdentifierBaseof int
|
||||
disabled bool
|
||||
posIdentifierLanguage int
|
||||
disabled bool
|
||||
|
||||
trimLeadingSlash bool
|
||||
|
||||
@@ -410,13 +293,9 @@ func (p *Path) reset() {
|
||||
p.posContainerHigh = -1
|
||||
p.posSectionHigh = -1
|
||||
p.component = ""
|
||||
p.pathType = 0
|
||||
p.identifiersKnown = p.identifiersKnown[:0]
|
||||
p.bundleType = 0
|
||||
p.identifiers = p.identifiers[:0]
|
||||
p.posIdentifierLanguage = -1
|
||||
p.posIdentifierOutputFormat = -1
|
||||
p.posIdentifierKind = -1
|
||||
p.posIdentifierLayout = -1
|
||||
p.posIdentifierBaseof = -1
|
||||
p.disabled = false
|
||||
p.trimLeadingSlash = false
|
||||
p.unnormalized = nil
|
||||
@@ -437,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()
|
||||
}
|
||||
|
||||
@@ -456,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 {
|
||||
@@ -483,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.
|
||||
@@ -503,7 +372,7 @@ func (p *Path) Name() string {
|
||||
// Name returns the last element of path without any extension.
|
||||
func (p *Path) NameNoExt() string {
|
||||
if i := p.identifierIndex(0); i != -1 {
|
||||
return p.s[p.posContainerHigh : p.identifiersKnown[i].Low-1]
|
||||
return p.s[p.posContainerHigh : p.identifiers[i].Low-1]
|
||||
}
|
||||
return p.s[p.posContainerHigh:]
|
||||
}
|
||||
@@ -515,7 +384,7 @@ func (p *Path) NameNoLang() string {
|
||||
return p.Name()
|
||||
}
|
||||
|
||||
return p.s[p.posContainerHigh:p.identifiersKnown[i].Low-1] + p.s[p.identifiersKnown[i].High:]
|
||||
return p.s[p.posContainerHigh:p.identifiers[i].Low-1] + p.s[p.identifiers[i].High:]
|
||||
}
|
||||
|
||||
// BaseNameNoIdentifier returns the logical base name for a resource without any identifier (e.g. no extension).
|
||||
@@ -529,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.identifiersKnown) > 0 {
|
||||
lastID := p.identifiersKnown[len(p.identifiersKnown)-1]
|
||||
if p.posContainerHigh == lastID.Low {
|
||||
// The last identifier is the name.
|
||||
return lastID
|
||||
}
|
||||
return types.LowHigh[string]{
|
||||
Low: p.posContainerHigh,
|
||||
High: p.identifiersKnown[len(p.identifiersKnown)-1].Low - 1,
|
||||
}
|
||||
}
|
||||
return types.LowHigh[string]{
|
||||
Low: p.posContainerHigh,
|
||||
High: len(p.s),
|
||||
if len(p.identifiers) > 0 {
|
||||
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.
|
||||
@@ -568,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
|
||||
@@ -588,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.identifiersKnown) == 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.identifiersKnown[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()
|
||||
@@ -636,42 +462,26 @@ 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:]
|
||||
}
|
||||
|
||||
func (p *Path) base(preserveExt, isBundle bool) string {
|
||||
if len(p.identifiersKnown) == 0 {
|
||||
if len(p.identifiers) == 0 {
|
||||
return p.norm(p.s)
|
||||
}
|
||||
|
||||
if preserveExt && len(p.identifiersKnown) == 1 {
|
||||
if preserveExt && len(p.identifiers) == 1 {
|
||||
// Preserve extension.
|
||||
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 {
|
||||
@@ -683,7 +493,7 @@ func (p *Path) base(preserveExt, isBundle bool) string {
|
||||
}
|
||||
|
||||
// For txt files etc. we want to preserve the extension.
|
||||
id := p.identifiersKnown[0]
|
||||
id = p.identifiers[0]
|
||||
|
||||
return p.norm(p.s[:high] + p.s[id.Low-1:id.High])
|
||||
}
|
||||
@@ -692,20 +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) Layout() string {
|
||||
return p.identifierAsString(p.posIdentifierLayout)
|
||||
}
|
||||
|
||||
func (p *Path) Lang() string {
|
||||
return p.identifierAsString(p.posIdentifierLanguage)
|
||||
return p.identifierAsString(1)
|
||||
}
|
||||
|
||||
func (p *Path) Identifier(i int) string {
|
||||
@@ -717,43 +515,35 @@ func (p *Path) Disabled() bool {
|
||||
}
|
||||
|
||||
func (p *Path) Identifiers() []string {
|
||||
ids := make([]string, len(p.identifiersKnown))
|
||||
for i, id := range p.identifiersKnown {
|
||||
ids := make([]string, len(p.identifiers))
|
||||
for i, id := range p.identifiers {
|
||||
ids[i] = p.s[id.Low:id.High]
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func (p *Path) IdentifiersUnknown() []string {
|
||||
ids := make([]string, len(p.identifiersUnknown))
|
||||
for i, id := range p.identifiersUnknown {
|
||||
ids[i] = p.s[id.Low: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) ForType(t Type) *Path {
|
||||
p.pathType = t
|
||||
func (p Path) ForBundleType(t PathType) *Path {
|
||||
p.bundleType = t
|
||||
return &p
|
||||
}
|
||||
|
||||
@@ -763,12 +553,12 @@ func (p *Path) identifierAsString(i int) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
id := p.identifiersKnown[i]
|
||||
id := p.identifiers[i]
|
||||
return p.s[id.Low:id.High]
|
||||
}
|
||||
|
||||
func (p *Path) identifierIndex(i int) int {
|
||||
if i < 0 || i >= len(p.identifiersKnown) {
|
||||
if i < 0 || i >= len(p.identifiers) {
|
||||
return -1
|
||||
}
|
||||
return i
|
||||
|
||||
@@ -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")
|
||||
@@ -174,10 +163,8 @@ func TestParse(t *testing.T) {
|
||||
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"})
|
||||
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"b", "a", "b"})
|
||||
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.Path(), qt.Equals, "/a/b.a.b.no.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.a.b")
|
||||
@@ -187,11 +174,7 @@ func TestParse(t *testing.T) {
|
||||
"Home branch cundle",
|
||||
"/_index.md",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
|
||||
c.Assert(p.IsBranchBundle(), qt.IsTrue)
|
||||
c.Assert(p.IsBundle(), qt.IsTrue)
|
||||
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, "/")
|
||||
@@ -202,14 +185,12 @@ 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.IdentifiersUnknown(), qt.DeepEquals, []string{"index"})
|
||||
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
|
||||
c.Assert(p.IsBranchBundle(), qt.IsFalse)
|
||||
c.Assert(p.IsBundle(), qt.IsTrue)
|
||||
@@ -227,7 +208,6 @@ 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")
|
||||
@@ -240,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")
|
||||
},
|
||||
@@ -376,225 +355,11 @@ func TestParse(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
c.Run(test.name, func(c *qt.C) {
|
||||
if test.name != "Home branch cundle" {
|
||||
// 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.IdentifiersUnknown(), qt.DeepEquals, []string{})
|
||||
c.Assert(p.Base(), qt.Equals, "/list.html")
|
||||
c.Assert(p.Lang(), qt.Equals, "no")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Kind",
|
||||
"/section.no.html",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Kind(), qt.Equals, kinds.KindSection)
|
||||
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section"})
|
||||
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
|
||||
c.Assert(p.Base(), qt.Equals, "/section.html")
|
||||
c.Assert(p.Lang(), qt.Equals, "no")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Layout",
|
||||
"/list.section.no.html",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Layout(), qt.Equals, "list")
|
||||
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section", "list"})
|
||||
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
|
||||
c.Assert(p.Base(), qt.Equals, "/list.html")
|
||||
c.Assert(p.Lang(), qt.Equals, "no")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Layout multiple",
|
||||
"/mylayout.list.section.no.html",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Layout(), qt.Equals, "mylayout")
|
||||
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section", "list", "mylayout"})
|
||||
c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
|
||||
c.Assert(p.Base(), qt.Equals, "/mylayout.html")
|
||||
c.Assert(p.Lang(), qt.Equals, "no")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Layout shortcode",
|
||||
"/_shortcodes/myshort.list.no.html",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Layout(), qt.Equals, "list")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Layout baseof",
|
||||
"/baseof.list.no.html",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Layout(), qt.Equals, "list")
|
||||
},
|
||||
},
|
||||
{
|
||||
"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")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Shortcode with layout",
|
||||
"/_shortcodes/myshortcode.list.html",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Base(), qt.Equals, "/_shortcodes/myshortcode.html")
|
||||
c.Assert(p.Type(), qt.Equals, TypeShortcode)
|
||||
c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "list"})
|
||||
c.Assert(p.Layout(), qt.Equals, "list")
|
||||
c.Assert(p.PathNoIdentifier(), qt.Equals, "/_shortcodes/myshortcode")
|
||||
c.Assert(p.PathBeforeLangAndOutputFormatAndExt(), qt.Equals, "/_shortcodes/myshortcode.list")
|
||||
c.Assert(p.Lang(), qt.Equals, "")
|
||||
c.Assert(p.Kind(), qt.Equals, "")
|
||||
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{})
|
||||
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)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Shortcode lang in root",
|
||||
"/_shortcodes/no.html",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Type(), qt.Equals, TypeShortcode)
|
||||
c.Assert(p.Lang(), qt.Equals, "")
|
||||
c.Assert(p.NameNoIdentifier(), qt.Equals, "no")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Shortcode lang layout",
|
||||
"/_shortcodes/myshortcode.no.html",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Type(), qt.Equals, TypeShortcode)
|
||||
c.Assert(p.Lang(), qt.Equals, "no")
|
||||
c.Assert(p.Layout(), qt.Equals, "")
|
||||
c.Assert(p.NameNoIdentifier(), qt.Equals, "myshortcode")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
c.Run(test.name, func(c *qt.C) {
|
||||
if test.name != "Shortcode lang layout" {
|
||||
// return
|
||||
}
|
||||
test.assert(c, testParser.Parse(files.ComponentFolderLayouts, test.path))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasExt(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
|
||||
@@ -78,26 +78,3 @@ disablePathToLower = true
|
||||
b.AssertFileContent("public/en/mysection/mybundle/index.html", "en|Single")
|
||||
b.AssertFileContent("public/fr/MySection/MyBundle/index.html", "fr|Single")
|
||||
}
|
||||
|
||||
func TestIssue13596(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
-- content/p1/index.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- content/p1/a.1.txt --
|
||||
-- content/p1/a.2.txt --
|
||||
-- layouts/all.html --
|
||||
{{ range .Resources.Match "*" }}{{ .Name }}|{{ end }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/p1/index.html", "a.1.txt|a.2.txt|")
|
||||
b.AssertFileExists("public/p1/a.1.txt", true)
|
||||
b.AssertFileExists("public/p1/a.2.txt", true) // fails
|
||||
}
|
||||
|
||||
@@ -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]]
|
||||
}
|
||||
@@ -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]]
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package terminal
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
@@ -40,6 +41,10 @@ func PrintANSIColors(f *os.File) bool {
|
||||
// IsTerminal return true if the file descriptor is terminal and the TERM
|
||||
// environment variable isn't a dumb one.
|
||||
func IsTerminal(f *os.File) bool {
|
||||
if runtime.GOOS == "windows" {
|
||||
return false
|
||||
}
|
||||
|
||||
fd := f.Fd()
|
||||
return os.Getenv("TERM") != "dumb" && (isatty.IsTerminal(fd) || isatty.IsCygwinTerminal(fd))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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}
|
||||
|
||||
+43
-155
@@ -20,7 +20,6 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -33,6 +32,7 @@ import (
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/privacy"
|
||||
@@ -42,7 +42,6 @@ import (
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugolib/segments"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
gc "github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
|
||||
"github.com/gohugoio/hugo/markup/markup_config"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/minifiers"
|
||||
@@ -83,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
|
||||
@@ -147,7 +146,7 @@ type Config struct {
|
||||
|
||||
// The cascade configuration section contains the top level front matter cascade configuration options,
|
||||
// a slice of page matcher and params to apply to those pages.
|
||||
Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]] `mapstructure:"-"`
|
||||
Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, *maps.Ordered[page.PageMatcher, maps.Params]] `mapstructure:"-"`
|
||||
|
||||
// The segments defines segments for the site. Used for partial/segmented builds.
|
||||
Segments *config.ConfigNamespace[map[string]segments.SegmentConfig, segments.Segments] `mapstructure:"-"`
|
||||
@@ -397,80 +396,44 @@ 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())
|
||||
hugo.Deprecate("the \":filename\" permalink token", "Use \":contentbasename\" instead.", "0.144.0")
|
||||
}
|
||||
if strings.Contains(vs, ":slugorfilename") {
|
||||
hugo.DeprecateWithLogger("the \":slugorfilename\" permalink token", "Use \":slugorcontentbasename\" instead.", "0.144.0", logger.Logger())
|
||||
}
|
||||
|
||||
// Legacy render hook values.
|
||||
alternativeDetails := fmt.Sprintf(
|
||||
"Set to %q if previous value was false, or set to %q if previous value was true.",
|
||||
gc.RenderHookUseEmbeddedNever,
|
||||
gc.RenderHookUseEmbeddedFallback,
|
||||
)
|
||||
if c.Markup.Goldmark.RenderHooks.Image.EnableDefault != nil {
|
||||
alternative := "Use markup.goldmark.renderHooks.image.useEmbedded instead." + " " + alternativeDetails
|
||||
hugo.DeprecateWithLogger("site config key markup.goldmark.renderHooks.image.enableDefault", alternative, "0.148.0", logger.Logger())
|
||||
if *c.Markup.Goldmark.RenderHooks.Image.EnableDefault {
|
||||
c.Markup.Goldmark.RenderHooks.Image.UseEmbedded = gc.RenderHookUseEmbeddedFallback
|
||||
} else {
|
||||
c.Markup.Goldmark.RenderHooks.Image.UseEmbedded = gc.RenderHookUseEmbeddedNever
|
||||
}
|
||||
}
|
||||
if c.Markup.Goldmark.RenderHooks.Link.EnableDefault != nil {
|
||||
alternative := "Use markup.goldmark.renderHooks.link.useEmbedded instead." + " " + alternativeDetails
|
||||
hugo.DeprecateWithLogger("site config key markup.goldmark.renderHooks.link.enableDefault", alternative, "0.148.0", logger.Logger())
|
||||
if *c.Markup.Goldmark.RenderHooks.Link.EnableDefault {
|
||||
c.Markup.Goldmark.RenderHooks.Link.UseEmbedded = gc.RenderHookUseEmbeddedFallback
|
||||
} else {
|
||||
c.Markup.Goldmark.RenderHooks.Link.UseEmbedded = gc.RenderHookUseEmbeddedNever
|
||||
}
|
||||
}
|
||||
|
||||
// Validate render hook configuration.
|
||||
renderHookUseEmbeddedModes := []string{
|
||||
gc.RenderHookUseEmbeddedAlways,
|
||||
gc.RenderHookUseEmbeddedAuto,
|
||||
gc.RenderHookUseEmbeddedFallback,
|
||||
gc.RenderHookUseEmbeddedNever,
|
||||
}
|
||||
if !slices.Contains(renderHookUseEmbeddedModes, c.Markup.Goldmark.RenderHooks.Image.UseEmbedded) {
|
||||
return fmt.Errorf("site config markup.goldmark.renderHooks.image must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
|
||||
}
|
||||
if !slices.Contains(renderHookUseEmbeddedModes, c.Markup.Goldmark.RenderHooks.Link.UseEmbedded) {
|
||||
return fmt.Errorf("site config markup.goldmark.renderHooks.link must be one of %s", helpers.StringSliceToList(renderHookUseEmbeddedModes, "or"))
|
||||
hugo.Deprecate("the \":slugorfilename\" permalink token", "Use \":slugorcontentbasename\" instead.", "0.144.0")
|
||||
}
|
||||
|
||||
c.C = &ConfigCompiled{
|
||||
@@ -813,7 +776,7 @@ type Configs struct {
|
||||
}
|
||||
|
||||
func (c *Configs) Validate(logger loggers.Logger) error {
|
||||
c.Base.Cascade.Config.Range(func(p page.PageMatcher, cfg page.PageMatcherParamsConfig) bool {
|
||||
c.Base.Cascade.Config.Range(func(p page.PageMatcher, params maps.Params) bool {
|
||||
page.CheckCascadePattern(logger, p)
|
||||
return true
|
||||
})
|
||||
@@ -837,58 +800,30 @@ func (c *Configs) IsZero() bool {
|
||||
|
||||
func (c *Configs) Init() error {
|
||||
var languages langs.Languages
|
||||
|
||||
var langKeys []string
|
||||
var hasEn bool
|
||||
|
||||
const en = "en"
|
||||
|
||||
for k := range c.LanguageConfigMap {
|
||||
langKeys = append(langKeys, k)
|
||||
if k == en {
|
||||
hasEn = true
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the LanguageConfigSlice by language weight (if set) or lang.
|
||||
sort.Slice(langKeys, func(i, j int) bool {
|
||||
ki := langKeys[i]
|
||||
kj := langKeys[j]
|
||||
lki := c.LanguageConfigMap[ki]
|
||||
lkj := c.LanguageConfigMap[kj]
|
||||
li := lki.Languages[ki]
|
||||
lj := lkj.Languages[kj]
|
||||
if li.Weight != lj.Weight {
|
||||
return li.Weight < lj.Weight
|
||||
}
|
||||
return ki < kj
|
||||
})
|
||||
|
||||
// See issue #13646.
|
||||
defaultConfigLanguageFallback := en
|
||||
if !hasEn {
|
||||
// Pick the first one.
|
||||
defaultConfigLanguageFallback = langKeys[0]
|
||||
}
|
||||
|
||||
if c.Base.DefaultContentLanguage == "" {
|
||||
c.Base.DefaultContentLanguage = defaultConfigLanguageFallback
|
||||
}
|
||||
|
||||
for _, k := range langKeys {
|
||||
v := c.LanguageConfigMap[k]
|
||||
if v.DefaultContentLanguage == "" {
|
||||
v.DefaultContentLanguage = defaultConfigLanguageFallback
|
||||
}
|
||||
c.LanguageConfigSlice = append(c.LanguageConfigSlice, v)
|
||||
defaultContentLanguage := c.Base.DefaultContentLanguage
|
||||
for k, v := range c.LanguageConfigMap {
|
||||
languageConf := v.Languages[k]
|
||||
language, err := langs.NewLanguage(k, c.Base.DefaultContentLanguage, v.TimeZone, languageConf)
|
||||
language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
languages = append(languages, language)
|
||||
}
|
||||
|
||||
// Sort the sites by language weight (if set) or lang.
|
||||
sort.Slice(languages, func(i, j int) bool {
|
||||
li := languages[i]
|
||||
lj := languages[j]
|
||||
if li.Weight != lj.Weight {
|
||||
return li.Weight < lj.Weight
|
||||
}
|
||||
return li.Lang < lj.Lang
|
||||
})
|
||||
|
||||
for _, l := range languages {
|
||||
c.LanguageConfigSlice = append(c.LanguageConfigSlice, c.LanguageConfigMap[l.Lang])
|
||||
}
|
||||
|
||||
// Filter out disabled languages.
|
||||
var n int
|
||||
for _, l := range languages {
|
||||
@@ -901,12 +836,12 @@ func (c *Configs) Init() error {
|
||||
|
||||
var languagesDefaultFirst langs.Languages
|
||||
for _, l := range languages {
|
||||
if l.Lang == c.Base.DefaultContentLanguage {
|
||||
if l.Lang == defaultContentLanguage {
|
||||
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
||||
}
|
||||
}
|
||||
for _, l := range languages {
|
||||
if l.Lang != c.Base.DefaultContentLanguage {
|
||||
if l.Lang != defaultContentLanguage {
|
||||
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
||||
}
|
||||
}
|
||||
@@ -914,24 +849,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.ContentTypes.Config.IsContentSuffix}
|
||||
|
||||
c.configLangs = make([]config.AllProvider, len(c.Languages))
|
||||
for i, l := range c.LanguagesDefaultFirst {
|
||||
@@ -992,48 +910,17 @@ func (c Configs) GetByLang(lang string) config.AllProvider {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDefaultConfig() *Config {
|
||||
return &Config{
|
||||
Taxonomies: map[string]string{"tag": "tags", "category": "categories"},
|
||||
Sitemap: config.SitemapConfig{Priority: -1, Filename: "sitemap.xml"},
|
||||
RootConfig: RootConfig{
|
||||
Environment: hugo.EnvironmentProduction,
|
||||
TitleCaseStyle: "AP",
|
||||
PluralizeListTitles: true,
|
||||
CapitalizeListTitles: true,
|
||||
StaticDir: []string{"static"},
|
||||
SummaryLength: 70,
|
||||
Timeout: "60s",
|
||||
|
||||
CommonDirs: config.CommonDirs{
|
||||
ArcheTypeDir: "archetypes",
|
||||
ContentDir: "content",
|
||||
ResourceDir: "resources",
|
||||
PublishDir: "public",
|
||||
ThemesDir: "themes",
|
||||
AssetDir: "assets",
|
||||
LayoutDir: "layouts",
|
||||
I18nDir: "i18n",
|
||||
DataDir: "data",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// fromLoadConfigResult creates a new Config from res.
|
||||
func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadConfigResult) (*Configs, error) {
|
||||
if !res.Cfg.IsSet("languages") {
|
||||
// We need at least one
|
||||
lang := res.Cfg.GetString("defaultContentLanguage")
|
||||
if lang == "" {
|
||||
lang = "en"
|
||||
}
|
||||
res.Cfg.Set("languages", maps.Params{lang: maps.Params{}})
|
||||
}
|
||||
bcfg := res.BaseConfig
|
||||
cfg := res.Cfg
|
||||
|
||||
all := newDefaultConfig()
|
||||
all := &Config{}
|
||||
|
||||
err := decodeConfigFromParams(fs, logger, bcfg, cfg, all, nil)
|
||||
if err != nil {
|
||||
@@ -1043,7 +930,6 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
langConfigMap := make(map[string]*Config)
|
||||
|
||||
languagesConfig := cfg.GetStringMap("languages")
|
||||
|
||||
var isMultihost bool
|
||||
|
||||
if err := all.CompileConfig(logger); err != nil {
|
||||
@@ -1119,11 +1005,13 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
|
||||
// Adjust Goldmark config defaults for multilingual, single-host sites.
|
||||
if len(languagesConfig) > 1 && !isMultihost && !clone.Markup.Goldmark.DuplicateResourceFiles {
|
||||
if clone.Markup.Goldmark.RenderHooks.Image.UseEmbedded == gc.RenderHookUseEmbeddedAuto {
|
||||
clone.Markup.Goldmark.RenderHooks.Image.UseEmbedded = gc.RenderHookUseEmbeddedFallback
|
||||
}
|
||||
if clone.Markup.Goldmark.RenderHooks.Link.UseEmbedded == gc.RenderHookUseEmbeddedAuto {
|
||||
clone.Markup.Goldmark.RenderHooks.Link.UseEmbedded = gc.RenderHookUseEmbeddedFallback
|
||||
if !clone.Markup.Goldmark.DuplicateResourceFiles {
|
||||
if clone.Markup.Goldmark.RenderHooks.Link.EnableDefault == nil {
|
||||
clone.Markup.Goldmark.RenderHooks.Link.EnableDefault = types.NewBool(true)
|
||||
}
|
||||
if clone.Markup.Goldmark.RenderHooks.Image.EnableDefault == nil {
|
||||
clone.Markup.Goldmark.RenderHooks.Image.EnableDefault = types.NewBool(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,11 @@ package allconfig_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/config/allconfig"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
gc "github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
)
|
||||
|
||||
@@ -237,172 +234,3 @@ baseURL = "https://example.com"
|
||||
b.Assert(c.IsContentFile("foo.md"), qt.Equals, true)
|
||||
b.Assert(len(s), qt.Equals, 6)
|
||||
}
|
||||
|
||||
func TestMergeDeep(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
theme = ["theme1", "theme2"]
|
||||
_merge = "deep"
|
||||
-- themes/theme1/hugo.toml --
|
||||
[sitemap]
|
||||
filename = 'mysitemap.xml'
|
||||
[services]
|
||||
[services.googleAnalytics]
|
||||
id = 'foo bar'
|
||||
[taxonomies]
|
||||
foo = 'bars'
|
||||
-- themes/theme2/config/_default/hugo.toml --
|
||||
[taxonomies]
|
||||
bar = 'baz'
|
||||
-- layouts/home.html --
|
||||
GA ID: {{ site.Config.Services.GoogleAnalytics.ID }}.
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
conf := b.H.Configs
|
||||
base := conf.Base
|
||||
|
||||
b.Assert(base.Environment, qt.Equals, hugo.EnvironmentProduction)
|
||||
b.Assert(base.BaseURL, qt.Equals, "https://example.com")
|
||||
b.Assert(base.Sitemap.Filename, qt.Equals, "mysitemap.xml")
|
||||
b.Assert(base.Taxonomies, qt.DeepEquals, map[string]string{"bar": "baz", "foo": "bars"})
|
||||
|
||||
b.AssertFileContent("public/index.html", "GA ID: foo bar.")
|
||||
}
|
||||
|
||||
func TestMergeDeepBuildStats(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
title = "Theme 1"
|
||||
_merge = "deep"
|
||||
[module]
|
||||
[module.hugoVersion]
|
||||
[[module.imports]]
|
||||
path = "theme1"
|
||||
-- themes/theme1/hugo.toml --
|
||||
[build]
|
||||
[build.buildStats]
|
||||
disableIDs = true
|
||||
enable = true
|
||||
-- layouts/home.html --
|
||||
Home.
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
|
||||
|
||||
conf := b.H.Configs
|
||||
base := conf.Base
|
||||
|
||||
b.Assert(base.Title, qt.Equals, "Theme 1")
|
||||
b.Assert(len(base.Module.Imports), qt.Equals, 1)
|
||||
b.Assert(base.Build.BuildStats.Enable, qt.Equals, true)
|
||||
b.AssertFileExists("/hugo_stats.json", true)
|
||||
}
|
||||
|
||||
func TestMergeDeepBuildStatsTheme(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
_merge = "deep"
|
||||
theme = ["theme1"]
|
||||
-- themes/theme1/hugo.toml --
|
||||
title = "Theme 1"
|
||||
[build]
|
||||
[build.buildStats]
|
||||
disableIDs = true
|
||||
enable = true
|
||||
-- layouts/home.html --
|
||||
Home.
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files, hugolib.TestOptOsFs())
|
||||
|
||||
conf := b.H.Configs
|
||||
base := conf.Base
|
||||
|
||||
b.Assert(base.Title, qt.Equals, "Theme 1")
|
||||
b.Assert(len(base.Module.Imports), qt.Equals, 1)
|
||||
b.Assert(base.Build.BuildStats.Enable, qt.Equals, true)
|
||||
b.AssertFileExists("/hugo_stats.json", true)
|
||||
}
|
||||
|
||||
func TestDefaultConfigLanguageBlankWhenNoEnglishExists(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
[languages]
|
||||
[languages.nn]
|
||||
weight = 20
|
||||
[languages.sv]
|
||||
weight = 10
|
||||
[languages.sv.taxonomies]
|
||||
tag = "taggar"
|
||||
-- layouts/all.html --
|
||||
All.
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.Assert(b.H.Conf.DefaultContentLanguage(), qt.Equals, "sv")
|
||||
}
|
||||
|
||||
func TestDefaultConfigEnvDisableLanguagesIssue13707(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableLanguages = []
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.nn]
|
||||
weight = 2
|
||||
[languages.sv]
|
||||
weight = 3
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files, hugolib.TestOptWithConfig(func(conf *hugolib.IntegrationTestConfig) {
|
||||
conf.Environ = []string{`HUGO_DISABLELANGUAGES=sv nn`}
|
||||
}))
|
||||
|
||||
b.Assert(len(b.H.Sites), qt.Equals, 1)
|
||||
}
|
||||
|
||||
// Issue 13535
|
||||
// We changed enablement of the embedded link and image render hooks from
|
||||
// booleans to enums in v0.148.0.
|
||||
func TestLegacyEmbeddedRenderHookEnablement(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[markup.goldmark.renderHooks.image]
|
||||
#KEY_VALUE
|
||||
|
||||
[markup.goldmark.renderHooks.link]
|
||||
#KEY_VALUE
|
||||
`
|
||||
f := strings.ReplaceAll(files, "#KEY_VALUE", "enableDefault = false")
|
||||
b := hugolib.Test(t, f)
|
||||
c := b.H.Configs.Base.Markup.Goldmark.RenderHooks
|
||||
b.Assert(c.Link.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedNever)
|
||||
b.Assert(c.Image.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedNever)
|
||||
|
||||
f = strings.ReplaceAll(files, "#KEY_VALUE", "enableDefault = true")
|
||||
b = hugolib.Test(t, f)
|
||||
c = b.H.Configs.Base.Markup.Goldmark.RenderHooks
|
||||
b.Assert(c.Link.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedFallback)
|
||||
b.Assert(c.Image.UseEmbedded, qt.Equals, gc.RenderHookUseEmbeddedFallback)
|
||||
}
|
||||
|
||||
@@ -249,18 +249,14 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
key: "sitemap",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
if p.p.IsSet(d.key) {
|
||||
p.c.Sitemap, err = config.DecodeSitemap(p.c.Sitemap, p.p.GetStringMap(d.key))
|
||||
}
|
||||
p.c.Sitemap, err = config.DecodeSitemap(config.SitemapConfig{Priority: -1, Filename: "sitemap.xml"}, p.p.GetStringMap(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
"taxonomies": {
|
||||
key: "taxonomies",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
if p.p.IsSet(d.key) {
|
||||
p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
|
||||
}
|
||||
p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
|
||||
return nil
|
||||
},
|
||||
},
|
||||
@@ -310,18 +306,16 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
}
|
||||
|
||||
// Validate defaultContentLanguage.
|
||||
if p.c.DefaultContentLanguage != "" {
|
||||
var found bool
|
||||
for lang := range p.c.Languages {
|
||||
if lang == p.c.DefaultContentLanguage {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("config value %q for defaultContentLanguage does not match any language definition", p.c.DefaultContentLanguage)
|
||||
var found bool
|
||||
for lang := range p.c.Languages {
|
||||
if lang == p.c.DefaultContentLanguage {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("config value %q for defaultContentLanguage does not match any language definition", p.c.DefaultContentLanguage)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
@@ -330,7 +324,7 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
key: "cascade",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Cascade, err = page.DecodeCascadeConfig(nil, true, p.p.Get(d.key))
|
||||
p.c.Cascade, err = page.DecodeCascadeConfig(nil, p.p.Get(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
|
||||
+79
-36
@@ -159,9 +159,63 @@ func (l configLoader) applyConfigAliases() error {
|
||||
|
||||
func (l configLoader) applyDefaultConfig() error {
|
||||
defaultSettings := maps.Params{
|
||||
// These dirs are used early/before we build the config struct.
|
||||
"themesDir": "themes",
|
||||
"configDir": "config",
|
||||
"baseURL": "",
|
||||
"cleanDestinationDir": false,
|
||||
"watch": false,
|
||||
"contentDir": "content",
|
||||
"resourceDir": "resources",
|
||||
"publishDir": "public",
|
||||
"publishDirOrig": "public",
|
||||
"themesDir": "themes",
|
||||
"assetDir": "assets",
|
||||
"layoutDir": "layouts",
|
||||
"i18nDir": "i18n",
|
||||
"dataDir": "data",
|
||||
"archetypeDir": "archetypes",
|
||||
"configDir": "config",
|
||||
"staticDir": "static",
|
||||
"buildDrafts": false,
|
||||
"buildFuture": false,
|
||||
"buildExpired": false,
|
||||
"params": maps.Params{},
|
||||
"environment": hugo.EnvironmentProduction,
|
||||
"uglyURLs": false,
|
||||
"verbose": false,
|
||||
"ignoreCache": false,
|
||||
"canonifyURLs": false,
|
||||
"relativeURLs": false,
|
||||
"removePathAccents": false,
|
||||
"titleCaseStyle": "AP",
|
||||
"taxonomies": maps.Params{"tag": "tags", "category": "categories"},
|
||||
"permalinks": maps.Params{},
|
||||
"sitemap": maps.Params{"priority": -1, "filename": "sitemap.xml"},
|
||||
"menus": maps.Params{},
|
||||
"disableLiveReload": false,
|
||||
"pluralizeListTitles": true,
|
||||
"capitalizeListTitles": true,
|
||||
"forceSyncStatic": false,
|
||||
"footnoteAnchorPrefix": "",
|
||||
"footnoteReturnLinkContents": "",
|
||||
"newContentEditor": "",
|
||||
"paginate": 0, // Moved into the paginator struct in Hugo v0.128.0.
|
||||
"paginatePath": "", // Moved into the paginator struct in Hugo v0.128.0.
|
||||
"summaryLength": 70,
|
||||
"rssLimit": -1,
|
||||
"sectionPagesMenu": "",
|
||||
"disablePathToLower": false,
|
||||
"hasCJKLanguage": false,
|
||||
"enableEmoji": false,
|
||||
"defaultContentLanguage": "en",
|
||||
"defaultContentLanguageInSubdir": false,
|
||||
"enableMissingTranslationPlaceholders": false,
|
||||
"enableGitInfo": false,
|
||||
"ignoreFiles": make([]string, 0),
|
||||
"disableAliases": false,
|
||||
"debug": false,
|
||||
"disableFastRender": false,
|
||||
"timeout": "30s",
|
||||
"timeZone": "",
|
||||
"enableInlineShortcodes": false,
|
||||
}
|
||||
|
||||
l.cfg.SetDefaults(defaultSettings)
|
||||
@@ -233,51 +287,40 @@ func (l configLoader) applyOsEnvOverrides(environ []string) error {
|
||||
|
||||
if existing != nil {
|
||||
val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing)
|
||||
if err == nil {
|
||||
val = l.envValToVal(env.Key, val)
|
||||
if owner != nil {
|
||||
owner[nestedKey] = val
|
||||
} else {
|
||||
l.cfg.Set(env.Key, val)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if owner != nil && nestedKey != "" {
|
||||
owner[nestedKey] = env.Value
|
||||
if owner != nil {
|
||||
owner[nestedKey] = val
|
||||
} else {
|
||||
l.cfg.Set(env.Key, val)
|
||||
}
|
||||
} else {
|
||||
var val any
|
||||
key := strings.ReplaceAll(env.Key, delim, ".")
|
||||
_, ok := allDecoderSetups[key]
|
||||
if ok {
|
||||
// A map.
|
||||
if v, err := metadecoders.Default.UnmarshalStringTo(env.Value, map[string]any{}); err == nil {
|
||||
val = v
|
||||
if nestedKey != "" {
|
||||
owner[nestedKey] = env.Value
|
||||
} else {
|
||||
var val any
|
||||
key := strings.ReplaceAll(env.Key, delim, ".")
|
||||
_, ok := allDecoderSetups[key]
|
||||
if ok {
|
||||
// A map.
|
||||
if v, err := metadecoders.Default.UnmarshalStringTo(env.Value, map[string]interface{}{}); err == nil {
|
||||
val = v
|
||||
}
|
||||
}
|
||||
if val == nil {
|
||||
// A string.
|
||||
val = l.envStringToVal(key, env.Value)
|
||||
}
|
||||
l.cfg.Set(key, val)
|
||||
}
|
||||
|
||||
if val == nil {
|
||||
// A string.
|
||||
val = l.envStringToVal(key, env.Value)
|
||||
}
|
||||
l.cfg.Set(key, val)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *configLoader) envValToVal(k string, v any) any {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
return l.envStringToVal(k, v)
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func (l *configLoader) envStringToVal(k, v string) any {
|
||||
switch k {
|
||||
case "disablekinds", "disablelanguages":
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -129,7 +128,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
|
||||
}
|
||||
|
||||
|
||||
@@ -166,7 +166,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",
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -33,13 +32,3 @@ func TestIsValidConfigFileName(t *testing.T) {
|
||||
c.Assert(IsValidConfigFilename(""), qt.Equals, false)
|
||||
c.Assert(IsValidConfigFilename("config.toml.swp"), qt.Equals, false)
|
||||
}
|
||||
|
||||
func TestFromTOMLConfigString(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(
|
||||
func() { FromTOMLConfigString("cfg") },
|
||||
qt.PanicMatches,
|
||||
regexp.MustCompile("_stream.toml:.*"),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ type AllProvider interface {
|
||||
}
|
||||
|
||||
// We cannot import the media package as that would create a circular dependency.
|
||||
// This interface defines a subset of what media.ContentTypes provides.
|
||||
// This interface defineds a sub set of what media.ContentTypes provides.
|
||||
type ContentTypesProvider interface {
|
||||
IsContentSuffix(suffix string) bool
|
||||
IsContentFile(filename string) bool
|
||||
|
||||
@@ -345,7 +345,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 {
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -333,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
|
||||
@@ -354,90 +353,6 @@ func TestDefaultConfigProvider(t *testing.T) {
|
||||
|
||||
c.Assert(r.Wait(), qt.IsNil)
|
||||
})
|
||||
|
||||
c.Run("GetBool", func(c *qt.C) {
|
||||
cfg := New()
|
||||
|
||||
var k string
|
||||
var v bool
|
||||
|
||||
k, v = "foo", true
|
||||
|
||||
cfg.Set(k, v)
|
||||
c.Assert(cfg.Get(k), qt.Equals, v)
|
||||
c.Assert(cfg.GetBool(k), qt.Equals, v)
|
||||
})
|
||||
|
||||
c.Run("GetParams", func(c *qt.C) {
|
||||
cfg := New()
|
||||
k := "foo"
|
||||
|
||||
cfg.Set(k, maps.Params{k: true})
|
||||
c.Assert(cfg.GetParams(k), qt.DeepEquals, maps.Params{
|
||||
k: true,
|
||||
})
|
||||
|
||||
c.Assert(cfg.GetParams("bar"), qt.IsNil)
|
||||
})
|
||||
|
||||
c.Run("Keys", func(c *qt.C) {
|
||||
cfg := New()
|
||||
k := "foo"
|
||||
k2 := "bar"
|
||||
|
||||
cfg.Set(k, maps.Params{k: struct{}{}})
|
||||
cfg.Set(k2, maps.Params{k2: struct{}{}})
|
||||
|
||||
c.Assert(len(cfg.Keys()), qt.Equals, 2)
|
||||
|
||||
got := cfg.Keys()
|
||||
slices.Sort(got)
|
||||
|
||||
want := []string{k, k2}
|
||||
slices.Sort(want)
|
||||
|
||||
c.Assert(got, qt.DeepEquals, want)
|
||||
})
|
||||
|
||||
c.Run("WalkParams", func(c *qt.C) {
|
||||
cfg := New()
|
||||
|
||||
cfg.Set("x", maps.Params{})
|
||||
cfg.Set("y", maps.Params{})
|
||||
|
||||
var got []string
|
||||
cfg.WalkParams(func(params ...maps.KeyParams) bool {
|
||||
got = append(got, params[len(params)-1].Key)
|
||||
return false
|
||||
})
|
||||
|
||||
want := []string{"", "x", "y"}
|
||||
slices.Sort(got)
|
||||
slices.Sort(want)
|
||||
|
||||
c.Assert(got, qt.DeepEquals, want)
|
||||
|
||||
cfg = New()
|
||||
cfg.WalkParams(func(params ...maps.KeyParams) bool {
|
||||
return true
|
||||
})
|
||||
|
||||
got = []string{""}
|
||||
want = []string{""}
|
||||
c.Assert(got, qt.DeepEquals, want)
|
||||
})
|
||||
|
||||
c.Run("SetDefaults", func(c *qt.C) {
|
||||
cfg := New()
|
||||
|
||||
cfg.SetDefaults(maps.Params{
|
||||
"foo": "bar",
|
||||
"bar": "baz",
|
||||
})
|
||||
|
||||
c.Assert(cfg.Get("foo"), qt.Equals, "bar")
|
||||
c.Assert(cfg.Get("bar"), qt.Equals, "baz")
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDefaultConfigProvider(b *testing.B) {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -101,9 +101,6 @@ func DecodeConfig(cfg config.Provider) (c Config, err error) {
|
||||
|
||||
if c.RSS.Limit == 0 {
|
||||
c.RSS.Limit = cfg.GetInt(rssLimitKey)
|
||||
if c.RSS.Limit == 0 {
|
||||
c.RSS.Limit = -1
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Theme Name
|
||||
|
||||
## Features
|
||||
|
||||
## Installation
|
||||
|
||||
## Configuration
|
||||
@@ -0,0 +1,5 @@
|
||||
+++
|
||||
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
|
||||
date = {{ .Date }}
|
||||
draft = true
|
||||
+++
|
||||
@@ -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
-1
@@ -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 }}
|
||||
+1
-1
@@ -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 }}
|
||||
@@ -1,7 +0,0 @@
|
||||
{{ define "main" }}
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
{{ range .Pages }}
|
||||
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@@ -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'
|
||||
+33
-48
@@ -36,7 +36,6 @@ import (
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/para"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/deploy/deployconfig"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
@@ -488,12 +487,7 @@ func knownHiddenDirectory(name string) bool {
|
||||
// walkLocal walks the source directory and returns a flat list of files,
|
||||
// using localFile.SlashPath as the map keys.
|
||||
func (d *Deployer) walkLocal(fs afero.Fs, matchers []*deployconfig.Matcher, include, exclude glob.Glob, mediaTypes media.Types, mappath func(string) string) (map[string]*localFile, error) {
|
||||
retval := make(map[string]*localFile)
|
||||
var mu sync.Mutex
|
||||
|
||||
workers := para.New(d.cfg.Workers)
|
||||
g, _ := workers.Start(context.Background())
|
||||
|
||||
retval := map[string]*localFile{}
|
||||
err := afero.Walk(fs, "", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -514,54 +508,45 @@ func (d *Deployer) walkLocal(fs afero.Fs, matchers []*deployconfig.Matcher, incl
|
||||
return nil
|
||||
}
|
||||
|
||||
// Process each file in a worker
|
||||
g.Run(func() error {
|
||||
// When a file system is HFS+, its filepath is in NFD form.
|
||||
if runtime.GOOS == "darwin" {
|
||||
path = norm.NFC.String(path)
|
||||
}
|
||||
// When a file system is HFS+, its filepath is in NFD form.
|
||||
if runtime.GOOS == "darwin" {
|
||||
path = norm.NFC.String(path)
|
||||
}
|
||||
|
||||
// Check include/exclude matchers.
|
||||
slashpath := filepath.ToSlash(path)
|
||||
if include != nil && !include.Match(slashpath) {
|
||||
d.logger.Infof(" dropping %q due to include\n", slashpath)
|
||||
return nil
|
||||
}
|
||||
if exclude != nil && exclude.Match(slashpath) {
|
||||
d.logger.Infof(" dropping %q due to exclude\n", slashpath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find the first matching matcher (if any).
|
||||
var m *deployconfig.Matcher
|
||||
for _, cur := range matchers {
|
||||
if cur.Matches(slashpath) {
|
||||
m = cur
|
||||
break
|
||||
}
|
||||
}
|
||||
// Apply any additional modifications to the local path, to map it to
|
||||
// the remote path.
|
||||
if mappath != nil {
|
||||
slashpath = mappath(slashpath)
|
||||
}
|
||||
lf, err := newLocalFile(fs, path, slashpath, m, mediaTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mu.Lock()
|
||||
retval[lf.SlashPath] = lf
|
||||
mu.Unlock()
|
||||
// Check include/exclude matchers.
|
||||
slashpath := filepath.ToSlash(path)
|
||||
if include != nil && !include.Match(slashpath) {
|
||||
d.logger.Infof(" dropping %q due to include\n", slashpath)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if exclude != nil && exclude.Match(slashpath) {
|
||||
d.logger.Infof(" dropping %q due to exclude\n", slashpath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Find the first matching matcher (if any).
|
||||
var m *deployconfig.Matcher
|
||||
for _, cur := range matchers {
|
||||
if cur.Matches(slashpath) {
|
||||
m = cur
|
||||
break
|
||||
}
|
||||
}
|
||||
// Apply any additional modifications to the local path, to map it to
|
||||
// the remote path.
|
||||
if mappath != nil {
|
||||
slashpath = mappath(slashpath)
|
||||
}
|
||||
lf, err := newLocalFile(fs, path, slashpath, m, mediaTypes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
retval[lf.SlashPath] = lf
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -623,7 +623,7 @@ func TestEndToEndSync(t *testing.T) {
|
||||
localFs: test.fs,
|
||||
bucket: test.bucket,
|
||||
mediaTypes: media.DefaultTypes,
|
||||
cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1},
|
||||
cfg: deployconfig.DeployConfig{MaxDeletes: -1},
|
||||
}
|
||||
|
||||
// Initial deployment should sync remote with local.
|
||||
@@ -706,7 +706,7 @@ func TestMaxDeletes(t *testing.T) {
|
||||
localFs: test.fs,
|
||||
bucket: test.bucket,
|
||||
mediaTypes: media.DefaultTypes,
|
||||
cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1},
|
||||
cfg: deployconfig.DeployConfig{MaxDeletes: -1},
|
||||
}
|
||||
|
||||
// Sync remote with local.
|
||||
@@ -836,7 +836,7 @@ func TestIncludeExclude(t *testing.T) {
|
||||
}
|
||||
deployer := &Deployer{
|
||||
localFs: fsTest.fs,
|
||||
cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1}, bucket: fsTest.bucket,
|
||||
cfg: deployconfig.DeployConfig{MaxDeletes: -1}, bucket: fsTest.bucket,
|
||||
target: tgt,
|
||||
mediaTypes: media.DefaultTypes,
|
||||
}
|
||||
@@ -893,7 +893,7 @@ func TestIncludeExcludeRemoteDelete(t *testing.T) {
|
||||
}
|
||||
deployer := &Deployer{
|
||||
localFs: fsTest.fs,
|
||||
cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1}, bucket: fsTest.bucket,
|
||||
cfg: deployconfig.DeployConfig{MaxDeletes: -1}, bucket: fsTest.bucket,
|
||||
mediaTypes: media.DefaultTypes,
|
||||
}
|
||||
|
||||
@@ -945,7 +945,7 @@ func TestCompression(t *testing.T) {
|
||||
deployer := &Deployer{
|
||||
localFs: test.fs,
|
||||
bucket: test.bucket,
|
||||
cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1, Matchers: []*deployconfig.Matcher{{Pattern: ".*", Gzip: true, Re: regexp.MustCompile(".*")}}},
|
||||
cfg: deployconfig.DeployConfig{MaxDeletes: -1, Matchers: []*deployconfig.Matcher{{Pattern: ".*", Gzip: true, Re: regexp.MustCompile(".*")}}},
|
||||
mediaTypes: media.DefaultTypes,
|
||||
}
|
||||
|
||||
@@ -1000,7 +1000,7 @@ func TestMatching(t *testing.T) {
|
||||
deployer := &Deployer{
|
||||
localFs: test.fs,
|
||||
bucket: test.bucket,
|
||||
cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1, Matchers: []*deployconfig.Matcher{{Pattern: "^subdir/aaa$", Force: true, Re: regexp.MustCompile("^subdir/aaa$")}}},
|
||||
cfg: deployconfig.DeployConfig{MaxDeletes: -1, Matchers: []*deployconfig.Matcher{{Pattern: "^subdir/aaa$", Force: true, Re: regexp.MustCompile("^subdir/aaa$")}}},
|
||||
mediaTypes: media.DefaultTypes,
|
||||
}
|
||||
|
||||
@@ -1097,6 +1097,5 @@ func verifyRemote(ctx context.Context, bucket *blob.Bucket, local []*fileData) (
|
||||
func newDeployer() *Deployer {
|
||||
return &Deployer{
|
||||
logger: loggers.NewDefault(),
|
||||
cfg: deployconfig.DeployConfig{Workers: 2},
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+28
-19
@@ -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.
|
||||
|
||||
+1
-5
@@ -60,7 +60,6 @@
|
||||
"redirections",
|
||||
"subexpression",
|
||||
"suppressible",
|
||||
"synchronisation",
|
||||
"templating",
|
||||
"transpile",
|
||||
"unmarshal",
|
||||
@@ -70,7 +69,6 @@
|
||||
"# cspell: ignore hugo terminology",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"alignx",
|
||||
"aligny",
|
||||
"attrlink",
|
||||
"canonify",
|
||||
"codeowners",
|
||||
@@ -163,7 +161,7 @@
|
||||
"# ----------------------------------------------------------------------",
|
||||
"achristie",
|
||||
"ccpa",
|
||||
"cpra",
|
||||
"crpa",
|
||||
"ddmaurier",
|
||||
"dring",
|
||||
"fleqn",
|
||||
@@ -173,8 +171,6 @@
|
||||
"jsmith",
|
||||
"leqno",
|
||||
"milli",
|
||||
"monokai",
|
||||
"mysanityprojectid",
|
||||
"rgba",
|
||||
"rsmith",
|
||||
"tdewolff",
|
||||
|
||||
+3
-2
@@ -4,9 +4,10 @@
|
||||
/.vscode
|
||||
/dist
|
||||
/public
|
||||
/resources
|
||||
hugo_stats.json
|
||||
node_modules/
|
||||
nohup.out
|
||||
package-lock.json
|
||||
trace.out
|
||||
public/
|
||||
resources/
|
||||
trace.out
|
||||
@@ -22,6 +22,5 @@ MD041: false
|
||||
MD046: false
|
||||
MD049: false
|
||||
MD050: false
|
||||
MD051: false
|
||||
MD053: false
|
||||
MD055: false
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# Ignore all SVG icons.
|
||||
**/icons.html
|
||||
|
||||
# These are whitespace sensitive.
|
||||
layouts/_markup/render-code*
|
||||
layouts/_markup/render-table*
|
||||
layouts/_shortcodes/glossary-term.html
|
||||
layouts/_shortcodes/glossary.html
|
||||
layouts/_shortcodes/highlighting-styles.html
|
||||
layouts/_shortcodes/list-pages-in-section.html
|
||||
layouts/_shortcodes/quick-reference.html
|
||||
|
||||
# No root node.
|
||||
layouts/_partials/layouts/head/head.html
|
||||
|
||||
# Auto generated.
|
||||
assets/css/components/chroma*.css
|
||||
assets/jsconfig.json
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"plugins": [
|
||||
"prettier-plugin-go-template",
|
||||
"@awmottaz/prettier-plugin-void-html"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.html"],
|
||||
"options": {
|
||||
"parser": "go-template",
|
||||
"goTemplateBracketSpacing": true,
|
||||
"bracketSameLine": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.ts"],
|
||||
"options": {
|
||||
"useTabs": true,
|
||||
"printWidth": 120,
|
||||
"singleQuote": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
See [content/LICENSE.md](content/LICENSE.md) for the license of the content of this repository.
|
||||
|
||||
The theme (layouts, CSS, JavaScript etc.) of this repository has no open source license. It is custom made for the Hugo sites and is not meant for reuse.
|
||||
The theme (layouts, CSS, JavaScript etc.) of this repository has no open source license. It is custom made for the Hugo sites and is not meant for reuse.
|
||||
+2
-2
@@ -19,9 +19,9 @@ Please see the [contributing] section for guidelines, examples, and process.
|
||||
|
||||
# Install
|
||||
|
||||
```sh
|
||||
```bash
|
||||
npm i
|
||||
hugo server
|
||||
```
|
||||
|
||||
**Note:** We're working on removing the need to run `npm i` for local development. Stay tuned.
|
||||
**Note:** We're working on removing the need to run `npm i` for local development. Stay tuned.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
title: {{ replace .File.ContentBaseName "-" " " | strings.FirstUpper }}
|
||||
description:
|
||||
categories: []
|
||||
keywords: []
|
||||
---
|
||||
@@ -3,9 +3,9 @@ title: {{ replace .File.ContentBaseName "-" " " | title }}
|
||||
description:
|
||||
categories: []
|
||||
keywords: []
|
||||
params:
|
||||
functions_and_methods:
|
||||
aliases: []
|
||||
returnType:
|
||||
signatures: []
|
||||
action:
|
||||
aliases: []
|
||||
related: []
|
||||
returnType:
|
||||
signatures: []
|
||||
---
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
---
|
||||
title: {{ replace .File.ContentBaseName "-" " " }}
|
||||
params:
|
||||
reference:
|
||||
reference:
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
@@ -3,8 +3,8 @@ title: {{ replace .File.ContentBaseName "-" " " | title }}
|
||||
description:
|
||||
categories: []
|
||||
keywords: []
|
||||
params:
|
||||
functions_and_methods:
|
||||
returnType:
|
||||
signatures: []
|
||||
action:
|
||||
related: []
|
||||
returnType:
|
||||
signatures: []
|
||||
---
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: {{ replace .File.ContentBaseName "-" " " | strings.FirstUpper }}
|
||||
description:
|
||||
categories: []
|
||||
keywords: []
|
||||
publishDate: {{ .Date }}
|
||||
---
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: {{ replace .File.ContentBaseName "-" " " | title }}
|
||||
description:
|
||||
date: {{ .Date }}
|
||||
---
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user