mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-26 16:28:52 +00:00
Compare commits
213 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 518eefee9b | |||
| 3aa22b0942 | |||
| 40c3d8233d | |||
| 7ff5ec734c | |||
| 3937ab24d0 | |||
| 9c57af1351 | |||
| d240a705d6 | |||
| bbcc2a7973 | |||
| 98ba786f2f | |||
| 6f42cfbc9b | |||
| a84beee429 | |||
| 65893efd8d | |||
| c0d9bebacc | |||
| 3e2f1cdfdb | |||
| 0a5b870281 | |||
| bba6996e15 | |||
| 94e2c276a8 | |||
| 90d397b142 | |||
| 61e6c730dd | |||
| e4f6b9eef8 | |||
| bb147f91ee | |||
| 266d46dccc | |||
| e77b2ad8fd | |||
| 9487acf6ab | |||
| 84b31721bf | |||
| b8ba33ca95 | |||
| f967212b72 | |||
| 1b4c423667 | |||
| 1e9a0b93e3 | |||
| cfc8d315b4 | |||
| dd6e2c8724 | |||
| 762417617c | |||
| 29bdbde19c | |||
| 6a4a3ab8f8 | |||
| 36f6f987a9 | |||
| 18a9ca7d7a | |||
| b6c8dfa9dc | |||
| 621ea42f3c | |||
| 4ef5720141 | |||
| 34e83789f7 | |||
| 4d3ebe4d21 | |||
| b5c0383bda | |||
| 4217fee4b0 | |||
| fad57964aa | |||
| 10da2bd765 | |||
| 01241d5dc9 | |||
| 8e61f1fe12 | |||
| f37412a575 | |||
| 21a4a9acd7 | |||
| 7a4a4790e5 | |||
| 54065b7ef8 | |||
| e333836f49 | |||
| cc7bfeea32 | |||
| 32eb1a8ad4 | |||
| 32af02cd3e | |||
| 189453612e | |||
| 5273a884d4 | |||
| 6334948515 | |||
| 75259636c8 | |||
| 0df9f3510f | |||
| 302e6a726b | |||
| 202fe0d45c | |||
| 843ffeb48d | |||
| bff5d19121 | |||
| da370d30de | |||
| 6bd328c584 | |||
| 766a2e7868 | |||
| 13e1617557 | |||
| 463e440c7a | |||
| 0a5fd8ebb8 | |||
| e57dcd3795 | |||
| eaf5ace30d | |||
| 9ad26b69ad | |||
| f47193669d | |||
| 013c8cfb25 | |||
| e25db38467 | |||
| 7766fc6241 | |||
| 4a48facef4 | |||
| 0c7b1a3f26 | |||
| 970b887ba1 | |||
| b9b95e5aec | |||
| 84c8426f32 | |||
| a03a245f0c | |||
| 5a81a3a4cf | |||
| 61317821e4 | |||
| 6142bc701c | |||
| e6574cf7a7 | |||
| 05417512bd | |||
| 81426998b8 | |||
| 6def5a1ba9 | |||
| bc98e7a80d | |||
| c745a3e108 | |||
| 9d1d8c8899 | |||
| 84d7a108e8 | |||
| 325a0dba63 | |||
| d70f828e2b | |||
| c7feb15d10 | |||
| 363ab48a24 | |||
| 80f0595311 | |||
| b39b249623 | |||
| d799c045fd | |||
| 95666fc5a4 | |||
| 620fc87b56 | |||
| 8b2124e7c3 | |||
| 5fec7829b1 | |||
| 927d1ec6c1 | |||
| be93d5218b | |||
| a1cb15e1cf | |||
| 673a4d00eb | |||
| 31db7edf6d | |||
| 5857b60cbc | |||
| 7d0039b86d | |||
| 07983e04e2 | |||
| 5c491409d3 | |||
| 75b219db89 | |||
| ad4f63c92f | |||
| 53202314ab | |||
| 2fce0bac03 | |||
| 179aea11ac | |||
| b3d87dd0fd | |||
| 61a286595e | |||
| 6a0e04241a | |||
| 1bd7ac7ed9 | |||
| 41cb880f9a | |||
| df44ee1353 | |||
| 1ad3d39dc4 | |||
| 496730840e | |||
| 6d69dc88a4 | |||
| db72a1f075 | |||
| ad787614e8 | |||
| 9c65b9e88d | |||
| 4eb0e4286a | |||
| 5e62cc6fce | |||
| 1408c156d8 | |||
| be3b147860 | |||
| 99fff2997d | |||
| 1e0b058efe | |||
| 088cd2f996 | |||
| a88b488181 | |||
| d5a8c330cb | |||
| 61328976e1 | |||
| 64cf008880 | |||
| d61b9fc605 | |||
| 01667275d4 | |||
| 985af1c097 | |||
| 65c94c7b23 | |||
| e8e8ce10d2 | |||
| cf9e6904cc | |||
| 8a2830f2dc | |||
| 1e0287f472 | |||
| 915ba3f7f0 | |||
| 05ef8b713a | |||
| 30b9c19c76 | |||
| c8710625b7 | |||
| 53221f88ca | |||
| ff3ab192c2 | |||
| d1c394442b | |||
| 1074e01152 | |||
| c19f1f2363 | |||
| 9221cbca49 | |||
| e3e3f9ae17 | |||
| acc4fe24be | |||
| 3b9f2a7ded | |||
| 648204b3f1 | |||
| 5d1b9d3985 | |||
| 383dd82f95 | |||
| 653f1c1d46 | |||
| 5be51ac3db | |||
| 208a0de6c3 | |||
| 18d2d2f985 | |||
| cd3e3499bd | |||
| 8cd799aac9 | |||
| d4c6dd16b1 | |||
| af0602c343 | |||
| 650fe6cd01 | |||
| d63491beb2 | |||
| 24ac6a9de9 | |||
| e6e18e9122 | |||
| 43af3bc07d | |||
| 10b55439a8 | |||
| 1e00842489 | |||
| 83cfdd78ca | |||
| 812ea0b325 | |||
| 07cbe5701e | |||
| c15ebce2fd | |||
| 6f14dbe24c | |||
| 8d2379bcb3 | |||
| 26d986fc0d | |||
| ebc0693659 | |||
| 7ca6bb06b6 | |||
| bddd2f9001 | |||
| 1c691358f7 | |||
| 17db4edb02 | |||
| 3968f9158e | |||
| e9bd2373a0 | |||
| a6bd67793b | |||
| f34cdc382a | |||
| 52561d561a | |||
| f4f21f5ea3 | |||
| a98ec3bd01 | |||
| d28c84a871 | |||
| 61c39ae63b | |||
| 2037137fbf | |||
| d78d4cf161 | |||
| f9aae1581b | |||
| b9add1c702 | |||
| b0686712ba | |||
| 93d9c0533a | |||
| 93df17661f | |||
| eebea9ec41 | |||
| 3f2e1c08e3 | |||
| 3a11d22da3 | |||
| cb4a097190 |
+4
-4
@@ -2,8 +2,8 @@
|
||||
# Twitter: https://twitter.com/gohugoio
|
||||
# Website: https://gohugo.io/
|
||||
|
||||
ARG GO_VERSION="1.23.2"
|
||||
ARG ALPINE_VERSION="3.20"
|
||||
ARG GO_VERSION="1.24"
|
||||
ARG ALPINE_VERSION="3.22"
|
||||
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,9 +65,11 @@ 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/gohugoioTheme/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/hugoDocs/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/gohugoioTheme/master/assets/images/sponsors/goland.svg" width="200" alt="The complete IDE crafted for professional Go developers."></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>
|
||||
</p>
|
||||
|
||||
## Editions
|
||||
@@ -189,7 +191,6 @@ 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
+12
-1
@@ -188,7 +188,7 @@ func (gm *GlobMatcher) CompilePredicate() (func(string) bool, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func DecodeConfig(bcfg config.BaseConfig, m map[string]any) (Config, error) {
|
||||
func DecodeConfig(_ config.BaseConfig, m map[string]any) (Config, error) {
|
||||
if len(m) == 0 {
|
||||
return DefaultConfig, nil
|
||||
}
|
||||
@@ -214,5 +214,16 @@ func DecodeConfig(bcfg config.BaseConfig, m map[string]any) (Config, error) {
|
||||
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
|
||||
}
|
||||
|
||||
Vendored
+31
@@ -17,6 +17,7 @@ import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
)
|
||||
|
||||
func TestGlobMatcher(t *testing.T) {
|
||||
@@ -40,3 +41,33 @@ 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)
|
||||
}
|
||||
|
||||
+13
-3
@@ -50,6 +50,7 @@ func newGenCommand() *genCommand {
|
||||
highlightStyle string
|
||||
lineNumbersInlineStyle string
|
||||
lineNumbersTableStyle string
|
||||
omitEmpty bool
|
||||
)
|
||||
|
||||
newChromaStyles := func() simplecobra.Commander {
|
||||
@@ -79,7 +80,14 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formatter := html.New(html.WithAllClasses(true))
|
||||
|
||||
var formatter *html.Formatter
|
||||
if omitEmpty {
|
||||
formatter = html.New(html.WithClasses(true))
|
||||
} else {
|
||||
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)
|
||||
@@ -95,6 +103,8 @@ 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)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -174,13 +184,13 @@ url: %s
|
||||
prepender := func(filename string) string {
|
||||
name := filepath.Base(filename)
|
||||
base := strings.TrimSuffix(name, path.Ext(name))
|
||||
url := "/commands/" + strings.ToLower(base) + "/"
|
||||
url := "/docs/reference/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 "/commands/" + strings.ToLower(base) + "/"
|
||||
return "/docs/reference/commands/" + strings.ToLower(base) + "/"
|
||||
}
|
||||
r.Println("Generating Hugo command-line documentation in", gendocdir, "...")
|
||||
doc.GenMarkdownTreeCustom(cd.CobraCommand.Root(), gendocdir, prepender, linkHandler)
|
||||
|
||||
@@ -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 {
|
||||
} else if len(otherChanges) > 0 || len(cssChanges) > 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,6 +972,9 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
lrl.Logf("force refresh")
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
} else {
|
||||
lrl.Logf("force refresh")
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
|
||||
if len(cssChanges) > 0 {
|
||||
|
||||
+14
-11
@@ -76,10 +76,8 @@ Ensure you run this within the root directory of your site.`,
|
||||
&simpleCommand{
|
||||
name: "site",
|
||||
use: "site [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.`,
|
||||
short: "Create a new site",
|
||||
long: `Create a new site at the specified path.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return newUserError("path needs to be provided")
|
||||
@@ -124,11 +122,9 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
|
||||
&simpleCommand{
|
||||
name: "theme",
|
||||
use: "theme [name]",
|
||||
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.`,
|
||||
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.`,
|
||||
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")
|
||||
@@ -144,7 +140,7 @@ according to your needs.`,
|
||||
createpath := paths.AbsPathify(conf.configs.Base.WorkingDir, filepath.Join(conf.configs.Base.ThemesDir, args[0]))
|
||||
r.Println("Creating new theme in", createpath)
|
||||
|
||||
err = skeletons.CreateTheme(createpath, sourceFs)
|
||||
err = skeletons.CreateTheme(createpath, sourceFs, format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -152,7 +148,14 @@ according to your needs.`,
|
||||
return nil
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
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))
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+9
-9
@@ -23,6 +23,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
@@ -48,6 +49,7 @@ 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"
|
||||
@@ -57,7 +59,6 @@ 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"
|
||||
@@ -65,7 +66,6 @@ import (
|
||||
"github.com/spf13/fsync"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sync/semaphore"
|
||||
"maps"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -627,7 +627,7 @@ func (c *serverCommand) setServerInfoInConfig() error {
|
||||
panic("no server ports set")
|
||||
}
|
||||
return c.withConfE(func(conf *commonConfig) error {
|
||||
for i, language := range conf.configs.Languages {
|
||||
for i, language := range conf.configs.LanguagesDefaultFirst {
|
||||
isMultihost := conf.configs.IsMultihost
|
||||
var serverPort int
|
||||
if isMultihost {
|
||||
@@ -897,16 +897,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 tpl.Template
|
||||
templHandler tpl.TemplateHandler
|
||||
errTempl *tplimpl.TemplInfo
|
||||
templHandler *tplimpl.TemplateStore
|
||||
)
|
||||
getErrorTemplateAndHandler := func(h *hugolib.HugoSites) (tpl.Template, tpl.TemplateHandler) {
|
||||
getErrorTemplateAndHandler := func(h *hugolib.HugoSites) (*tplimpl.TemplInfo, *tplimpl.TemplateStore) {
|
||||
if h == nil {
|
||||
return errTempl, templHandler
|
||||
}
|
||||
templHandler := h.Tmpl()
|
||||
errTempl, found := templHandler.Lookup("_server/error.html")
|
||||
if !found {
|
||||
templHandler := h.GetTemplateStore()
|
||||
errTempl := templHandler.LookupByPath("/_server/error.html")
|
||||
if errTempl == nil {
|
||||
panic("template server/error.html not found")
|
||||
}
|
||||
return errTempl, templHandler
|
||||
|
||||
@@ -15,6 +15,7 @@ package collections
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
@@ -77,6 +78,7 @@ 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...)
|
||||
@@ -146,3 +148,66 @@ 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,3 +136,37 @@ 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
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,6 +23,8 @@ 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.
|
||||
|
||||
+5
-18
@@ -27,7 +27,6 @@ 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"
|
||||
@@ -113,18 +112,6 @@ 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
|
||||
@@ -197,7 +184,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 := safeexec.LookPath(nodeBinFilename)
|
||||
_, err := exec.LookPath(nodeBinFilename)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -215,7 +202,7 @@ func (e *Exec) Npx(name string, arg ...any) (Runner, error) {
|
||||
}
|
||||
},
|
||||
binaryLocationPath: func() func(...any) (Runner, error) {
|
||||
if _, err := safeexec.LookPath(name); err != nil {
|
||||
if _, err := exec.LookPath(name); err != nil {
|
||||
return nil
|
||||
}
|
||||
return func(arg2 ...any) (Runner, error) {
|
||||
@@ -346,7 +333,7 @@ func (c *commandeer) command(arg ...any) (*cmdWrapper, error) {
|
||||
bin = c.fullyQualifiedName
|
||||
} else {
|
||||
var err error
|
||||
bin, err = safeexec.LookPath(c.name)
|
||||
bin, err = exec.LookPath(c.name)
|
||||
if err != nil {
|
||||
return nil, &NotFoundError{
|
||||
name: c.name,
|
||||
@@ -384,7 +371,7 @@ func InPath(binaryName string) bool {
|
||||
if strings.Contains(binaryName, "/") {
|
||||
panic("binary name should not contain any slash")
|
||||
}
|
||||
_, err := safeexec.LookPath(binaryName)
|
||||
_, err := exec.LookPath(binaryName)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -394,7 +381,7 @@ func LookPath(binaryName string) string {
|
||||
if strings.Contains(binaryName, "/") {
|
||||
panic("binary name should not contain any slash")
|
||||
}
|
||||
s, err := safeexec.LookPath(binaryName)
|
||||
s, err := exec.LookPath(binaryName)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -134,12 +134,7 @@ type methodKey struct {
|
||||
name string
|
||||
}
|
||||
|
||||
type methods struct {
|
||||
sync.RWMutex
|
||||
cache map[methodKey]int
|
||||
}
|
||||
|
||||
var methodCache = &methods{cache: make(map[methodKey]int)}
|
||||
var methodCache sync.Map
|
||||
|
||||
// GetMethodByName is the same as reflect.Value.MethodByName, but it caches the
|
||||
// type lookup.
|
||||
@@ -157,22 +152,16 @@ 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}
|
||||
methodCache.RLock()
|
||||
index, found := methodCache.cache[k]
|
||||
methodCache.RUnlock()
|
||||
v, found := methodCache.Load(k)
|
||||
if found {
|
||||
return index
|
||||
return v.(int)
|
||||
}
|
||||
|
||||
methodCache.Lock()
|
||||
defer methodCache.Unlock()
|
||||
|
||||
m, ok := tp.MethodByName(name)
|
||||
index = m.Index
|
||||
index := m.Index
|
||||
if !ok {
|
||||
index = -1
|
||||
}
|
||||
methodCache.cache[k] = index
|
||||
methodCache.Store(k, index)
|
||||
|
||||
if !ok {
|
||||
return -1
|
||||
|
||||
@@ -134,3 +134,17 @@ 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,11 +16,11 @@ package hstrings
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gohugoio/hugo/compare"
|
||||
"slices"
|
||||
)
|
||||
|
||||
var _ compare.Eqer = StringEqualFold("")
|
||||
@@ -128,7 +128,7 @@ func ToString(v any) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
type Tuple struct {
|
||||
First string
|
||||
Second string
|
||||
}
|
||||
type (
|
||||
Strings2 [2]string
|
||||
Strings3 [3]string
|
||||
)
|
||||
|
||||
+21
-6
@@ -139,9 +139,13 @@ func (i HugoInfo) IsMultilingual() bool {
|
||||
return i.conf.IsMultilingual()
|
||||
}
|
||||
|
||||
type contextKey string
|
||||
type contextKey uint8
|
||||
|
||||
var markupScope = hcontext.NewContextDispatcher[string](contextKey("markupScope"))
|
||||
const (
|
||||
contextKeyMarkupScope contextKey = iota
|
||||
)
|
||||
|
||||
var markupScope = hcontext.NewContextDispatcher[string](contextKeyMarkupScope)
|
||||
|
||||
type Context struct{}
|
||||
|
||||
@@ -411,17 +415,28 @@ 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)
|
||||
deprecateLevel(item, alternative, version, level)
|
||||
}
|
||||
|
||||
// See Deprecate for details.
|
||||
func DeprecateWithLogger(item, alternative string, version string, log logg.Logger) {
|
||||
level := deprecationLogLevelFromVersion(version)
|
||||
deprecateLevelWithLogger(item, alternative, version, level, log)
|
||||
}
|
||||
|
||||
// DeprecateLevelMin informs about a deprecation starting at the given version, but with a minimum log level.
|
||||
func DeprecateLevelMin(item, alternative string, version string, minLevel logg.Level) {
|
||||
level := max(deprecationLogLevelFromVersion(version), minLevel)
|
||||
DeprecateLevel(item, alternative, version, level)
|
||||
deprecateLevel(item, alternative, version, level)
|
||||
}
|
||||
|
||||
// deprecateLevel informs about a deprecation logging at the given level.
|
||||
func deprecateLevel(item, alternative, version string, level logg.Level) {
|
||||
deprecateLevelWithLogger(item, alternative, version, level, loggers.Log().Logger())
|
||||
}
|
||||
|
||||
// DeprecateLevel informs about a deprecation logging at the given level.
|
||||
func DeprecateLevel(item, alternative, version string, level logg.Level) {
|
||||
func deprecateLevelWithLogger(item, alternative, version string, level logg.Level, log logg.Logger) {
|
||||
var msg string
|
||||
if level == logg.LevelError {
|
||||
msg = fmt.Sprintf("%s was deprecated in Hugo %s and subsequently removed. %s", item, version, alternative)
|
||||
@@ -429,7 +444,7 @@ func DeprecateLevel(item, alternative, version string, level logg.Level) {
|
||||
msg = fmt.Sprintf("%s was deprecated in Hugo %s and will be removed in a future release. %s", item, version, alternative)
|
||||
}
|
||||
|
||||
loggers.Log().Logger().WithLevel(level).WithField(loggers.FieldNameCmd, "deprecated").Logf("%s", msg)
|
||||
log.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: 145,
|
||||
Minor: 149,
|
||||
PatchLevel: 0,
|
||||
Suffix: "",
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
+20
-1
@@ -69,6 +69,14 @@ 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) {
|
||||
@@ -108,6 +116,17 @@ 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
|
||||
}
|
||||
@@ -141,7 +160,7 @@ func (c *Cache[K, T]) Len() int {
|
||||
|
||||
func (c *Cache[K, T]) Reset() {
|
||||
c.Lock()
|
||||
c.m = make(map[K]T)
|
||||
clear(c.m)
|
||||
c.hasBeenInitialized = false
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
+11
-1
@@ -14,8 +14,9 @@
|
||||
package maps
|
||||
|
||||
import (
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"slices"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
)
|
||||
|
||||
// Ordered is a map that can be iterated in the order of insertion.
|
||||
@@ -57,6 +58,15 @@ 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 {
|
||||
|
||||
+308
-98
@@ -23,6 +23,11 @@ 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.
|
||||
@@ -33,6 +38,10 @@ 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
|
||||
}
|
||||
@@ -83,13 +92,10 @@ func (pp *PathParser) Parse(c, s string) *Path {
|
||||
}
|
||||
|
||||
func (pp *PathParser) newPath(component string) *Path {
|
||||
return &Path{
|
||||
component: component,
|
||||
posContainerLow: -1,
|
||||
posContainerHigh: -1,
|
||||
posSectionHigh: -1,
|
||||
posIdentifierLanguage: -1,
|
||||
}
|
||||
p := &Path{}
|
||||
p.reset()
|
||||
p.component = component
|
||||
return p
|
||||
}
|
||||
|
||||
func (pp *PathParser) parse(component, s string) (*Path, error) {
|
||||
@@ -114,10 +120,101 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
|
||||
hasLang := pp.LanguageIndex != nil
|
||||
hasLang = hasLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
|
||||
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) {
|
||||
if runtime.GOOS == "windows" {
|
||||
s = path.Clean(filepath.ToSlash(s))
|
||||
if s == "." {
|
||||
@@ -140,46 +237,26 @@ 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 '.':
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pp.parseIdentifier(component, s, p, i, lastDot, numDots, false)
|
||||
lastDot = i
|
||||
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
|
||||
@@ -190,26 +267,52 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.identifiers) > 0 {
|
||||
if len(p.identifiersKnown) > 0 {
|
||||
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
|
||||
isContent := isContentComponent && pp.IsContentExt(p.Ext())
|
||||
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
|
||||
}
|
||||
id := p.identifiersKnown[len(p.identifiersKnown)-1]
|
||||
|
||||
if slashCount == 2 && p.IsLeafBundle() {
|
||||
p.posSectionHigh = 0
|
||||
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
|
||||
}
|
||||
} else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
|
||||
p.bundleType = PathTypeContentData
|
||||
}
|
||||
}
|
||||
|
||||
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 p.pathType == TypeShortcode && p.posIdentifierLayout != -1 {
|
||||
id := p.identifiersKnown[p.posIdentifierLayout]
|
||||
if id.Low == p.posContainerHigh {
|
||||
// First identifier is shortcode name.
|
||||
p.posIdentifierLayout = -1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,35 +321,44 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
|
||||
|
||||
func ModifyPathBundleTypeResource(p *Path) {
|
||||
if p.IsContent() {
|
||||
p.bundleType = PathTypeContentResource
|
||||
p.pathType = TypeContentResource
|
||||
} else {
|
||||
p.bundleType = PathTypeFile
|
||||
p.pathType = TypeFile
|
||||
}
|
||||
}
|
||||
|
||||
type PathType int
|
||||
//go:generate stringer -type Type
|
||||
|
||||
type Type int
|
||||
|
||||
const (
|
||||
|
||||
// A generic resource, e.g. a JSON file.
|
||||
PathTypeFile PathType = iota
|
||||
TypeFile Type = iota
|
||||
|
||||
// All below are content files.
|
||||
// A resource of a content type with front matter.
|
||||
PathTypeContentResource
|
||||
TypeContentResource
|
||||
|
||||
// E.g. /blog/my-post.md
|
||||
PathTypeContentSingle
|
||||
TypeContentSingle
|
||||
|
||||
// All below are bundled content files.
|
||||
|
||||
// Leaf bundles, e.g. /blog/my-post/index.md
|
||||
PathTypeLeaf
|
||||
TypeLeaf
|
||||
|
||||
// Branch bundles, e.g. /blog/_index.md
|
||||
PathTypeBranch
|
||||
TypeBranch
|
||||
|
||||
// Content data file, _content.gotmpl.
|
||||
PathTypeContentData
|
||||
TypeContentData
|
||||
|
||||
// Layout types.
|
||||
TypeMarkup
|
||||
TypeShortcode
|
||||
TypePartial
|
||||
TypeBaseof
|
||||
)
|
||||
|
||||
type Path struct {
|
||||
@@ -257,13 +369,18 @@ type Path struct {
|
||||
posContainerHigh int
|
||||
posSectionHigh int
|
||||
|
||||
component string
|
||||
bundleType PathType
|
||||
component string
|
||||
pathType Type
|
||||
|
||||
identifiers []types.LowHigh[string]
|
||||
identifiersKnown []types.LowHigh[string]
|
||||
identifiersUnknown []types.LowHigh[string]
|
||||
|
||||
posIdentifierLanguage int
|
||||
disabled bool
|
||||
posIdentifierLanguage int
|
||||
posIdentifierOutputFormat int
|
||||
posIdentifierKind int
|
||||
posIdentifierLayout int
|
||||
posIdentifierBaseof int
|
||||
disabled bool
|
||||
|
||||
trimLeadingSlash bool
|
||||
|
||||
@@ -293,9 +410,13 @@ func (p *Path) reset() {
|
||||
p.posContainerHigh = -1
|
||||
p.posSectionHigh = -1
|
||||
p.component = ""
|
||||
p.bundleType = 0
|
||||
p.identifiers = p.identifiers[:0]
|
||||
p.pathType = 0
|
||||
p.identifiersKnown = p.identifiersKnown[: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
|
||||
@@ -316,6 +437,9 @@ 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()
|
||||
}
|
||||
|
||||
@@ -332,6 +456,13 @@ 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 {
|
||||
@@ -352,13 +483,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.BundleType() >= PathTypeContentResource
|
||||
return p.Type() >= TypeContentResource && p.Type() <= TypeContentData
|
||||
}
|
||||
|
||||
// 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.BundleType() >= PathTypeContentSingle
|
||||
return p.Type() >= TypeContentSingle && p.Type() <= TypeContentData
|
||||
}
|
||||
|
||||
// Name returns the last element of path.
|
||||
@@ -372,7 +503,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.identifiers[i].Low-1]
|
||||
return p.s[p.posContainerHigh : p.identifiersKnown[i].Low-1]
|
||||
}
|
||||
return p.s[p.posContainerHigh:]
|
||||
}
|
||||
@@ -384,7 +515,7 @@ func (p *Path) NameNoLang() string {
|
||||
return p.Name()
|
||||
}
|
||||
|
||||
return p.s[p.posContainerHigh:p.identifiers[i].Low-1] + p.s[p.identifiers[i].High:]
|
||||
return p.s[p.posContainerHigh:p.identifiersKnown[i].Low-1] + p.s[p.identifiersKnown[i].High:]
|
||||
}
|
||||
|
||||
// BaseNameNoIdentifier returns the logical base name for a resource without any identifier (e.g. no extension).
|
||||
@@ -398,10 +529,26 @@ func (p *Path) BaseNameNoIdentifier() string {
|
||||
|
||||
// NameNoIdentifier returns the last element of path without any identifier (e.g. no extension).
|
||||
func (p *Path) NameNoIdentifier() string {
|
||||
if len(p.identifiers) > 0 {
|
||||
return p.s[p.posContainerHigh : p.identifiers[len(p.identifiers)-1].Low-1]
|
||||
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),
|
||||
}
|
||||
return p.s[p.posContainerHigh:]
|
||||
}
|
||||
|
||||
// Dir returns all but the last element of path, typically the path's directory.
|
||||
@@ -421,6 +568,11 @@ 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
|
||||
@@ -436,6 +588,28 @@ 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()
|
||||
@@ -462,26 +636,42 @@ 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.identifiers) == 0 {
|
||||
if len(p.identifiersKnown) == 0 {
|
||||
return p.norm(p.s)
|
||||
}
|
||||
|
||||
if preserveExt && len(p.identifiers) == 1 {
|
||||
if preserveExt && len(p.identifiersKnown) == 1 {
|
||||
// Preserve extension.
|
||||
return p.norm(p.s)
|
||||
}
|
||||
|
||||
id := p.identifiers[len(p.identifiers)-1]
|
||||
high := id.Low - 1
|
||||
var high int
|
||||
|
||||
if isBundle {
|
||||
high = p.posContainerHigh - 1
|
||||
} else {
|
||||
high = p.nameLowHigh().High
|
||||
}
|
||||
|
||||
if high == 0 {
|
||||
@@ -493,7 +683,7 @@ func (p *Path) base(preserveExt, isBundle bool) string {
|
||||
}
|
||||
|
||||
// For txt files etc. we want to preserve the extension.
|
||||
id = p.identifiers[0]
|
||||
id := p.identifiersKnown[0]
|
||||
|
||||
return p.norm(p.s[:high] + p.s[id.Low-1:id.High])
|
||||
}
|
||||
@@ -502,8 +692,20 @@ 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(1)
|
||||
return p.identifierAsString(p.posIdentifierLanguage)
|
||||
}
|
||||
|
||||
func (p *Path) Identifier(i int) string {
|
||||
@@ -515,35 +717,43 @@ func (p *Path) Disabled() bool {
|
||||
}
|
||||
|
||||
func (p *Path) Identifiers() []string {
|
||||
ids := make([]string, len(p.identifiers))
|
||||
for i, id := range p.identifiers {
|
||||
ids := make([]string, len(p.identifiersKnown))
|
||||
for i, id := range p.identifiersKnown {
|
||||
ids[i] = p.s[id.Low:id.High]
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func (p *Path) BundleType() PathType {
|
||||
return p.bundleType
|
||||
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) IsBundle() bool {
|
||||
return p.bundleType >= PathTypeLeaf
|
||||
return p.pathType >= TypeLeaf && p.pathType <= TypeContentData
|
||||
}
|
||||
|
||||
func (p *Path) IsBranchBundle() bool {
|
||||
return p.bundleType == PathTypeBranch
|
||||
return p.pathType == TypeBranch
|
||||
}
|
||||
|
||||
func (p *Path) IsLeafBundle() bool {
|
||||
return p.bundleType == PathTypeLeaf
|
||||
return p.pathType == TypeLeaf
|
||||
}
|
||||
|
||||
func (p *Path) IsContentData() bool {
|
||||
return p.bundleType == PathTypeContentData
|
||||
return p.pathType == TypeContentData
|
||||
}
|
||||
|
||||
func (p Path) ForBundleType(t PathType) *Path {
|
||||
p.bundleType = t
|
||||
func (p Path) ForType(t Type) *Path {
|
||||
p.pathType = t
|
||||
return &p
|
||||
}
|
||||
|
||||
@@ -553,12 +763,12 @@ func (p *Path) identifierAsString(i int) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
id := p.identifiers[i]
|
||||
id := p.identifiersKnown[i]
|
||||
return p.s[id.Low:id.High]
|
||||
}
|
||||
|
||||
func (p *Path) identifierIndex(i int) int {
|
||||
if i < 0 || i >= len(p.identifiers) {
|
||||
if i < 0 || i >= len(p.identifiersKnown) {
|
||||
return -1
|
||||
}
|
||||
return i
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/resources/kinds"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
@@ -26,10 +27,18 @@ 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) {
|
||||
@@ -105,17 +114,19 @@ 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")
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -130,7 +141,7 @@ func TestParse(t *testing.T) {
|
||||
|
||||
// Reclassify it as a content resource.
|
||||
ModifyPathBundleTypeResource(p)
|
||||
c.Assert(p.BundleType(), qt.Equals, PathTypeContentResource)
|
||||
c.Assert(p.Type(), qt.Equals, TypeContentResource)
|
||||
c.Assert(p.IsContent(), qt.IsTrue)
|
||||
c.Assert(p.Name(), qt.Equals, "b.md")
|
||||
c.Assert(p.Base(), qt.Equals, "/a/b.md")
|
||||
@@ -163,6 +174,7 @@ 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")
|
||||
@@ -175,7 +187,11 @@ 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, "/")
|
||||
@@ -186,12 +202,14 @@ 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)
|
||||
@@ -209,6 +227,7 @@ 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")
|
||||
@@ -357,11 +376,225 @@ 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,3 +78,26 @@ 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
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
// 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]]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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]]
|
||||
}
|
||||
@@ -17,7 +17,6 @@ package terminal
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
@@ -41,10 +40,6 @@ 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))
|
||||
}
|
||||
|
||||
@@ -28,6 +28,16 @@ type RLocker interface {
|
||||
RUnlock()
|
||||
}
|
||||
|
||||
type Locker interface {
|
||||
Lock()
|
||||
Unlock()
|
||||
}
|
||||
|
||||
type RWLocker interface {
|
||||
RLocker
|
||||
Locker
|
||||
}
|
||||
|
||||
// KeyValue is a interface{} tuple.
|
||||
type KeyValue struct {
|
||||
Key any
|
||||
|
||||
+154
-42
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -32,7 +33,6 @@ 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,6 +42,7 @@ 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"
|
||||
@@ -146,7 +147,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, maps.Params]] `mapstructure:"-"`
|
||||
Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]] `mapstructure:"-"`
|
||||
|
||||
// The segments defines segments for the site. Used for partial/segmented builds.
|
||||
Segments *config.ConfigNamespace[map[string]segments.SegmentConfig, segments.Segments] `mapstructure:"-"`
|
||||
@@ -396,44 +397,80 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
|
||||
// Legacy paginate values.
|
||||
if c.Paginate != 0 {
|
||||
hugo.Deprecate("site config key paginate", "Use pagination.pagerSize instead.", "v0.128.0")
|
||||
hugo.DeprecateWithLogger("site config key paginate", "Use pagination.pagerSize instead.", "v0.128.0", logger.Logger())
|
||||
c.Pagination.PagerSize = c.Paginate
|
||||
}
|
||||
|
||||
if c.PaginatePath != "" {
|
||||
hugo.Deprecate("site config key paginatePath", "Use pagination.path instead.", "v0.128.0")
|
||||
hugo.DeprecateWithLogger("site config key paginatePath", "Use pagination.path instead.", "v0.128.0", logger.Logger())
|
||||
c.Pagination.Path = c.PaginatePath
|
||||
}
|
||||
|
||||
// Legacy privacy values.
|
||||
if c.Privacy.Twitter.Disable {
|
||||
hugo.Deprecate("site config key privacy.twitter.disable", "Use privacy.x.disable instead.", "v0.141.0")
|
||||
hugo.DeprecateWithLogger("site config key privacy.twitter.disable", "Use privacy.x.disable instead.", "v0.141.0", logger.Logger())
|
||||
c.Privacy.X.Disable = c.Privacy.Twitter.Disable
|
||||
}
|
||||
|
||||
if c.Privacy.Twitter.EnableDNT {
|
||||
hugo.Deprecate("site config key privacy.twitter.enableDNT", "Use privacy.x.enableDNT instead.", "v0.141.0")
|
||||
hugo.DeprecateWithLogger("site config key privacy.twitter.enableDNT", "Use privacy.x.enableDNT instead.", "v0.141.0", logger.Logger())
|
||||
c.Privacy.X.EnableDNT = c.Privacy.Twitter.EnableDNT
|
||||
}
|
||||
|
||||
if c.Privacy.Twitter.Simple {
|
||||
hugo.Deprecate("site config key privacy.twitter.simple", "Use privacy.x.simple instead.", "v0.141.0")
|
||||
hugo.DeprecateWithLogger("site config key privacy.twitter.simple", "Use privacy.x.simple instead.", "v0.141.0", logger.Logger())
|
||||
c.Privacy.X.Simple = c.Privacy.Twitter.Simple
|
||||
}
|
||||
|
||||
// Legacy services values.
|
||||
if c.Services.Twitter.DisableInlineCSS {
|
||||
hugo.Deprecate("site config key services.twitter.disableInlineCSS", "Use services.x.disableInlineCSS instead.", "v0.141.0")
|
||||
hugo.DeprecateWithLogger("site config key services.twitter.disableInlineCSS", "Use services.x.disableInlineCSS instead.", "v0.141.0", logger.Logger())
|
||||
c.Services.X.DisableInlineCSS = c.Services.Twitter.DisableInlineCSS
|
||||
}
|
||||
|
||||
// Legacy permalink tokens
|
||||
vs := fmt.Sprintf("%v", c.Permalinks)
|
||||
if strings.Contains(vs, ":filename") {
|
||||
hugo.Deprecate("the \":filename\" permalink token", "Use \":contentbasename\" instead.", "0.144.0")
|
||||
hugo.DeprecateWithLogger("the \":filename\" permalink token", "Use \":contentbasename\" instead.", "0.144.0", logger.Logger())
|
||||
}
|
||||
if strings.Contains(vs, ":slugorfilename") {
|
||||
hugo.Deprecate("the \":slugorfilename\" permalink token", "Use \":slugorcontentbasename\" instead.", "0.144.0")
|
||||
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"))
|
||||
}
|
||||
|
||||
c.C = &ConfigCompiled{
|
||||
@@ -776,7 +813,7 @@ type Configs struct {
|
||||
}
|
||||
|
||||
func (c *Configs) Validate(logger loggers.Logger) error {
|
||||
c.Base.Cascade.Config.Range(func(p page.PageMatcher, params maps.Params) bool {
|
||||
c.Base.Cascade.Config.Range(func(p page.PageMatcher, cfg page.PageMatcherParamsConfig) bool {
|
||||
page.CheckCascadePattern(logger, p)
|
||||
return true
|
||||
})
|
||||
@@ -800,30 +837,58 @@ func (c *Configs) IsZero() bool {
|
||||
|
||||
func (c *Configs) Init() error {
|
||||
var languages langs.Languages
|
||||
defaultContentLanguage := c.Base.DefaultContentLanguage
|
||||
for k, v := range c.LanguageConfigMap {
|
||||
|
||||
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)
|
||||
languageConf := v.Languages[k]
|
||||
language, err := langs.NewLanguage(k, defaultContentLanguage, v.TimeZone, languageConf)
|
||||
language, err := langs.NewLanguage(k, c.Base.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 {
|
||||
@@ -836,12 +901,12 @@ func (c *Configs) Init() error {
|
||||
|
||||
var languagesDefaultFirst langs.Languages
|
||||
for _, l := range languages {
|
||||
if l.Lang == defaultContentLanguage {
|
||||
if l.Lang == c.Base.DefaultContentLanguage {
|
||||
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
||||
}
|
||||
}
|
||||
for _, l := range languages {
|
||||
if l.Lang != defaultContentLanguage {
|
||||
if l.Lang != c.Base.DefaultContentLanguage {
|
||||
languagesDefaultFirst = append(languagesDefaultFirst, l)
|
||||
}
|
||||
}
|
||||
@@ -849,7 +914,24 @@ 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}
|
||||
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.configLangs = make([]config.AllProvider, len(c.Languages))
|
||||
for i, l := range c.LanguagesDefaultFirst {
|
||||
@@ -910,17 +992,48 @@ 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 := &Config{}
|
||||
all := newDefaultConfig()
|
||||
|
||||
err := decodeConfigFromParams(fs, logger, bcfg, cfg, all, nil)
|
||||
if err != nil {
|
||||
@@ -930,6 +1043,7 @@ 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 {
|
||||
@@ -1005,13 +1119,11 @@ 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.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)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,14 @@ 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"
|
||||
)
|
||||
|
||||
@@ -234,3 +237,172 @@ 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,14 +249,18 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
key: "sitemap",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Sitemap, err = config.DecodeSitemap(config.SitemapConfig{Priority: -1, Filename: "sitemap.xml"}, p.p.GetStringMap(d.key))
|
||||
if p.p.IsSet(d.key) {
|
||||
p.c.Sitemap, err = config.DecodeSitemap(p.c.Sitemap, p.p.GetStringMap(d.key))
|
||||
}
|
||||
return err
|
||||
},
|
||||
},
|
||||
"taxonomies": {
|
||||
key: "taxonomies",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
|
||||
if p.p.IsSet(d.key) {
|
||||
p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
@@ -306,15 +310,17 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
}
|
||||
|
||||
// Validate defaultContentLanguage.
|
||||
var found bool
|
||||
for lang := range p.c.Languages {
|
||||
if lang == p.c.DefaultContentLanguage {
|
||||
found = true
|
||||
break
|
||||
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)
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("config value %q for defaultContentLanguage does not match any language definition", p.c.DefaultContentLanguage)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -324,7 +330,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, p.p.Get(d.key))
|
||||
p.c.Cascade, err = page.DecodeCascadeConfig(nil, true, p.p.Get(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
|
||||
+40
-83
@@ -159,63 +159,9 @@ func (l configLoader) applyConfigAliases() error {
|
||||
|
||||
func (l configLoader) applyDefaultConfig() error {
|
||||
defaultSettings := maps.Params{
|
||||
"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,
|
||||
// These dirs are used early/before we build the config struct.
|
||||
"themesDir": "themes",
|
||||
"configDir": "config",
|
||||
}
|
||||
|
||||
l.cfg.SetDefaults(defaultSettings)
|
||||
@@ -287,40 +233,51 @@ func (l configLoader) applyOsEnvOverrides(environ []string) error {
|
||||
|
||||
if existing != nil {
|
||||
val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing)
|
||||
if err != nil {
|
||||
if err == nil {
|
||||
val = l.envValToVal(env.Key, val)
|
||||
if owner != nil {
|
||||
owner[nestedKey] = val
|
||||
} else {
|
||||
l.cfg.Set(env.Key, val)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if owner != nil {
|
||||
owner[nestedKey] = val
|
||||
} else {
|
||||
l.cfg.Set(env.Key, val)
|
||||
}
|
||||
} else {
|
||||
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]any{}); err == nil {
|
||||
val = v
|
||||
}
|
||||
}
|
||||
if val == nil {
|
||||
// A string.
|
||||
val = l.envStringToVal(key, env.Value)
|
||||
}
|
||||
l.cfg.Set(key, val)
|
||||
}
|
||||
}
|
||||
|
||||
if owner != nil && 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]any{}); err == nil {
|
||||
val = v
|
||||
}
|
||||
}
|
||||
|
||||
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,6 +17,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -28,7 +29,6 @@ import (
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/cast"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type BaseConfig struct {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -32,3 +33,13 @@ 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 defineds a sub set of what media.ContentTypes provides.
|
||||
// This interface defines a subset of what media.ContentTypes provides.
|
||||
type ContentTypesProvider interface {
|
||||
IsContentSuffix(suffix string) bool
|
||||
IsContentFile(filename string) bool
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -353,6 +354,90 @@ 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) {
|
||||
|
||||
@@ -101,6 +101,9 @@ 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.Tuple]bool{}
|
||||
seen := map[hstrings.Strings2]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.Tuple{First: pi.PathNoIdentifier(), Second: fim.Meta().Lang}
|
||||
pathLang := hstrings.Strings2{pi.PathBeforeLangAndOutputFormatAndExt(), 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
|
||||
|
||||
@@ -34,10 +34,60 @@ var siteFs embed.FS
|
||||
var themeFs embed.FS
|
||||
|
||||
// CreateTheme creates a theme skeleton.
|
||||
func CreateTheme(createpath string, sourceFs afero.Fs) error {
|
||||
func CreateTheme(createpath string, sourceFs afero.Fs, format string) 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)
|
||||
}
|
||||
|
||||
@@ -71,12 +121,24 @@ func CreateSite(createpath string, sourceFs afero.Fs, force bool, format string)
|
||||
}
|
||||
}
|
||||
|
||||
err := newSiteCreateConfig(sourceFs, createpath, format)
|
||||
siteConfig := map[string]any{
|
||||
"baseURL": "https://example.org/",
|
||||
"title": "My New Hugo Site",
|
||||
"languageCode": "en-us",
|
||||
}
|
||||
|
||||
err := createSiteConfig(sourceFs, createpath, siteConfig, format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = newSiteCreateArchetype(sourceFs, createpath, format)
|
||||
defaultArchetype := map[string]any{
|
||||
"title": "{{ replace .File.ContentBaseName \"-\" \" \" | title }}",
|
||||
"date": "{{ .Date }}",
|
||||
"draft": true,
|
||||
}
|
||||
|
||||
err = createDefaultArchetype(sourceFs, createpath, defaultArchetype, format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -99,13 +161,7 @@ func copyFiles(createpath string, sourceFs afero.Fs, skeleton embed.FS) 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",
|
||||
}
|
||||
|
||||
func createSiteConfig(fs afero.Fs, createpath string, in map[string]any, format string) (err error) {
|
||||
var buf bytes.Buffer
|
||||
err = parser.InterfaceToConfig(in, metadecoders.FormatFromString(format), &buf)
|
||||
if err != nil {
|
||||
@@ -115,13 +171,7 @@ func newSiteCreateConfig(fs afero.Fs, createpath string, format string) (err err
|
||||
return helpers.WriteToDisk(filepath.Join(createpath, "hugo."+format), &buf, fs)
|
||||
}
|
||||
|
||||
func newSiteCreateArchetype(fs afero.Fs, createpath string, format string) (err error) {
|
||||
in := map[string]any{
|
||||
"title": "{{ replace .File.ContentBaseName \"-\" \" \" | title }}",
|
||||
"date": "{{ .Date }}",
|
||||
"draft": true,
|
||||
}
|
||||
|
||||
func createDefaultArchetype(fs afero.Fs, createpath string, in map[string]any, format string) (err error) {
|
||||
var buf bytes.Buffer
|
||||
err = parser.InterfaceToFrontMatter(in, metadecoders.FormatFromString(format), &buf)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
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.
|
||||
@@ -1,7 +0,0 @@
|
||||
# Theme Name
|
||||
|
||||
## Features
|
||||
|
||||
## Installation
|
||||
|
||||
## Configuration
|
||||
@@ -1,5 +0,0 @@
|
||||
+++
|
||||
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
|
||||
date = {{ .Date }}
|
||||
draft = true
|
||||
+++
|
||||
@@ -1,23 +0,0 @@
|
||||
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
-1
@@ -1,5 +1,5 @@
|
||||
{{- with resources.Get "css/main.css" }}
|
||||
{{- if eq hugo.Environment "development" }}
|
||||
{{- if hugo.IsDevelopment }}
|
||||
<link rel="stylesheet" href="{{ .RelPermalink }}">
|
||||
{{- else }}
|
||||
{{- with . | minify | fingerprint }}
|
||||
@@ -0,0 +1,16 @@
|
||||
{{- 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
@@ -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,12 +0,0 @@
|
||||
{{- 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 }}
|
||||
@@ -0,0 +1,7 @@
|
||||
{{ define "main" }}
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
{{ range .Pages }}
|
||||
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,7 @@
|
||||
{{ define "main" }}
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ .Content }}
|
||||
{{ range .Pages }}
|
||||
<h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@@ -1,31 +0,0 @@
|
||||
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'
|
||||
+49
-34
@@ -36,6 +36,7 @@ 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"
|
||||
@@ -487,7 +488,12 @@ 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 := map[string]*localFile{}
|
||||
retval := make(map[string]*localFile)
|
||||
var mu sync.Mutex
|
||||
|
||||
workers := para.New(d.cfg.Workers)
|
||||
g, _ := workers.Start(context.Background())
|
||||
|
||||
err := afero.Walk(fs, "", func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -508,45 +514,54 @@ func (d *Deployer) walkLocal(fs afero.Fs, matchers []*deployconfig.Matcher, incl
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
// 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
|
||||
|
||||
// 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()
|
||||
return nil
|
||||
})
|
||||
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{MaxDeletes: -1},
|
||||
cfg: deployconfig.DeployConfig{Workers: 2, 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{MaxDeletes: -1},
|
||||
cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1},
|
||||
}
|
||||
|
||||
// Sync remote with local.
|
||||
@@ -836,7 +836,7 @@ func TestIncludeExclude(t *testing.T) {
|
||||
}
|
||||
deployer := &Deployer{
|
||||
localFs: fsTest.fs,
|
||||
cfg: deployconfig.DeployConfig{MaxDeletes: -1}, bucket: fsTest.bucket,
|
||||
cfg: deployconfig.DeployConfig{Workers: 2, 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{MaxDeletes: -1}, bucket: fsTest.bucket,
|
||||
cfg: deployconfig.DeployConfig{Workers: 2, 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{MaxDeletes: -1, Matchers: []*deployconfig.Matcher{{Pattern: ".*", Gzip: true, Re: regexp.MustCompile(".*")}}},
|
||||
cfg: deployconfig.DeployConfig{Workers: 2, 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{MaxDeletes: -1, Matchers: []*deployconfig.Matcher{{Pattern: "^subdir/aaa$", Force: true, Re: regexp.MustCompile("^subdir/aaa$")}}},
|
||||
cfg: deployconfig.DeployConfig{Workers: 2, MaxDeletes: -1, Matchers: []*deployconfig.Matcher{{Pattern: "^subdir/aaa$", Force: true, Re: regexp.MustCompile("^subdir/aaa$")}}},
|
||||
mediaTypes: media.DefaultTypes,
|
||||
}
|
||||
|
||||
@@ -1097,5 +1097,6 @@ func verifyRemote(ctx context.Context, bucket *blob.Bucket, local []*fileData) (
|
||||
func newDeployer() *Deployer {
|
||||
return &Deployer{
|
||||
logger: loggers.NewDefault(),
|
||||
cfg: deployconfig.DeployConfig{Workers: 2},
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+19
-28
@@ -29,6 +29,7 @@ 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"
|
||||
@@ -46,12 +47,6 @@ 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:"-"`
|
||||
|
||||
@@ -79,7 +74,8 @@ type Deps struct {
|
||||
// The site building.
|
||||
Site page.Site
|
||||
|
||||
TemplateProvider ResourceProvider
|
||||
TemplateStore *tplimpl.TemplateStore
|
||||
|
||||
// Used in tests
|
||||
OverloadedTemplateFuncs map[string]any
|
||||
|
||||
@@ -102,6 +98,9 @@ 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
|
||||
@@ -109,9 +108,6 @@ type Deps struct {
|
||||
// The JS batcher client.
|
||||
JSBatcherClient js.BatcherClient
|
||||
|
||||
// The JS batcher client.
|
||||
// JSBatcherClient *esbuild.BatcherClient
|
||||
|
||||
isClosed bool
|
||||
|
||||
*globalErrHandler
|
||||
@@ -130,8 +126,8 @@ func (d Deps) Clone(s page.Site, conf config.AllProvider) (*Deps, error) {
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
func (d *Deps) SetTempl(t *tpl.TemplateHandlers) {
|
||||
d.tmplHandlers = t
|
||||
func (d *Deps) GetTemplateStore() *tplimpl.TemplateStore {
|
||||
return d.TemplateStore
|
||||
}
|
||||
|
||||
func (d *Deps) Init() error {
|
||||
@@ -153,10 +149,12 @@ 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)
|
||||
@@ -263,22 +261,17 @@ 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
|
||||
}
|
||||
@@ -378,14 +371,6 @@ 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
|
||||
@@ -454,6 +439,12 @@ 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.
|
||||
|
||||
+5
-1
@@ -60,6 +60,7 @@
|
||||
"redirections",
|
||||
"subexpression",
|
||||
"suppressible",
|
||||
"synchronisation",
|
||||
"templating",
|
||||
"transpile",
|
||||
"unmarshal",
|
||||
@@ -69,6 +70,7 @@
|
||||
"# cspell: ignore hugo terminology",
|
||||
"# ----------------------------------------------------------------------",
|
||||
"alignx",
|
||||
"aligny",
|
||||
"attrlink",
|
||||
"canonify",
|
||||
"codeowners",
|
||||
@@ -161,7 +163,7 @@
|
||||
"# ----------------------------------------------------------------------",
|
||||
"achristie",
|
||||
"ccpa",
|
||||
"crpa",
|
||||
"cpra",
|
||||
"ddmaurier",
|
||||
"dring",
|
||||
"fleqn",
|
||||
@@ -171,6 +173,8 @@
|
||||
"jsmith",
|
||||
"leqno",
|
||||
"milli",
|
||||
"monokai",
|
||||
"mysanityprojectid",
|
||||
"rgba",
|
||||
"rsmith",
|
||||
"tdewolff",
|
||||
|
||||
+2
-3
@@ -4,10 +4,9 @@
|
||||
/.vscode
|
||||
/dist
|
||||
/public
|
||||
/resources
|
||||
hugo_stats.json
|
||||
node_modules/
|
||||
nohup.out
|
||||
package-lock.json
|
||||
public/
|
||||
resources/
|
||||
trace.out
|
||||
trace.out
|
||||
|
||||
@@ -22,5 +22,6 @@ MD041: false
|
||||
MD046: false
|
||||
MD049: false
|
||||
MD050: false
|
||||
MD051: false
|
||||
MD053: false
|
||||
MD055: false
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# 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
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"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
|
||||
|
||||
```bash
|
||||
```sh
|
||||
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.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
title: {{ replace .File.ContentBaseName "-" " " | strings.FirstUpper }}
|
||||
description:
|
||||
categories: []
|
||||
keywords: []
|
||||
---
|
||||
@@ -3,9 +3,9 @@ title: {{ replace .File.ContentBaseName "-" " " | title }}
|
||||
description:
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related: []
|
||||
returnType:
|
||||
signatures: []
|
||||
params:
|
||||
functions_and_methods:
|
||||
aliases: []
|
||||
returnType:
|
||||
signatures: []
|
||||
---
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
title: {{ replace .File.ContentBaseName "-" " " }}
|
||||
reference:
|
||||
params:
|
||||
reference:
|
||||
---
|
||||
|
||||
<!--
|
||||
|
||||
@@ -3,8 +3,8 @@ title: {{ replace .File.ContentBaseName "-" " " | title }}
|
||||
description:
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
related: []
|
||||
returnType:
|
||||
signatures: []
|
||||
params:
|
||||
functions_and_methods:
|
||||
returnType:
|
||||
signatures: []
|
||||
---
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
title: {{ replace .File.ContentBaseName "-" " " | strings.FirstUpper }}
|
||||
description:
|
||||
categories: []
|
||||
keywords: []
|
||||
publishDate: {{ .Date }}
|
||||
---
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: {{ replace .File.ContentBaseName "-" " " | title }}
|
||||
description:
|
||||
date: {{ .Date }}
|
||||
---
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
Add some **general info** about {{ replace .Name "-" " " | title }} here.
|
||||
|
||||
The site is built by:
|
||||
|
||||
* [Person 1](https://example.org)
|
||||
* [Person 1](https://example.org)
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
|
||||
title: {{ replace .File.ContentBaseName "-" " " | title }}
|
||||
date: {{ now.Format "2006-01-02" }}
|
||||
|
||||
description: A short description of this page.
|
||||
|
||||
# The URL to the site on the internet.
|
||||
siteURL: https://gohugo.io/
|
||||
|
||||
# Link to the site's Hugo source code if public and you can/want to share.
|
||||
# Remove or leave blank if not needed/wanted.
|
||||
siteSource: https://github.com/gohugoio/hugoDocs
|
||||
|
||||
# Add credit to the article author. Leave blank or remove if not needed/wanted.
|
||||
byline: "[bep](https://github.com/bep), Hugo Lead"
|
||||
|
||||
---
|
||||
|
||||
To complete this showcase:
|
||||
|
||||
1. Write the story about your site in this file.
|
||||
2. Add a summary to the `bio.md` file in this directory.
|
||||
3. Replace the `featured-template.png` with a screenshot of your site. You can rename it, but it must contain the word `featured`.
|
||||
4. Create a new pull request in https://github.com/gohugoio/hugoDocs/pulls
|
||||
|
||||
The content of this bundle explained:
|
||||
|
||||
index.md
|
||||
: The main content file. Fill in required front matter metadata and write your story. I does not have to be a novel. It can even be self-promotional, but it should include Hugo in some form.
|
||||
|
||||
bio.md
|
||||
: A short summary of the website. Site credits (who built it) fits nicely here.
|
||||
|
||||
featured.png
|
||||
: A reasonably sized screenshot of your website. It can be named anything, but the name must start with "featured". The sample image is `1500x750` (2:1 aspect ratio).
|
||||
@@ -1,4 +1,4 @@
|
||||
/* The ordeer of these does not matter. */
|
||||
/* The order of these does not matter. */
|
||||
@import "./content.css";
|
||||
@import "./fonts.css";
|
||||
@import "./helpers.css";
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
/* CodeLine */ .dark .chroma .cl { }
|
||||
/* LineTableTD */ .dark .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
|
||||
/* LineTable */ .dark .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
|
||||
/* LineHighlight */ .dark .chroma .hl { background-color: #ffffcc }
|
||||
/* LineHighlight */ .dark .chroma .hl { background-color: rgb(0,19,28) }
|
||||
/* LineNumbersTable */ .dark .chroma .lnt { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
|
||||
/* LineNumbers */ .dark .chroma .ln { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
|
||||
/* Line */ .dark .chroma .line { display: flex; }
|
||||
|
||||
@@ -12,31 +12,32 @@
|
||||
}
|
||||
|
||||
.highlight code {
|
||||
@apply text-sm;
|
||||
@apply text-sm/6;
|
||||
}
|
||||
|
||||
.content {
|
||||
@apply prose prose-sm sm:prose-base prose-stone max-w-none dark:prose-invert dark:text-slate-200;
|
||||
/* headings */
|
||||
@apply prose-h4:font-bold prose-h5:font-bold prose-h6:font-bold;
|
||||
@apply prose-headings:font-semibold;
|
||||
/* lead */
|
||||
@apply prose-lead:text-slate-500 prose-lead:text-xl prose-lead:mt-2 sm:prose-lead:mt-4 prose-lead:leading-relaxed dark:prose-lead:text-slate-400;
|
||||
/* links */
|
||||
@apply prose-a:text-primary prose-a:hover:text-primary/70 prose-a:underline;
|
||||
@apply prose-a:prose-code:underline prose-a:prose-code:hover:text-primary/70 prose-a:prose-code:hover:underline;
|
||||
@apply prose-a:text-primary dark:prose-a:text-blue-500 prose-a:hover:text-blue-500 dark:prose-a:hover:text-blue-400 prose-a:underline;
|
||||
@apply prose-a:prose-code:underline prose-a:prose-code:hover:text-blue-500 prose-a:prose-code:hover:underline;
|
||||
/* pre */
|
||||
@apply prose-pre:text-gray-800 prose-pre:border-1 prose-pre:border-gray-100 prose-pre:bg-light dark:prose-pre:bg-dark dark:prose-pre:ring-1 dark:prose-pre:ring-slate-300/10;
|
||||
/* code */
|
||||
@apply prose-code:px-0.5 prose-code:text-gray-500 prose-code:dark:text-gray-300 border-none;
|
||||
@apply prose-code:before:hidden prose-code:after:hidden prose-code:font-mono;
|
||||
@apply prose-table:prose-th:prose-code:text-white;
|
||||
/* tables */
|
||||
@apply prose-table:border-2 prose-table:border-gray-100 prose-table:dark:border-gray-800 prose-table:relative prose-table:overflow-scroll prose-table:prose-th:font-bold prose-table:prose-th:bg-blue-500 dark:prose-table:prose-th:bg-blue-500/50 prose-table:prose-th:p-2 prose-table:prose-td:p-2 prose-table:prose-th:text-white;
|
||||
@apply prose-table:w-auto prose-table:border-2 prose-table:border-gray-100 prose-table:dark:border-gray-800 prose-table:prose-th:font-bold prose-table:prose-th:bg-blue-500 dark:prose-table:prose-th:bg-blue-500/50 prose-table:prose-th:p-2 prose-table:prose-td:p-2 prose-table:prose-th:text-white;
|
||||
/* hr */
|
||||
@apply dark:prose-hr:border-slate-800;
|
||||
|
||||
h6 + * {
|
||||
@apply mt-2;
|
||||
}
|
||||
/* ol */
|
||||
@apply prose-ol:marker:prose dark:prose-ol:marker:text-gray-300;
|
||||
/* ul */
|
||||
@apply prose-ul:marker:text-gray-500 dark:prose-ul:marker:text-gray-300;
|
||||
}
|
||||
|
||||
/* This will not match highlighting inside e.g. the code-toggle shortcode. */
|
||||
|
||||
@@ -16,7 +16,5 @@
|
||||
|
||||
/* Turbo styles */
|
||||
.turbo-progress-bar {
|
||||
@apply bg-blue-500;
|
||||
opacity: 0.35;
|
||||
height: 3px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@@ -76,8 +76,9 @@
|
||||
--color-green-950: #051410;
|
||||
|
||||
/* Fonts. */
|
||||
--font-sans: "Mulish", ui-sans-serif, system-ui, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--font-sans:
|
||||
"Mulish", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
}
|
||||
|
||||
html {
|
||||
@@ -121,3 +122,10 @@ body {
|
||||
.algolia-docsearch-suggestion--highlight {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Footnotes */
|
||||
.footnote-backref,
|
||||
.footnote-ref {
|
||||
text-decoration: none;
|
||||
padding-left: .0625em;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 299 118" height="100%" width="100%" style="enable-background:new 0 0 299 118;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{clip-path:url(#SVGID_2_);}
|
||||
.st1{fill:#414141;}
|
||||
.st2{fill:#3D4DF4;}
|
||||
</style>
|
||||
<g>
|
||||
<defs>
|
||||
<rect id="SVGID_1_" x="0.4" y="0" width="66.5" height="67.8"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_2_">
|
||||
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
|
||||
</clipPath>
|
||||
<g class="st0">
|
||||
<path class="st1" d="M34.2,28l31.4,18.3c0.9,0.5,1.1,1.6,0.6,2.4c-0.2,0.3-0.4,0.5-0.6,0.6L35.6,66.9c-1.4,0.8-3.1,0.8-4.5,0
|
||||
L1.3,49.6c-0.9-0.5-1.1-1.6-0.6-2.4c0.2-0.3,0.4-0.5,0.6-0.6l23-13.4l7.9,13.6c0.5,0.8,1.5,1.1,2.4,0.7l0.1,0l0.1,0
|
||||
c0.6-0.4,0.9-1.2,0.7-2l-5-15.7l2.5-1.5C33.3,27.8,33.8,27.8,34.2,28z M36,58.6c-1.4-0.8-3.9-0.7-5.5,0.3l-3,1.7l7,4.1l2-1.2
|
||||
l-2-1.1l1-0.6C37.1,60.9,37.3,59.4,36,58.6z M32.1,59.9c0.6-0.3,1.4-0.4,1.8-0.1c0.5,0.3,0.4,0.7-0.2,1.1l-1,0.6l-1.7-1L32.1,59.9
|
||||
z M36.2,55.6l-2,1.2l7,4.1l2-1.2L36.2,55.6z M42.6,51.9l-2,1.2l2.9,1.7l-4.5-0.8L37,55.1l7,4.1l2-1.2L43,56.3l4.5,0.8l2.1-1.2
|
||||
L42.6,51.9z M56.3,43.8l-6,3.5l2.5,1.4l-3.7-0.7L47,49.3l2.9,1.7l-4.5-0.8l-2.1,1.2l7,4.1l2-1.2l-2.9-1.7l4.5,0.8l2.1-1.2
|
||||
l-2.9-1.7l4,0.7l0.2,0.1l2-1.2l4-2.3l-1.7-1l-4,2.3l-1-0.6l4-2.3l-1.7-1l-4,2.3L54,47.1l4-2.3L56.3,43.8z"/>
|
||||
<path class="st2" d="M41.7,0c0.1,0,0.2,0,0.3,0c1,0.2,1.6,1.1,1.5,2.1l-2.3,13.5c3.9,2.5,6.5,6.9,6.5,11.9v0.7l-10.3,0l-2,17.9
|
||||
c-0.1,0.8-0.7,1.4-1.5,1.5l-0.1,0c-1,0.1-1.9-0.6-2-1.5l-2-17.9H19.6v-0.7c0-5,2.6-9.4,6.5-11.9L23.8,2.1c0-0.1,0-0.2,0-0.3
|
||||
c0-1,0.8-1.8,1.8-1.8H41.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="st1" d="M277.4,61c-3.7,0-7.1-0.8-10-2.4c-2.8-1.6-5.1-3.9-6.7-6.8c-1.6-2.9-2.4-6.4-2.4-10.3v-0.9
|
||||
c0-4,0.8-7.4,2.4-10.3c1.6-2.9,3.8-5.2,6.6-6.8c2.8-1.6,6.1-2.4,9.9-2.4c3.7,0,6.9,0.8,9.7,2.5c2.7,1.6,4.9,3.9,6.4,6.8
|
||||
c1.5,2.9,2.3,6.3,2.3,10.1v3.3h-27.4c0.1,2.6,1.1,4.7,2.9,6.3c1.8,1.6,4.1,2.4,6.7,2.4c2.7,0,4.7-0.6,5.9-1.7
|
||||
c1.3-1.2,2.2-2.5,2.9-3.9l7.8,4.1c-0.7,1.3-1.7,2.8-3.1,4.3c-1.3,1.5-3.1,2.8-5.3,4C283.6,60.5,280.8,61,277.4,61z M268.2,36.8h17.6
|
||||
c-0.2-2.2-1.1-3.9-2.7-5.2c-1.5-1.3-3.5-2-6-2c-2.6,0-4.6,0.7-6.2,2C269.5,32.9,268.5,34.6,268.2,36.8z"/>
|
||||
<path class="st1" d="M192.9,60V6.8h18.6l9.2,46.4h1.4l9.2-46.4h18.6V60h-9.7V14.1h-1.4L229.6,60h-16.6L204,14.1h-1.4V60H192.9z"/>
|
||||
<path class="st1" d="M146.3,60V22.3h9.4v4.9h1.4c0.6-1.3,1.7-2.6,3.4-3.7c1.7-1.2,4.2-1.8,7.6-1.8c2.9,0,5.5,0.7,7.7,2.1
|
||||
c2.2,1.3,4,3.2,5.2,5.5c1.2,2.3,1.8,5.1,1.8,8.2V60h-9.6V38.2c0-2.8-0.7-5-2.1-6.4c-1.4-1.4-3.3-2.1-5.9-2.1c-2.9,0-5.2,1-6.8,3
|
||||
c-1.6,1.9-2.4,4.6-2.4,8.1V60H146.3z"/>
|
||||
<path class="st1" d="M126.1,60V22.3h9.6V60H126.1z M130.9,17.9c-1.7,0-3.2-0.6-4.4-1.7c-1.2-1.1-1.7-2.6-1.7-4.4
|
||||
c0-1.8,0.6-3.3,1.7-4.4c1.2-1.1,2.7-1.7,4.4-1.7c1.8,0,3.2,0.6,4.4,1.7c1.2,1.1,1.7,2.6,1.7,4.4c0,1.8-0.6,3.3-1.7,4.4
|
||||
C134.2,17.3,132.7,17.9,130.9,17.9z"/>
|
||||
<path class="st1" d="M79.9,60V6.8h21.9c3.3,0,6.3,0.7,8.8,2.1c2.6,1.3,4.6,3.2,6,5.6c1.5,2.4,2.2,5.3,2.2,8.7v1.1
|
||||
c0,3.3-0.8,6.2-2.3,8.7c-1.5,2.4-3.5,4.3-6.1,5.7c-2.5,1.3-5.4,2-8.7,2H89.9V60H79.9z M89.9,31.4h10.9c2.4,0,4.3-0.7,5.8-2
|
||||
c1.5-1.3,2.2-3.1,2.2-5.4v-0.8c0-2.3-0.7-4.1-2.2-5.4c-1.5-1.3-3.4-2-5.8-2H89.9V31.4z"/>
|
||||
<path class="st2" d="M285.3,112V91h13.5v3.6h-9.5v5h8.7v3.6h-8.7v5.2h9.7v3.6H285.3z"/>
|
||||
<path class="st2" d="M268.7,112V91h13.5v3.6h-9.5v5h8.7v3.6h-8.7v5.2h9.7v3.6H268.7z"/>
|
||||
<path class="st2" d="M249.7,112V91h9.1c1.3,0,2.5,0.2,3.4,0.7c1,0.5,1.7,1.1,2.3,1.9c0.5,0.8,0.8,1.8,0.8,3v0.4
|
||||
c0,1.3-0.3,2.3-0.9,3.1c-0.6,0.8-1.3,1.4-2.2,1.7v0.5c0.8,0,1.4,0.3,1.9,0.8c0.4,0.5,0.7,1.2,0.7,2v6.9h-4v-6.3
|
||||
c0-0.5-0.1-0.9-0.4-1.2c-0.2-0.3-0.6-0.4-1.2-0.4h-5.5v7.9H249.7z M253.7,100.4h4.7c0.9,0,1.7-0.2,2.2-0.8c0.5-0.5,0.8-1.2,0.8-2
|
||||
v-0.3c0-0.8-0.3-1.5-0.8-2c-0.5-0.5-1.3-0.8-2.2-0.8h-4.7V100.4z"/>
|
||||
<path class="st2" d="M233.7,112V91h13.2v3.6h-9.2v5.1h8.5v3.6h-8.5v8.7H233.7z"/>
|
||||
<path class="st1" d="M214.3,112V97.1h3.7v1.7h0.5c0.2-0.6,0.6-1,1.1-1.3c0.5-0.3,1.1-0.4,1.8-0.4h1.8v3.4h-1.9c-1,0-1.8,0.3-2.4,0.8
|
||||
c-0.6,0.5-0.9,1.3-0.9,2.3v8.5H214.3z"/>
|
||||
<path class="st1" d="M203,112.4c-1.5,0-2.8-0.3-4-0.9c-1.2-0.6-2.1-1.5-2.8-2.6c-0.7-1.1-1-2.5-1-4.1v-0.5c0-1.6,0.3-3,1-4.1
|
||||
c0.7-1.1,1.6-2,2.8-2.6c1.2-0.6,2.5-0.9,4-0.9c1.5,0,2.8,0.3,4,0.9c1.2,0.6,2.1,1.5,2.8,2.6c0.7,1.1,1,2.5,1,4.1v0.5
|
||||
c0,1.6-0.3,3-1,4.1c-0.7,1.1-1.6,2-2.8,2.6C205.8,112.1,204.5,112.4,203,112.4z M203,109c1.2,0,2.1-0.4,2.9-1.1
|
||||
c0.8-0.8,1.1-1.8,1.1-3.2v-0.3c0-1.4-0.4-2.5-1.1-3.2c-0.7-0.8-1.7-1.1-2.9-1.1c-1.2,0-2.1,0.4-2.9,1.1c-0.8,0.7-1.1,1.8-1.1,3.2
|
||||
v0.3c0,1.4,0.4,2.5,1.1,3.2C200.9,108.7,201.8,109,203,109z"/>
|
||||
<path class="st1" d="M185.8,112v-11.8H182v-3.1h3.8v-2.8c0-1,0.3-1.8,0.9-2.4c0.6-0.6,1.4-0.9,2.4-0.9h3.9v3.1h-2.6
|
||||
c-0.6,0-0.8,0.3-0.8,0.9v2.1h3.9v3.1h-3.9V112H185.8z"/>
|
||||
<path class="st1" d="M155.9,104.6v-0.5c0-1.6,0.3-2.9,0.9-4c0.6-1.1,1.4-2,2.5-2.5c1-0.6,2.2-0.9,3.4-0.9c1.4,0,2.4,0.2,3.1,0.7
|
||||
c0.7,0.5,1.2,1,1.5,1.5h0.5v-1.8h3.7v17.5c0,1-0.3,1.8-0.9,2.4c-0.6,0.6-1.4,0.9-2.4,0.9h-10v-3.3h8.6c0.6,0,0.8-0.3,0.8-0.9v-3.9
|
||||
h-0.5c-0.2,0.3-0.5,0.7-0.8,1c-0.4,0.3-0.8,0.6-1.4,0.8c-0.6,0.2-1.4,0.3-2.3,0.3c-1.2,0-2.3-0.3-3.4-0.9c-1-0.6-1.8-1.4-2.5-2.6
|
||||
C156.2,107.5,155.9,106.1,155.9,104.6z M163.7,108.7c1.2,0,2.1-0.4,2.9-1.1s1.2-1.8,1.2-3.1v-0.3c0-1.4-0.4-2.4-1.2-3.1
|
||||
c-0.8-0.7-1.7-1.1-2.9-1.1c-1.2,0-2.1,0.4-2.9,1.1c-0.8,0.7-1.2,1.8-1.2,3.1v0.3c0,1.3,0.4,2.4,1.2,3.1S162.6,108.7,163.7,108.7z"/>
|
||||
<path class="st1" d="M145.3,112.4c-1.5,0-2.8-0.3-4-0.9c-1.2-0.6-2.1-1.5-2.8-2.6s-1-2.5-1-4.1v-0.5c0-1.6,0.3-3,1-4.1
|
||||
c0.7-1.1,1.6-2,2.8-2.6c1.2-0.6,2.5-0.9,4-0.9s2.8,0.3,4,0.9c1.2,0.6,2.1,1.5,2.8,2.6c0.7,1.1,1,2.5,1,4.1v0.5c0,1.6-0.3,3-1,4.1
|
||||
c-0.7,1.1-1.6,2-2.8,2.6C148.1,112.1,146.8,112.4,145.3,112.4z M145.3,109c1.2,0,2.1-0.4,2.9-1.1c0.8-0.8,1.1-1.8,1.1-3.2v-0.3
|
||||
c0-1.4-0.4-2.5-1.1-3.2c-0.7-0.8-1.7-1.1-2.9-1.1c-1.2,0-2.1,0.4-2.9,1.1c-0.8,0.7-1.1,1.8-1.1,3.2v0.3c0,1.4,0.4,2.5,1.1,3.2
|
||||
C143.2,108.7,144.1,109,145.3,109z"/>
|
||||
<path class="st1" d="M130.3,112V91h3.8v21H130.3z"/>
|
||||
<path class="st1" d="M109.6,112v-3.5h2.8v-14h-2.8V91h10.8c1.3,0,2.4,0.2,3.3,0.7c1,0.4,1.7,1,2.2,1.8c0.5,0.8,0.8,1.7,0.8,2.8v0.3
|
||||
c0,1-0.2,1.8-0.5,2.4c-0.4,0.6-0.8,1.1-1.3,1.4c-0.5,0.3-0.9,0.6-1.4,0.7v0.5c0.4,0.1,0.9,0.3,1.4,0.7c0.5,0.3,1,0.8,1.3,1.4
|
||||
c0.4,0.6,0.6,1.4,0.6,2.4v0.3c0,1.2-0.3,2.2-0.8,3c-0.5,0.8-1.3,1.4-2.2,1.9c-0.9,0.4-2,0.7-3.3,0.7H109.6z M116.3,108.4h3.7
|
||||
c0.9,0,1.6-0.2,2.1-0.6c0.5-0.4,0.8-1,0.8-1.8v-0.3c0-0.8-0.3-1.4-0.8-1.8c-0.5-0.4-1.2-0.6-2.1-0.6h-3.7V108.4z M116.3,99.6h3.7
|
||||
c0.8,0,1.5-0.2,2-0.6c0.5-0.4,0.8-1,0.8-1.7v-0.3c0-0.8-0.3-1.3-0.8-1.7c-0.5-0.4-1.2-0.6-2-0.6h-3.7V99.6z"/>
|
||||
<path class="st1" d="M85.9,118v-3.3H94c0.6,0,0.8-0.3,0.8-0.9V110h-0.5c-0.2,0.3-0.4,0.7-0.8,1c-0.3,0.3-0.8,0.6-1.4,0.8
|
||||
c-0.6,0.2-1.3,0.3-2.2,0.3c-1.2,0-2.2-0.3-3.1-0.8c-0.9-0.5-1.5-1.3-2-2.2c-0.5-0.9-0.7-2-0.7-3.2v-8.9h3.8v8.6c0,1.1,0.3,2,0.8,2.5
|
||||
c0.6,0.6,1.4,0.8,2.4,0.8c1.2,0,2.1-0.4,2.7-1.1c0.6-0.8,1-1.9,1-3.2v-7.6h3.8v17.5c0,1-0.3,1.8-0.9,2.4c-0.6,0.6-1.4,0.9-2.4,0.9
|
||||
H85.9z"/>
|
||||
<path class="st1" d="M72.9,112.4c-1.5,0-2.8-0.3-4-0.9s-2.1-1.5-2.8-2.6s-1-2.5-1-4.1v-0.5c0-1.6,0.3-3,1-4.1c0.7-1.1,1.6-2,2.8-2.6
|
||||
s2.5-0.9,4-0.9c1.5,0,2.8,0.3,4,0.9s2.1,1.5,2.8,2.6c0.7,1.1,1,2.5,1,4.1v0.5c0,1.6-0.3,3-1,4.1s-1.6,2-2.8,2.6
|
||||
S74.4,112.4,72.9,112.4z M72.9,109c1.2,0,2.1-0.4,2.9-1.1c0.8-0.8,1.1-1.8,1.1-3.2v-0.3c0-1.4-0.4-2.5-1.1-3.2
|
||||
c-0.7-0.8-1.7-1.1-2.9-1.1c-1.2,0-2.1,0.4-2.9,1.1c-0.8,0.7-1.1,1.8-1.1,3.2v0.3c0,1.4,0.4,2.5,1.1,3.2
|
||||
C70.8,108.7,71.8,109,72.9,109z"/>
|
||||
<path class="st1" d="M57.9,112V91h3.8v21H57.9z"/>
|
||||
<path class="st1" d="M38.8,118V97.1h3.7v1.8H43c0.3-0.6,0.9-1.1,1.6-1.5c0.7-0.5,1.8-0.7,3.1-0.7c1.2,0,2.3,0.3,3.3,0.9
|
||||
c1,0.6,1.8,1.4,2.5,2.6c0.6,1.1,0.9,2.5,0.9,4.1v0.5c0,1.6-0.3,3-0.9,4.1c-0.6,1.1-1.4,2-2.5,2.6c-1,0.6-2.1,0.9-3.3,0.9
|
||||
c-0.9,0-1.7-0.1-2.3-0.3c-0.6-0.2-1.1-0.5-1.5-0.8c-0.4-0.3-0.7-0.7-0.9-1h-0.5v7.7H38.8z M46.6,109.1c1.2,0,2.1-0.4,2.9-1.1
|
||||
c0.8-0.8,1.2-1.9,1.2-3.3v-0.3c0-1.4-0.4-2.5-1.2-3.3c-0.8-0.8-1.8-1.1-2.9-1.1s-2.1,0.4-2.9,1.1c-0.8,0.7-1.2,1.8-1.2,3.3v0.3
|
||||
c0,1.4,0.4,2.5,1.2,3.3C44.4,108.7,45.4,109.1,46.6,109.1z"/>
|
||||
<path class="st1" d="M28.2,112.4c-1.5,0-2.8-0.3-3.9-0.9c-1.1-0.6-2-1.5-2.6-2.7c-0.6-1.2-0.9-2.5-0.9-4.1v-0.4
|
||||
c0-1.6,0.3-2.9,0.9-4.1c0.6-1.2,1.5-2,2.6-2.7c1.1-0.6,2.4-1,3.9-1c1.5,0,2.7,0.3,3.8,1c1.1,0.6,1.9,1.5,2.5,2.7
|
||||
c0.6,1.1,0.9,2.5,0.9,4v1.3H24.6c0,1,0.4,1.8,1.1,2.5c0.7,0.6,1.6,1,2.6,1c1.1,0,1.8-0.2,2.3-0.7s0.9-1,1.1-1.5l3.1,1.6
|
||||
c-0.3,0.5-0.7,1.1-1.2,1.7c-0.5,0.6-1.2,1.1-2.1,1.6C30.7,112.2,29.6,112.4,28.2,112.4z M24.6,102.8h7c-0.1-0.9-0.4-1.6-1-2.1
|
||||
c-0.6-0.5-1.4-0.8-2.4-0.8c-1,0-1.8,0.3-2.4,0.8C25.1,101.3,24.7,102,24.6,102.8z"/>
|
||||
<path class="st1" d="M0.8,112v-3.5h2.8v-14H0.8V91h8.6c2.8,0,5,0.7,6.4,2.2c1.5,1.4,2.2,3.5,2.2,6.4v4c0,2.8-0.7,4.9-2.2,6.4
|
||||
c-1.5,1.4-3.6,2.1-6.4,2.1H0.8z M7.5,108.4h2c1.6,0,2.8-0.4,3.5-1.3c0.7-0.8,1.1-2,1.1-3.5v-4.2c0-1.5-0.4-2.7-1.1-3.5
|
||||
c-0.7-0.8-1.9-1.3-3.5-1.3h-2V108.4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.8 KiB |
@@ -1,6 +1,6 @@
|
||||
var debug = 0 ? console.log.bind(console, '[explorer]') : function () {};
|
||||
|
||||
// This is cureently not used, but kept in case I change my mind.
|
||||
// This is currently not used, but kept in case I change my mind.
|
||||
export const explorer = (Alpine) => ({
|
||||
uiState: {
|
||||
containerScrollTop: -1,
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
export const navbar = (Alpine) => ({
|
||||
init: function () {
|
||||
Alpine.bind(this.$root, this.root);
|
||||
|
||||
return this.$nextTick(() => {
|
||||
let contentEl = document.querySelector('.content:not(.content--ready)');
|
||||
if (contentEl) {
|
||||
contentEl.classList.add('content--ready');
|
||||
let anchorTemplate = document.getElementById('anchor-heading');
|
||||
if (anchorTemplate) {
|
||||
let els = contentEl.querySelectorAll('h2[id], h3[id], h4[id], h5[id], h6[id], dt[id]');
|
||||
for (let i = 0; i < els.length; i++) {
|
||||
let el = els[i];
|
||||
el.classList.add('group');
|
||||
let a = anchorTemplate.content.cloneNode(true).firstElementChild;
|
||||
a.href = '#' + el.id;
|
||||
el.appendChild(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
root: {
|
||||
['@scroll.window.debounce.10ms'](event) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { LRUCache } from '../../helpers';
|
||||
|
||||
const designMode = false;
|
||||
|
||||
const groupByLvl0 = (array) => {
|
||||
@@ -33,10 +35,10 @@ const applyHelperFuncs = (array) => {
|
||||
};
|
||||
|
||||
export const search = (Alpine, cfg) => ({
|
||||
query: designMode ? 'shortcodes' : '',
|
||||
query: designMode ? 'apac' : '',
|
||||
open: designMode,
|
||||
result: {},
|
||||
|
||||
cache: new LRUCache(10), // Small cache, avoids network requests on e.g. backspace.
|
||||
init() {
|
||||
Alpine.bind(this.$root, this.root);
|
||||
|
||||
@@ -66,6 +68,13 @@ export const search = (Alpine, cfg) => ({
|
||||
this.result = {};
|
||||
return;
|
||||
}
|
||||
|
||||
// Check cache first.
|
||||
const cached = this.cache.get(this.query);
|
||||
if (cached) {
|
||||
this.result = cached;
|
||||
return;
|
||||
}
|
||||
var queries = {
|
||||
requests: [
|
||||
{
|
||||
@@ -91,6 +100,7 @@ export const search = (Alpine, cfg) => ({
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
this.result = groupByLvl0(applyHelperFuncs(data.results[0].hits));
|
||||
this.cache.put(this.query, this.result);
|
||||
});
|
||||
},
|
||||
root: {
|
||||
@@ -102,7 +112,7 @@ export const search = (Alpine, cfg) => ({
|
||||
['@search-toggle.window']() {
|
||||
this.toggleOpen();
|
||||
},
|
||||
['@keydown.meta.k.window.prevent']() {
|
||||
['@keydown.slash.window.prevent']() {
|
||||
this.toggleOpen();
|
||||
},
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ export const toc = (Alpine) => ({
|
||||
});
|
||||
|
||||
return this.$nextTick(() => {
|
||||
let contentEl = document.getElementById('content');
|
||||
let contentEl = document.getElementById('article');
|
||||
if (contentEl) {
|
||||
const handleIntersect = (entries) => {
|
||||
if (this.justClicked) {
|
||||
|
||||
@@ -87,8 +87,8 @@ export function initColorScheme() {
|
||||
|
||||
const toggleDarkMode = function (dark) {
|
||||
if (dark) {
|
||||
document.body.classList.add('dark');
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.body.classList.remove('dark');
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './bridgeTurboAndAlpine';
|
||||
export * from './helpers';
|
||||
export * from './lrucache';
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// A simple LRU cache implementation backed by a map.
|
||||
export class LRUCache {
|
||||
constructor(maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
this.cache = new Map();
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this.cache.get(key);
|
||||
}
|
||||
|
||||
put(key, value) {
|
||||
if (this.cache.size >= this.maxSize) {
|
||||
const firstKey = this.cache.keys().next().value;
|
||||
this.cache.delete(firstKey);
|
||||
}
|
||||
this.cache.set(key, value);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
cascade:
|
||||
_build:
|
||||
build:
|
||||
list: never
|
||||
publishResources: false
|
||||
render: never
|
||||
@@ -9,5 +9,5 @@ cascade:
|
||||
<!--
|
||||
Files within this headless branch bundle are Markdown snippets. Each file must contain front matter delimiters, though front matter fields are not required.
|
||||
|
||||
Include the rendered content using the "include" shortcode.
|
||||
Include the rendered content using the "include" shortcode.
|
||||
-->
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
_comment: Do not remove front matter.
|
||||
---
|
||||
|
||||
Content format|Media type|Identifier|File extensions
|
||||
:--|:--|:--|:--
|
||||
Markdown|`text/markdown`|`markdown`|`markdown`,`md`, `mdown`
|
||||
HTML|`text/html`|`html`|`htm`, `html`
|
||||
Emacs Org Mode|`text/org`|`org`|`org`
|
||||
AsciiDoc|`text/asciidoc`|`asciidoc`|`ad`, `adoc`, `asciidoc`
|
||||
Pandoc|`text/pandoc`|`pandoc`|`pandoc`, `pdc`
|
||||
reStructuredText|`text/rst`|`rst`|`rst`
|
||||
<!-- do not remove this comment -->
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
_comment: Do not remove front matter.
|
||||
---
|
||||
|
||||
> [!note]
|
||||
> The [page collections quick reference guide] describes methods and functions to filter, sort, and group page collections.
|
||||
|
||||
[page collections quick reference guide]: /quick-reference/page-collections/
|
||||
@@ -0,0 +1,108 @@
|
||||
---
|
||||
_comment: Do not remove front matter.
|
||||
---
|
||||
|
||||
params
|
||||
: (`map` or `slice`) Params that can be imported as JSON in your JS files, e.g.
|
||||
|
||||
```go-html-template
|
||||
{{ $js := resources.Get "js/main.js" | js.Build (dict "params" (dict "api" "https://example.org/api")) }}
|
||||
```
|
||||
And then in your JS file:
|
||||
|
||||
```js
|
||||
import * as params from '@params';
|
||||
```
|
||||
|
||||
Note that this is meant for small data sets, e.g., configuration settings. For larger data sets, please put/mount the files into `assets` and import them directly.
|
||||
|
||||
minify
|
||||
: (`bool`) Whether to let `js.Build` handle the minification.
|
||||
|
||||
loaders
|
||||
: {{< new-in 0.140.0 />}}
|
||||
: (`map`) Configuring a loader for a given file type lets you load that file type with an `import` statement or a `require` call. For example, configuring the `.png` file extension to use the data URL loader means importing a `.png` file gives you a data URL containing the contents of that image. Loaders available are `none`, `base64`, `binary`, `copy`, `css`, `dataurl`, `default`, `empty`, `file`, `global-css`, `js`, `json`, `jsx`, `local-css`, `text`, `ts`, `tsx`. See https://esbuild.github.io/api/#loader.
|
||||
|
||||
inject
|
||||
: (`slice`) This option allows you to automatically replace a global variable with an import from another file. The path names must be relative to `assets`. See https://esbuild.github.io/api/#inject.
|
||||
|
||||
shims
|
||||
: (`map`) This option allows swapping out a component with another. A common use case is to load dependencies like React from a CDN (with _shims_) when in production, but running with the full bundled `node_modules` dependency during development:
|
||||
|
||||
```go-html-template
|
||||
{{ $shims := dict "react" "js/shims/react.js" "react-dom" "js/shims/react-dom.js" }}
|
||||
{{ $js = $js | js.Build dict "shims" $shims }}
|
||||
```
|
||||
|
||||
The _shim_ files may look like these:
|
||||
|
||||
```js
|
||||
// js/shims/react.js
|
||||
module.exports = window.React;
|
||||
```
|
||||
|
||||
```js
|
||||
// js/shims/react-dom.js
|
||||
module.exports = window.ReactDOM;
|
||||
```
|
||||
|
||||
With the above, these imports should work in both scenarios:
|
||||
|
||||
```js
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom/client';
|
||||
```
|
||||
|
||||
target
|
||||
: (`string`) The language target. One of: `es5`, `es2015`, `es2016`, `es2017`, `es2018`, `es2019`, `es2020`, `es2021`, `es2022`, `es2023`, `es2024`, or `esnext`. Default is `esnext`.
|
||||
|
||||
platform
|
||||
: {{< new-in 0.140.0 />}}
|
||||
: (`string`) One of `browser`, `node`, `neutral`. Default is `browser`. See https://esbuild.github.io/api/#platform.
|
||||
|
||||
externals
|
||||
: (`slice`) External dependencies. Use this to trim dependencies you know will never be executed. See https://esbuild.github.io/api/#external.
|
||||
|
||||
defines
|
||||
: (`map`) This option allows you to define a set of string replacements to be performed when building. It must be a map where each key will be replaced by its value.
|
||||
|
||||
```go-html-template
|
||||
{{ $defines := dict "process.env.NODE_ENV" `"development"` }}
|
||||
```
|
||||
|
||||
drop
|
||||
: {{< new-in 0.144.0 />}}
|
||||
: (`string`) Edit your source code before building to drop certain constructs: One of `debugger` or `console`.
|
||||
: See https://esbuild.github.io/api/#drop
|
||||
|
||||
sourceMap
|
||||
: (`string`) Whether to generate `inline`, `linked`, or `external` source maps from esbuild. Linked and external source maps will be written to the target with the output file name + ".map". When `linked` a `sourceMappingURL` will also be written to the output file. By default, source maps are not created. Note that the `linked` option was added in Hugo 0.140.0.
|
||||
|
||||
sourcesContent
|
||||
: {{< new-in 0.140.0 />}}
|
||||
: (`bool`) Whether to include the content of the source files in the source map. By default, this is `true`.
|
||||
|
||||
JSX
|
||||
: {{< new-in 0.124.0 />}}
|
||||
: (`string`) How to handle/transform JSX syntax. One of: `transform`, `preserve`, `automatic`. Default is `transform`. Notably, the `automatic` transform was introduced in React 17+ and will cause the necessary JSX helper functions to be imported automatically. See https://esbuild.github.io/api/#jsx.
|
||||
|
||||
JSXImportSource
|
||||
: {{< new-in 0.124.0 />}}
|
||||
: (`string`) Which library to use to automatically import its JSX helper functions from. This only works if `JSX` is set to `automatic`. The specified library needs to be installed through npm and expose certain exports. See https://esbuild.github.io/api/#jsx-import-source.
|
||||
|
||||
The combination of `JSX` and `JSXImportSource` is helpful if you want to use a non-React JSX library like Preact, e.g.:
|
||||
|
||||
```go-html-template
|
||||
{{ $js := resources.Get "js/main.jsx" | js.Build (dict "JSX" "automatic" "JSXImportSource" "preact") }}
|
||||
```
|
||||
|
||||
With the above, you can use Preact components and JSX without having to manually import `h` and `Fragment` every time:
|
||||
|
||||
```jsx
|
||||
import { render } from 'preact';
|
||||
|
||||
const App = () => <>Hello world!</>;
|
||||
|
||||
const container = document.getElementById('app');
|
||||
if (container) render(<App />, container);
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
_comment: Do not remove front matter.
|
||||
---
|
||||
|
||||
> [!note]
|
||||
> Localization of dates, currencies, numbers, and percentages is performed by the [gohugoio/locales] package. The language tag of the current site must match one of the listed locales.
|
||||
|
||||
[gohugoio/locales]: https://github.com/gohugoio/locales
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
_comment: Do not remove front matter.
|
||||
---
|
||||
|
||||
The [`anchorize`] and [`urlize`] functions are similar:
|
||||
The [`anchorize`] and [`urlize`] functions are similar:
|
||||
|
||||
[`anchorize`]: /functions/urls/anchorize/
|
||||
[`urlize`]: /functions/urls/urlize/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user