Compare commits

..

1 Commits

Author SHA1 Message Date
Bjørn Erik Pedersen 0c8232e3bd github: Add a "free space" step on Ubuntu 2024-04-14 16:14:16 +02:00
76 changed files with 327 additions and 1596 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ parameters:
defaults: &defaults
resource_class: large
docker:
- image: bepsays/ci-hugoreleaser:1.22200.20201
- image: bepsays/ci-hugoreleaser:1.22200.20200
environment: &buildenv
GOMODCACHE: /root/project/gomodcache
version: 2
@@ -60,7 +60,7 @@ jobs:
environment:
<<: [*buildenv]
docker:
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20201
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20200
steps:
- *restore-cache
- &attach-workspace
+1 -1
View File
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2022 The Hugo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+2 -1
View File
@@ -62,7 +62,8 @@ And with [Hugo Modules], you can share content, assets, data, translations, them
<p>&nbsp;</p>
<p float="left">
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<a href="https://cloudcannon.com/hugo-cms/?utm_campaign=HugoSponsorship&utm_source=sponsor&utm_content=gohugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/cloudcannon-blue.svg" width="220" alt="CloudCannon"></a>
<p>&nbsp;</p>
## Installation
+1 -1
View File
@@ -4,4 +4,4 @@
Please report (suspected) security vulnerabilities to **[bjorn.erik.pedersen@gmail.com](mailto:bjorn.erik.pedersen@gmail.com)**. You will receive a response from us within 48 hours. If we can confirm the issue, we will release a patch as soon as possible depending on the complexity of the issue but historically within days.
Also see [Hugo's Security Model](https://gohugo.io/about/security/).
Also see [Hugo's Security Model](https://gohugo.io/about/security-model/).
+10 -44
View File
@@ -67,7 +67,7 @@ func New(opts Options) *Cache {
evictedIdentities := collections.NewStack[identity.Identity]()
onEvict := func(k, v any) {
if !opts.Watching {
if !opts.Running {
return
}
identity.WalkIdentitiesShallow(v, func(level int, id identity.Identity) bool {
@@ -97,7 +97,7 @@ type Options struct {
CheckInterval time.Duration
MaxSize int
MinMaxSize int
Watching bool
Running bool
}
// Options for a partition.
@@ -140,25 +140,16 @@ func (c *Cache) DrainEvictedIdentities() []identity.Identity {
}
// ClearMatching clears all partition for which the predicate returns true.
func (c *Cache) ClearMatching(predicatePartition func(k string, p PartitionManager) bool, predicateValue func(k, v any) bool) {
if predicatePartition == nil {
predicatePartition = func(k string, p PartitionManager) bool { return true }
}
if predicateValue == nil {
panic("nil predicateValue")
}
func (c *Cache) ClearMatching(predicate func(k, v any) bool) {
g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
NumWorkers: len(c.partitions),
Handle: func(ctx context.Context, partition PartitionManager) error {
partition.clearMatching(predicateValue)
partition.clearMatching(predicate)
return nil
},
})
for k, p := range c.partitions {
if !predicatePartition(k, p) {
continue
}
for _, p := range c.partitions {
g.Enqueue(p)
}
@@ -365,7 +356,6 @@ func GetOrCreatePartition[K comparable, V any](c *Cache, name string, opts Optio
trace: c.opts.Log.Logger().WithLevel(logg.LevelTrace).WithField("partition", name),
opts: opts,
}
c.partitions[name] = partition
return partition
@@ -385,37 +375,13 @@ type Partition[K comparable, V any] struct {
// GetOrCreate gets or creates a value for the given key.
func (p *Partition[K, V]) GetOrCreate(key K, create func(key K) (V, error)) (V, error) {
v, err := p.doGetOrCreate(key, create)
if err != nil {
return p.zero, err
}
if resource.StaleVersion(v) > 0 {
p.c.Delete(key)
return p.doGetOrCreate(key, create)
}
return v, err
}
func (p *Partition[K, V]) doGetOrCreate(key K, create func(key K) (V, error)) (V, error) {
v, _, err := p.c.GetOrCreate(key, create)
return v, err
}
func (p *Partition[K, V]) GetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
v, err := p.doGetOrCreateWitTimeout(key, duration, create)
if err != nil {
return p.zero, err
}
if resource.StaleVersion(v) > 0 {
p.c.Delete(key)
return p.doGetOrCreateWitTimeout(key, duration, create)
}
return v, err
}
// GetOrCreateWitTimeout gets or creates a value for the given key and times out if the create function
// takes too long.
func (p *Partition[K, V]) doGetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
func (p *Partition[K, V]) GetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
resultch := make(chan V, 1)
errch := make(chan error, 1)
@@ -472,7 +438,7 @@ func (p *Partition[K, V]) clearOnRebuild(changeset ...identity.Identity) {
shouldDelete := func(key K, v V) bool {
// We always clear elements marked as stale.
if resource.StaleVersion(v) > 0 {
if resource.IsStaleAny(v) {
return true
}
@@ -527,8 +493,8 @@ func (p *Partition[K, V]) Keys() []K {
func (p *Partition[K, V]) clearStale() {
p.c.DeleteFunc(func(key K, v V) bool {
staleVersion := resource.StaleVersion(v)
if staleVersion > 0 {
isStale := resource.IsStaleAny(v)
if isStale {
p.trace.Log(
logg.StringFunc(
func() string {
@@ -538,7 +504,7 @@ func (p *Partition[K, V]) clearStale() {
)
}
return staleVersion > 0
return isStale
})
}
+7 -7
View File
@@ -29,12 +29,12 @@ var (
)
type testItem struct {
name string
staleVersion uint32
name string
isStale bool
}
func (t testItem) StaleVersion() uint32 {
return t.staleVersion
func (t testItem) IsStale() bool {
return t.isStale
}
func (t testItem) IdentifierBase() string {
@@ -109,7 +109,7 @@ func newTestCache(t *testing.T) *Cache {
p2.GetOrCreate("clearBecauseStale", func(string) (testItem, error) {
return testItem{
staleVersion: 32,
isStale: true,
}, nil
})
@@ -121,7 +121,7 @@ func newTestCache(t *testing.T) *Cache {
p2.GetOrCreate("clearNever", func(string) (testItem, error) {
return testItem{
staleVersion: 0,
isStale: false,
}, nil
})
@@ -156,7 +156,7 @@ func TestClear(t *testing.T) {
cache = newTestCache(t)
cache.ClearMatching(nil, func(k, v any) bool {
cache.ClearMatching(func(k, v any) bool {
return k.(string) == "clearOnRebuild"
})
+24 -32
View File
@@ -128,7 +128,6 @@ type rootCommand struct {
verbose bool
debug bool
quiet bool
devMode bool // Hidden flag.
renderToMemory bool
@@ -327,12 +326,12 @@ func (r *rootCommand) Name() string {
}
func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
b := newHugoBuilder(r, nil)
if !r.buildWatch {
defer b.postBuild("Total", time.Now())
defer r.timeTrack(time.Now(), "Total")
}
b := newHugoBuilder(r, nil)
if err := b.loadConfig(cd, false); err != nil {
return err
}
@@ -424,33 +423,29 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
level := logg.LevelWarn
if r.devMode {
level = logg.LevelTrace
if r.logLevel != "" {
switch strings.ToLower(r.logLevel) {
case "debug":
level = logg.LevelDebug
case "info":
level = logg.LevelInfo
case "warn", "warning":
level = logg.LevelWarn
case "error":
level = logg.LevelError
default:
return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel)
}
} else {
if r.logLevel != "" {
switch strings.ToLower(r.logLevel) {
case "debug":
level = logg.LevelDebug
case "info":
level = logg.LevelInfo
case "warn", "warning":
level = logg.LevelWarn
case "error":
level = logg.LevelError
default:
return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel)
}
} else {
if r.verbose {
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
level = logg.LevelInfo
}
if r.verbose {
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
level = logg.LevelInfo
}
if r.debug {
hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0")
level = logg.LevelDebug
}
if r.debug {
hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0")
level = logg.LevelDebug
}
}
@@ -510,13 +505,10 @@ Complete documentation is available at https://gohugo.io/.`
cmd.PersistentFlags().BoolVarP(&r.verbose, "verbose", "v", false, "verbose output")
cmd.PersistentFlags().BoolVarP(&r.debug, "debug", "", false, "debug output")
cmd.PersistentFlags().BoolVarP(&r.devMode, "devMode", "", false, "only used for internal testing, flag hidden.")
cmd.PersistentFlags().StringVar(&r.logLevel, "logLevel", "", "log level (debug|info|warn|error)")
_ = cmd.RegisterFlagCompletionFunc("logLevel", cobra.FixedCompletions([]string{"debug", "info", "warn", "error"}, cobra.ShellCompDirectiveNoFileComp))
cmd.Flags().BoolVarP(&r.buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed")
cmd.PersistentFlags().MarkHidden("devMode")
// Configure local flags
applyLocalFlagsBuild(cmd, r)
+8 -14
View File
@@ -45,10 +45,9 @@ func newGenCommand() *genCommand {
genmandir string
// Chroma flags.
style string
highlightStyle string
lineNumbersInlineStyle string
lineNumbersTableStyle string
style string
highlightStyle string
linesStyle string
)
newChromaStyles := func() simplecobra.Commander {
@@ -64,11 +63,8 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
if highlightStyle != "" {
builder.Add(chroma.LineHighlight, highlightStyle)
}
if lineNumbersInlineStyle != "" {
builder.Add(chroma.LineNumbers, lineNumbersInlineStyle)
}
if lineNumbersTableStyle != "" {
builder.Add(chroma.LineNumbersTable, lineNumbersTableStyle)
if linesStyle != "" {
builder.Add(chroma.LineNumbers, linesStyle)
}
style, err := builder.Build()
if err != nil {
@@ -82,12 +78,10 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
cmd.ValidArgsFunction = cobra.NoFileCompletions
cmd.PersistentFlags().StringVar(&style, "style", "friendly", "highlighter style (see https://xyproto.github.io/splash/docs/)")
_ = cmd.RegisterFlagCompletionFunc("style", cobra.NoFileCompletions)
cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", `foreground and background colors for highlighted lines, e.g. --highlightStyle "#fff000 bg:#000fff"`)
cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", "style used for highlighting lines (see https://github.com/alecthomas/chroma)")
_ = cmd.RegisterFlagCompletionFunc("highlightStyle", cobra.NoFileCompletions)
cmd.PersistentFlags().StringVar(&lineNumbersInlineStyle, "lineNumbersInlineStyle", "", `foreground and background colors for inline line numbers, e.g. --lineNumbersInlineStyle "#fff000 bg:#000fff"`)
_ = 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().StringVar(&linesStyle, "linesStyle", "", "style used for line numbers (see https://github.com/alecthomas/chroma)")
_ = cmd.RegisterFlagCompletionFunc("linesStyle", cobra.NoFileCompletions)
},
}
}
+2 -14
View File
@@ -75,14 +75,9 @@ type hugoBuilder struct {
errState hugoBuilderErrState
}
var errConfigNotSet = errors.New("config not set")
func (c *hugoBuilder) withConfE(fn func(conf *commonConfig) error) error {
c.confmu.Lock()
defer c.confmu.Unlock()
if c.conf == nil {
return errConfigNotSet
}
return fn(c.conf)
}
@@ -590,7 +585,7 @@ func (c *hugoBuilder) fullRebuild(changeType string) {
time.Sleep(2 * time.Second)
}()
defer c.postBuild("Rebuilt", time.Now())
defer c.r.timeTrack(time.Now(), "Rebuilt")
err := c.reloadConfig()
if err != nil {
@@ -860,7 +855,7 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
c.changeDetector.PrepareNew()
func() {
defer c.postBuild("Total", time.Now())
defer c.r.timeTrack(time.Now(), "Total")
if err := c.rebuildSites(dynamicEvents); err != nil {
c.handleBuildErr(err, "Rebuild failed")
}
@@ -906,13 +901,6 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
}
}
func (c *hugoBuilder) postBuild(what string, start time.Time) {
if h, err := c.hugo(); err == nil && h.Conf.Running() {
h.LogServerAddresses()
}
c.r.timeTrack(start, what)
}
func (c *hugoBuilder) hugo() (*hugolib.HugoSites, error) {
var h *hugolib.HugoSites
if err := c.withConfE(func(conf *commonConfig) error {
+1 -1
View File
@@ -209,7 +209,7 @@ func (c *newCommand) newSiteNextStepsText(path string, format string) string {
1. Change the current directory to ` + path + `.
2. Create or install a theme:
- Create a new theme with the command "hugo new theme <THEMENAME>"
- Or, install a theme from https://themes.gohugo.io/
- Install a theme from https://themes.gohugo.io/
3. Edit hugo.` + format + `, setting the "theme" property to the theme name.
4. Create new content with the command "hugo new content `)
-14
View File
@@ -123,20 +123,6 @@ func InSlicEqualFold(arr []string, el string) bool {
return false
}
// ToString converts the given value to a string.
// Note that this is a more strict version compared to cast.ToString,
// as it will not try to convert numeric values to strings,
// but only accept strings or fmt.Stringer.
func ToString(v any) (string, bool) {
switch vv := v.(type) {
case string:
return vv, true
case fmt.Stringer:
return vv.String(), true
}
return "", false
}
type Tuple struct {
First string
Second string
+1 -1
View File
@@ -17,7 +17,7 @@ package hugo
// This should be the only one.
var CurrentVersion = Version{
Major: 0,
Minor: 126,
Minor: 125,
PatchLevel: 0,
Suffix: "-DEV",
}
-3
View File
@@ -71,9 +71,6 @@ func (c ConfigLanguage) Environment() string {
}
func (c ConfigLanguage) IsMultihost() bool {
if len(c.m.Languages)-len(c.config.C.DisabledLanguages) <= 1 {
return false
}
return c.m.IsMultihost
}
+1 -1
View File
@@ -155,7 +155,7 @@ func (d *Deps) Init() error {
}
if d.MemCache == nil {
d.MemCache = dynacache.New(dynacache.Options{Watching: d.Conf.Watching(), Log: d.Log})
d.MemCache = dynacache.New(dynacache.Options{Running: d.Conf.Running(), Log: d.Log})
}
if d.PathSpec == nil {
@@ -20,11 +20,10 @@ hugo gen chromastyles [flags] [args]
### Options
```
-h, --help help for chromastyles
--highlightStyle string foreground and background colors for highlighted lines, e.g. --highlightStyle "#fff000 bg:#000fff"
--lineNumbersInlineStyle string foreground and background colors for inline line numbers, e.g. --lineNumbersInlineStyle "#fff000 bg:#000fff"
--lineNumbersTableStyle string foreground and background colors for table line numbers, e.g. --lineNumbersTableStyle "#fff000 bg:#000fff"
--style string highlighter style (see https://xyproto.github.io/splash/docs/) (default "friendly")
-h, --help help for chromastyles
--highlightStyle string style used for highlighting lines (see https://github.com/alecthomas/chroma)
--linesStyle string style used for line numbers (see https://github.com/alecthomas/chroma)
--style string highlighter style (see https://xyproto.github.io/splash/docs/) (default "friendly")
```
### Options inherited from parent commands
-5
View File
@@ -4017,11 +4017,6 @@ tpl:
- s
Description: CountWords returns the approximate word count in s.
Examples: []
Diff:
Aliases: null
Args: null
Description: ""
Examples: null
FindRE:
Aliases:
- findRE
+5 -5
View File
@@ -30,7 +30,7 @@ require (
github.com/fortytw2/leaktest v1.3.0
github.com/frankban/quicktest v1.14.6
github.com/fsnotify/fsnotify v1.7.0
github.com/getkin/kin-openapi v0.123.0
github.com/getkin/kin-openapi v0.124.0
github.com/ghodss/yaml v1.0.0
github.com/gobuffalo/flect v1.0.2
github.com/gobwas/glob v0.2.3
@@ -55,7 +55,7 @@ require (
github.com/niklasfasching/go-org v1.7.0
github.com/olekukonko/tablewriter v0.0.5
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
github.com/pelletier/go-toml/v2 v2.2.2
github.com/pelletier/go-toml/v2 v2.2.0
github.com/rogpeppe/go-internal v1.12.0
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
github.com/sanity-io/litter v1.5.5
@@ -64,8 +64,8 @@ require (
github.com/spf13/cobra v1.8.0
github.com/spf13/fsync v0.10.1
github.com/spf13/pflag v1.0.5
github.com/tdewolff/minify/v2 v2.20.20
github.com/tdewolff/parse/v2 v2.7.13
github.com/tdewolff/minify/v2 v2.20.19
github.com/tdewolff/parse/v2 v2.7.12
github.com/yuin/goldmark v1.7.1
github.com/yuin/goldmark-emoji v1.0.2
go.uber.org/automaxprocs v1.5.3
@@ -152,7 +152,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
howett.net/plist v1.0.0 // indirect
software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect
+8 -10
View File
@@ -193,8 +193,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8=
github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M=
github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@@ -378,8 +376,8 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
@@ -431,10 +429,10 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tdewolff/minify/v2 v2.20.20 h1:vhULb+VsW2twkplgsawAoUY957efb+EdiZ7zu5fUhhk=
github.com/tdewolff/minify/v2 v2.20.20/go.mod h1:GYaLXFpIIwsX99apQHXfGdISUdlA98wmaoWxjT9C37k=
github.com/tdewolff/parse/v2 v2.7.13 h1:iSiwOUkCYLNfapHoqdLcqZVgvQ0jrsao8YYKP/UJYTI=
github.com/tdewolff/parse/v2 v2.7.13/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo=
github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ=
github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
@@ -799,8 +797,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+14 -16
View File
@@ -36,6 +36,11 @@ import (
"github.com/gohugoio/hugo/config"
)
var (
openingPTag = []byte("<p>")
closingPTag = []byte("</p>")
)
// ContentSpec provides functionality to render markdown content.
type ContentSpec struct {
Converters markup.ConverterProvider
@@ -237,26 +242,19 @@ func (c *ContentSpec) TruncateWordsToWholeSentence(s string) (string, bool) {
return strings.TrimSpace(s[:endIndex]), endIndex < len(s)
}
// TrimShortHTML removes the outer tags from HTML input where (a) the opening
// tag is present only once with the input, and (b) the opening and closing
// tags wrap the input after white space removal.
func (c *ContentSpec) TrimShortHTML(input []byte, markup string) []byte {
openingTag := []byte("<p>")
closingTag := []byte("</p>")
if markup == "asciidocext" {
openingTag = []byte("<div class=\"paragraph\">\n<p>")
closingTag = []byte("</p>\n</div>")
}
if bytes.Count(input, openingTag) == 1 {
// TrimShortHTML removes the <p>/</p> tags from HTML input in the situation
// where said tags are the only <p> tags in the input and enclose the content
// of the input (whitespace excluded).
func (c *ContentSpec) TrimShortHTML(input []byte) []byte {
if bytes.Count(input, openingPTag) == 1 {
input = bytes.TrimSpace(input)
if bytes.HasPrefix(input, openingTag) && bytes.HasSuffix(input, closingTag) {
input = bytes.TrimPrefix(input, openingTag)
input = bytes.TrimSuffix(input, closingTag)
if bytes.HasPrefix(input, openingPTag) && bytes.HasSuffix(input, closingPTag) {
input = bytes.TrimPrefix(input, openingPTag)
input = bytes.TrimSuffix(input, closingPTag)
input = bytes.TrimSpace(input)
}
}
return input
}
+14 -17
View File
@@ -26,27 +26,24 @@ import (
func TestTrimShortHTML(t *testing.T) {
tests := []struct {
markup string
input []byte
output []byte
input, output []byte
}{
{"markdown", []byte(""), []byte("")},
{"markdown", []byte("Plain text"), []byte("Plain text")},
{"markdown", []byte("<p>Simple paragraph</p>"), []byte("Simple paragraph")},
{"markdown", []byte("\n \n \t <p> \t Whitespace\nHTML \n\t </p>\n\t"), []byte("Whitespace\nHTML")},
{"markdown", []byte("<p>Multiple</p><p>paragraphs</p>"), []byte("<p>Multiple</p><p>paragraphs</p>")},
{"markdown", []byte("<p>Nested<p>paragraphs</p></p>"), []byte("<p>Nested<p>paragraphs</p></p>")},
{"markdown", []byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>"), []byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>")},
// Issue 11698
{"markdown", []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>"), []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>")},
// Issue 12369
{"markdown", []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>"), []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>")},
{"asciidocext", []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>"), []byte("foo")},
{[]byte(""), []byte("")},
{[]byte("Plain text"), []byte("Plain text")},
// This seems wrong. Why touch it if it doesn't have p tag?
// {[]byte(" \t\n Whitespace text\n\n"), []byte("Whitespace text")},
{[]byte("<p>Simple paragraph</p>"), []byte("Simple paragraph")},
{[]byte("\n \n \t <p> \t Whitespace\nHTML \n\t </p>\n\t"), []byte("Whitespace\nHTML")},
{[]byte("<p>Multiple</p><p>paragraphs</p>"), []byte("<p>Multiple</p><p>paragraphs</p>")},
{[]byte("<p>Nested<p>paragraphs</p></p>"), []byte("<p>Nested<p>paragraphs</p></p>")},
{[]byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>"), []byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>")},
// Issue #11698
{[]byte("<h2 id=`a`>b</h2>\n\n<p>c</p>"), []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>")},
}
c := newTestContentSpec(nil)
for i, test := range tests {
output := c.TrimShortHTML(test.input, test.markup)
output := c.TrimShortHTML(test.input)
if !bytes.Equal(test.output, output) {
t.Errorf("Test %d failed. Expected %q got %q", i, test.output, output)
}
@@ -57,7 +54,7 @@ func BenchmarkTrimShortHTML(b *testing.B) {
c := newTestContentSpec(nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
c.TrimShortHTML([]byte("<p>Simple paragraph</p>"), "markdown")
c.TrimShortHTML([]byte("<p>Simple paragraph</p>"))
}
}
-6
View File
@@ -16,7 +16,6 @@ package hqt
import (
"errors"
"fmt"
"math"
"reflect"
"strings"
@@ -39,11 +38,6 @@ var IsSameType qt.Checker = &typeChecker{
argNames: []string{"got", "want"},
}
// IsSameFloat64 asserts that two float64 values are equal within a small delta.
var IsSameFloat64 = qt.CmpEquals(cmp.Comparer(func(a, b float64) bool {
return math.Abs(a-b) < 0.0001
}))
type argNames []string
func (a argNames) ArgNames() []string {
+1 -50
View File
@@ -329,7 +329,7 @@ cascade:
counters := &buildCounters{}
b.Build(BuildCfg{testCounters: counters})
b.Assert(int(counters.contentRenderCounter.Load()), qt.Equals, 1)
b.Assert(int(counters.contentRenderCounter.Load()), qt.Equals, 2)
b.AssertFileContent("public/post/index.html", `Banner: post.jpg|Layout: postlayout|Type: posttype|Content: <p>content edit</p>`)
b.AssertFileContent("public/post/dir/p1/index.html", `Banner: post.jpg|Layout: postlayout|`)
@@ -672,55 +672,6 @@ S1|p1:|p2:p2|
})
}
func TestCascadeEditIssue12449(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ['sitemap','rss', 'home', 'taxonomy','term']
disableLiveReload = true
-- layouts/_default/list.html --
Title: {{ .Title }}|{{ .Content }}|cascadeparam: {{ .Params.cascadeparam }}|
-- layouts/_default/single.html --
Title: {{ .Title }}|{{ .Content }}|cascadeparam: {{ .Params.cascadeparam }}|
-- content/mysect/_index.md --
---
title: mysect
cascade:
description: descriptionvalue
params:
cascadeparam: cascadeparamvalue
---
mysect-content|
-- content/mysect/p1/index.md --
---
slug: p1
---
p1-content|
-- content/mysect/subsect/_index.md --
---
slug: subsect
---
subsect-content|
`
b := TestRunning(t, files)
// Make the cascade set the title.
b.EditFileReplaceAll("content/mysect/_index.md", "description: descriptionvalue", "title: cascadetitle").Build()
b.AssertFileContent("public/mysect/subsect/index.html", "Title: cascadetitle|")
// Edit cascade title.
b.EditFileReplaceAll("content/mysect/_index.md", "title: cascadetitle", "title: cascadetitle-edit").Build()
b.AssertFileContent("public/mysect/subsect/index.html", "Title: cascadetitle-edit|")
// Revert title change.
// The step below failed in #12449.
b.EditFileReplaceAll("content/mysect/_index.md", "title: cascadetitle-edit", "description: descriptionvalue").Build()
b.AssertFileContent("public/mysect/subsect/index.html", "Title: |")
}
// Issue 11977.
func TestCascadeExtensionInPath(t *testing.T) {
t.Parallel()
+5 -17
View File
@@ -825,9 +825,6 @@ func (s *contentNodeShifter) Insert(old, new contentNodeI) contentNodeI {
panic(fmt.Sprintf("unknown type %T", new))
}
if vv.s.languagei == newp.s.languagei {
if newp != old {
resource.MarkStale(old)
}
return new
}
is := make(contentNodeIs, s.numLanguages)
@@ -839,10 +836,7 @@ func (s *contentNodeShifter) Insert(old, new contentNodeI) contentNodeI {
if !ok {
panic(fmt.Sprintf("unknown type %T", new))
}
oldp := vv[newp.s.languagei]
if oldp != newp {
resource.MarkStale(oldp)
}
resource.MarkStale(vv[newp.s.languagei])
vv[newp.s.languagei] = new
return vv
case *resourceSource:
@@ -851,9 +845,6 @@ func (s *contentNodeShifter) Insert(old, new contentNodeI) contentNodeI {
panic(fmt.Sprintf("unknown type %T", new))
}
if vv.LangIndex() == newp.LangIndex() {
if vv != newp {
resource.MarkStale(vv)
}
return new
}
rs := make(resourceSources, s.numLanguages)
@@ -865,10 +856,7 @@ func (s *contentNodeShifter) Insert(old, new contentNodeI) contentNodeI {
if !ok {
panic(fmt.Sprintf("unknown type %T", new))
}
oldp := vv[newp.LangIndex()]
if oldp != newp {
resource.MarkStale(oldp)
}
resource.MarkStale(vv[newp.LangIndex()])
vv[newp.LangIndex()] = newp
return vv
default:
@@ -1066,7 +1054,7 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
)
for _, id := range changes {
if staler, ok := id.(resource.Staler); ok {
if staler, ok := id.(resource.Staler); ok && !staler.IsStale() {
var msgDetail string
if p, ok := id.(*pageState); ok && p.File() != nil {
msgDetail = fmt.Sprintf(" (%s)", p.File().Filename())
@@ -1096,7 +1084,7 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
return b
}
h.MemCache.ClearMatching(nil, shouldDelete)
h.MemCache.ClearMatching(shouldDelete)
return ll, nil
}); err != nil {
@@ -1615,7 +1603,7 @@ func (sa *sitePagesAssembler) assembleResources() error {
targetPaths := ps.targetPaths()
baseTarget := targetPaths.SubResourceBaseTarget
duplicateResourceFiles := true
if ps.m.pageConfig.IsGoldmark {
if ps.s.ContentSpec.Converters.IsGoldmark(ps.m.pageConfig.Markup) {
duplicateResourceFiles = ps.s.ContentSpec.Converters.GetMarkupConfig().Goldmark.DuplicateResourceFiles
}
-50
View File
@@ -14,7 +14,6 @@
package hugolib
import (
"fmt"
"strings"
"testing"
)
@@ -242,52 +241,3 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
"p1|<p><a href=\"p2\">P2</a>", "<img src=\"pixel.png\" alt=\"Pixel\">")
})
}
func TestRenderHooksDefaultEscape(t *testing.T) {
files := `
-- hugo.toml --
[markup.goldmark.renderHooks]
[markup.goldmark.renderHooks.image]
enableDefault = ENABLE
[markup.goldmark.renderHooks.link]
enableDefault = ENABLE
[markup.goldmark.parser]
wrapStandAloneImageWithinParagraph = false
[markup.goldmark.parser.attribute]
block = true
title = true
-- content/_index.md --
---
title: "Home"
---
Link: [text-"<>&](/destination-"<> 'title-"<>&')
Image: ![alt-"<>&](/destination-"<> 'title-"<>&')
{class="><script>alert()</script>" id="baz"}
-- layouts/index.html --
{{ .Content }}
`
for _, enabled := range []bool{true, false} {
enabled := enabled
t.Run(fmt.Sprint(enabled), func(t *testing.T) {
t.Parallel()
b := Test(t, strings.ReplaceAll(files, "ENABLE", fmt.Sprint(enabled)))
// The escaping is slightly different between the two.
if enabled {
b.AssertFileContent("public/index.html",
"Link: <a href=\"/destination-%22%3C%3E\" title=\"title-&#34;&lt;&gt;&amp;\">text-&quot;&lt;&gt;&amp;</a>",
"img alt=\"alt-&quot;&lt;&gt;&amp;\" src=\"/destination-%22%3C%3E\" title=\"title-&#34;&lt;&gt;&amp;\">",
"&gt;&lt;script&gt;",
)
} else {
b.AssertFileContent("public/index.html",
"Link: <a href=\"/destination-%22%3C%3E\" title=\"title-&quot;&lt;&gt;&amp;\">text-&quot;&lt;&gt;&amp;</a>",
"Image: <img src=\"/destination-%22%3C%3E\" alt=\"alt-&quot;&lt;&gt;&amp;\" title=\"title-&quot;&lt;&gt;&amp;\">",
)
}
})
}
}
+17 -8
View File
@@ -26,6 +26,7 @@ import (
"time"
"github.com/bep/logg"
"github.com/gohugoio/hugo/cache/dynacache"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugofs/files"
@@ -274,7 +275,7 @@ func (h *HugoSites) assemble(ctx context.Context, l logg.LevelLogger, bcfg *Buil
changes := assembleChanges.Changes()
// Changes from the assemble step (e.g. lastMod, cascade) needs a re-calculation
// Changes from the assemble step (e.g. lastMod, cascase) needs a re-calculation
// of what needs to be re-built.
if len(changes) > 0 {
if err := h.resolveAndClearStateForIdentities(ctx, l, nil, changes); err != nil {
@@ -595,10 +596,6 @@ type pathChange struct {
isDir bool
}
func (p pathChange) isStructuralChange() bool {
return p.delete || p.isDir
}
// processPartial prepares the Sites' sources for a partial rebuild.
func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, config *BuildCfg, init func(config *BuildCfg) error, events []fsnotify.Event) error {
h.Log.Trace(logg.StringFunc(func() string {
@@ -613,7 +610,7 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
// For a list of events for the different OSes, see the test output in https://github.com/bep/fsnotifyeventlister/.
events = h.fileEventsFilter(events)
events = h.fileEventsTrim(events)
events = h.fileEventsTranslate(events)
eventInfos := h.fileEventsApplyInfo(events)
logger := h.Log
@@ -762,7 +759,17 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
}
case files.ComponentFolderAssets:
logger.Println("Asset changed", pathInfo.Path())
changes = append(changes, pathInfo)
var hasID bool
r, _ := h.ResourceSpec.ResourceCache.Get(context.Background(), dynacache.CleanKey(pathInfo.Base()))
identity.WalkIdentitiesShallow(r, func(level int, rid identity.Identity) bool {
hasID = true
changes = append(changes, rid)
return false
})
if !hasID {
changes = append(changes, pathInfo)
}
case files.ComponentFolderData:
logger.Println("Data changed", pathInfo.Path())
@@ -912,10 +919,12 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
}
}
h.logServerAddresses()
return nil
}
func (h *HugoSites) LogServerAddresses() {
func (h *HugoSites) logServerAddresses() {
if h.hugoInfo.IsMultihost() {
for _, s := range h.Sites {
h.Log.Printf("Web Server is available at %s (bind address %s) %s\n", s.conf.C.BaseURL, s.conf.C.ServerInterface, s.Language().Lang)
-28
View File
@@ -252,31 +252,3 @@ Files: {{ range $files }}{{ .Permalink }}|{{ end }}$
b.AssertFileContent("public/en/enpages/mybundle-en/file2.txt", "File 2 en.")
b.AssertFileContent("public/fr/section/mybundle/file2.txt", "File 2 en.")
}
func TestMultihostAllButOneLanguageDisabledIssue12288(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
defaultContentLanguage = "en"
disableLanguages = ["fr"]
#baseURL = "https://example.com"
[languages]
[languages.en]
baseURL = "https://example.en"
weight = 1
[languages.fr]
baseURL = "https://example.fr"
weight = 2
-- assets/css/main.css --
body { color: red; }
-- layouts/index.html --
{{ $css := resources.Get "css/main.css" | minify }}
CSS: {{ $css.Permalink }}|{{ $css.RelPermalink }}|
`
b := Test(t, files)
b.AssertFileContent("public/css/main.min.css", "body{color:red}")
b.AssertFileContent("public/index.html", "CSS: https://example.en/css/main.min.css|/css/main.min.css|")
}
+1 -48
View File
@@ -10,7 +10,6 @@ import (
"os"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
"sync"
@@ -38,20 +37,12 @@ import (
type TestOpt func(*IntegrationTestConfig)
// TestOptRunning will enable running in integration tests.
func TestOptRunning() TestOpt {
return func(c *IntegrationTestConfig) {
c.Running = true
}
}
// TestOptWatching will enable watching in integration tests.
func TestOptWatching() TestOpt {
return func(c *IntegrationTestConfig) {
c.Watching = true
}
}
// Enable tracing in integration tests.
// THis should only be used during development and not committed to the repo.
func TestOptTrace() TestOpt {
@@ -578,10 +569,6 @@ func (s *IntegrationTestBuilder) initBuilder() error {
"running": s.Cfg.Running,
"watch": s.Cfg.Running,
})
} else if s.Cfg.Watching {
flags.Set("internal", maps.Params{
"watch": s.Cfg.Watching,
})
}
if s.Cfg.WorkingDir != "" {
@@ -698,17 +685,8 @@ func (s *IntegrationTestBuilder) build(cfg BuildCfg) error {
return nil
}
// We simulate the fsnotify events.
// See the test output in https://github.com/bep/fsnotifyeventlister for what events gets produced
// by the different OSes.
func (s *IntegrationTestBuilder) changeEvents() []fsnotify.Event {
var (
events []fsnotify.Event
isLinux = runtime.GOOS == "linux"
isMacOs = runtime.GOOS == "darwin"
isWindows = runtime.GOOS == "windows"
)
var events []fsnotify.Event
for _, v := range s.removedFiles {
events = append(events, fsnotify.Event{
Name: v,
@@ -735,32 +713,12 @@ func (s *IntegrationTestBuilder) changeEvents() []fsnotify.Event {
Name: v,
Op: fsnotify.Write,
})
if isLinux || isWindows {
// Duplicate write events, for some reason.
events = append(events, fsnotify.Event{
Name: v,
Op: fsnotify.Write,
})
}
if isMacOs {
events = append(events, fsnotify.Event{
Name: v,
Op: fsnotify.Chmod,
})
}
}
for _, v := range s.createdFiles {
events = append(events, fsnotify.Event{
Name: v,
Op: fsnotify.Create,
})
if isLinux || isWindows {
events = append(events, fsnotify.Event{
Name: v,
Op: fsnotify.Write,
})
}
}
// Shuffle events.
@@ -829,11 +787,6 @@ type IntegrationTestConfig struct {
// Whether to simulate server mode.
Running bool
// Watch for changes.
// This is (currently) always set to true when Running is set.
// Note that the CLI for the server does allow for --watch=false, but that is not used in these test.
Watching bool
// Will print the log buffer after the build
Verbose bool
-34
View File
@@ -676,37 +676,3 @@ menu: main
b.AssertFileContent("public/fr/index.html", `<a href="/fr/p1/">p1</a>`)
b.AssertLogNotContains("WARN")
}
func TestSectionPagesIssue12399(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['rss','sitemap','taxonomy','term']
capitalizeListTitles = false
pluralizeListTitles = false
sectionPagesMenu = 'main'
-- content/p1.md --
---
title: p1
---
-- content/s1/p2.md --
---
title: p2
menus: main
---
-- content/s1/p3.md --
---
title: p3
---
-- layouts/_default/list.html --
{{ range site.Menus.main }}<a href="{{ .URL }}">{{ .Name }}</a>{{ end }}
-- layouts/_default/single.html --
{{ .Title }}
`
b := Test(t, files)
b.AssertFileExists("public/index.html", true)
b.AssertFileContent("public/index.html", `<a href="/s1/p2/">p2</a><a href="/s1/">s1</a>`)
}
-1
View File
@@ -67,7 +67,6 @@ type pageCommon struct {
page.InSectionPositioner
page.OutputFormatsProvider
page.PageMetaProvider
page.PageMetaInternalProvider
page.Positioner
page.RawContentProvider
page.RelatedKeywordsProvider
+13 -23
View File
@@ -418,8 +418,6 @@ func (c *cachedContent) mustSource() []byte {
func (c *contentParseInfo) contentSource(s resource.StaleInfo) ([]byte, error) {
key := c.sourceKey
versionv := s.StaleVersion()
v, err := c.h.cacheContentSource.GetOrCreate(key, func(string) (*resources.StaleValue[[]byte], error) {
b, err := c.readSourceAll()
if err != nil {
@@ -428,8 +426,8 @@ func (c *contentParseInfo) contentSource(s resource.StaleInfo) ([]byte, error) {
return &resources.StaleValue[[]byte]{
Value: b,
StaleVersionFunc: func() uint32 {
return s.StaleVersion() - versionv
IsStaleFunc: func() bool {
return s.IsStale()
},
}, nil
})
@@ -489,7 +487,7 @@ type contentPlainPlainWords struct {
func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutput) (contentSummary, error) {
ctx = tpl.Context.DependencyScope.Set(ctx, pageDependencyScopeGlobal)
key := c.pi.sourceKey + "/" + cp.po.f.Name
versionv := c.version(cp)
versionv := cp.contentRenderedVersion
v, err := c.pm.cacheContentRendered.GetOrCreate(key, func(string) (*resources.StaleValue[contentSummary], error) {
cp.po.p.s.Log.Trace(logg.StringFunc(func() string {
@@ -506,8 +504,8 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
}
rs := &resources.StaleValue[contentSummary]{
StaleVersionFunc: func() uint32 {
return c.version(cp) - versionv
IsStaleFunc: func() bool {
return c.IsStale() || cp.contentRenderedVersion != versionv
},
}
@@ -524,7 +522,6 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
if err != nil {
return nil, err
}
if !ok {
return nil, errors.New("invalid state: astDoc is set but RenderContent returned false")
}
@@ -609,7 +606,7 @@ var setGetContentCallbackInContext = hcontext.NewContextDispatcher[func(*pageCon
func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (contentTableOfContents, error) {
key := c.pi.sourceKey + "/" + cp.po.f.Name
versionv := c.version(cp)
versionv := cp.contentRenderedVersion
v, err := c.pm.contentTableOfContents.GetOrCreate(key, func(string) (*resources.StaleValue[contentTableOfContents], error) {
source, err := c.pi.contentSource(c)
@@ -629,10 +626,8 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
return nil, err
}
// Callback called from below (e.g. in .RenderString)
// Callback called from above (e.g. in .RenderString)
ctxCallback := func(cp2 *pageContentOutput, ct2 contentTableOfContents) {
cp.otherOutputs[cp2.po.p.pid] = cp2
// Merge content placeholders
for k, v := range ct2.contentPlaceholders {
ct.contentPlaceholders[k] = v
@@ -715,8 +710,8 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
return &resources.StaleValue[contentTableOfContents]{
Value: ct,
StaleVersionFunc: func() uint32 {
return c.version(cp) - versionv
IsStaleFunc: func() bool {
return c.IsStale() || cp.contentRenderedVersion != versionv
},
}, nil
})
@@ -727,21 +722,16 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
return v.Value, nil
}
func (c *cachedContent) version(cp *pageContentOutput) uint32 {
// Both of these gets incremented on change.
return c.StaleVersion() + cp.contentRenderedVersion
}
func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput) (contentPlainPlainWords, error) {
key := c.pi.sourceKey + "/" + cp.po.f.Name
versionv := c.version(cp)
versionv := cp.contentRenderedVersion
v, err := c.pm.cacheContentPlain.GetOrCreateWitTimeout(key, cp.po.p.s.Conf.Timeout(), func(string) (*resources.StaleValue[contentPlainPlainWords], error) {
var result contentPlainPlainWords
rs := &resources.StaleValue[contentPlainPlainWords]{
StaleVersionFunc: func() uint32 {
return c.version(cp) - versionv
IsStaleFunc: func() bool {
return c.IsStale() || cp.contentRenderedVersion != versionv
},
}
@@ -788,7 +778,7 @@ func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput)
if err != nil {
return nil, err
}
html := cp.po.p.s.ContentSpec.TrimShortHTML(b.Bytes(), cp.po.p.m.pageConfig.Markup)
html := cp.po.p.s.ContentSpec.TrimShortHTML(b.Bytes())
result.summary = helpers.BytesToHTML(html)
} else {
var summary string
+5 -25
View File
@@ -74,9 +74,7 @@ type pageMeta struct {
// Prepare for a rebuild of the data passed in from front matter.
func (m *pageMeta) setMetaPostPrepareRebuild() {
params := xmaps.Clone[map[string]any](m.paramsOriginal)
m.pageMetaParams.pageConfig = &pagemeta.PageConfig{
Params: params,
}
m.pageMetaParams.pageConfig.Params = params
m.pageMetaFrontMatter = pageMetaFrontMatter{}
}
@@ -277,7 +275,6 @@ func (p *pageMeta) Weight() int {
func (p *pageMeta) setMetaPre(pi *contentParseInfo, logger loggers.Logger, conf config.AllProvider) error {
frontmatter := pi.frontMatter
if frontmatter != nil {
pcfg := p.pageConfig
if pcfg == nil {
@@ -365,7 +362,6 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
if ps.m.setMetaPostCount > 1 {
ps.m.setMetaPostCascadeChanged = cascadeHashPre != identity.HashUint64(ps.m.pageConfig.Cascade)
if !ps.m.setMetaPostCascadeChanged {
// No changes, restore any value that may be changed by aggregation.
ps.m.pageConfig.Dates = ps.m.datesOriginal
return nil
@@ -741,8 +737,6 @@ func (p *pageMeta) applyDefaultValues() error {
}
}
p.pageConfig.IsGoldmark = p.s.ContentSpec.Converters.IsGoldmark(p.pageConfig.Markup)
if p.pageConfig.Title == "" && p.f == nil {
switch p.Kind() {
case kinds.KindHome:
@@ -800,26 +794,12 @@ func (p *pageMeta) newContentConverter(ps *pageState, markup string) (converter.
path = p.Path()
}
doc := newPageForRenderHook(ps)
documentLookup := func(id uint64) any {
if id == ps.pid {
// This prevents infinite recursion in some cases.
return doc
}
if v, ok := ps.pageOutput.pco.otherOutputs[id]; ok {
return v.po.p
}
return nil
}
cpp, err := cp.New(
converter.DocumentContext{
Document: doc,
DocumentLookup: documentLookup,
DocumentID: id,
DocumentName: path,
Filename: filename,
Document: newPageForRenderHook(ps),
DocumentID: id,
DocumentName: path,
Filename: filename,
},
)
if err != nil {
-1
View File
@@ -184,7 +184,6 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
ResourceNameTitleProvider: m,
ResourceParamsProvider: m,
PageMetaProvider: m,
PageMetaInternalProvider: m,
RelatedKeywordsProvider: m,
OutputFormatsProvider: page.NopPage,
ResourceTypeProvider: pageTypesProvider,
+7 -22
View File
@@ -30,7 +30,6 @@ import (
"github.com/spf13/cast"
"github.com/gohugoio/hugo/markup/converter/hooks"
"github.com/gohugoio/hugo/markup/goldmark/hugocontext"
"github.com/gohugoio/hugo/markup/highlight/chromalexers"
"github.com/gohugoio/hugo/markup/tableofcontents"
@@ -69,9 +68,8 @@ var (
func newPageContentOutput(po *pageOutput) (*pageContentOutput, error) {
cp := &pageContentOutput{
po: po,
renderHooks: &renderHooks{},
otherOutputs: make(map[uint64]*pageContentOutput),
po: po,
renderHooks: &renderHooks{},
}
return cp, nil
}
@@ -85,12 +83,8 @@ type renderHooks struct {
type pageContentOutput struct {
po *pageOutput
// Other pages involved in rendering of this page,
// typically included with .RenderShortcodes.
otherOutputs map[uint64]*pageContentOutput
contentRenderedVersion uint32 // Incremented on reset.
contentRendered bool // Set on content render.
contentRenderedVersion int // Incremented on reset.
contentRendered bool // Set on content render.
// Renders Markdown hooks.
renderHooks *renderHooks
@@ -171,13 +165,6 @@ func (pco *pageContentOutput) RenderShortcodes(ctx context.Context) (template.HT
cb(pco, ct)
}
if tpl.Context.IsInGoldmark.Get(ctx) {
// This content will be parsed and rendered by Goldmark.
// Wrap it in a special Hugo markup to assign the correct Page from
// the stack.
return template.HTML(hugocontext.Wrap(c, pco.po.p.pid)), nil
}
return helpers.BytesToHTML(c), nil
}
@@ -376,11 +363,9 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
}
if opts.Display == "inline" {
markup := pco.po.p.m.pageConfig.Markup
if opts.Markup != "" {
markup = pco.po.p.s.ContentSpec.ResolveMarkup(opts.Markup)
}
rendered = pco.po.p.s.ContentSpec.TrimShortHTML(rendered, markup)
// We may have to rethink this in the future when we get other
// renderers.
rendered = pco.po.p.s.ContentSpec.TrimShortHTML(rendered)
}
return template.HTML(string(rendered)), nil
+2 -2
View File
@@ -147,7 +147,7 @@ func (c *pagesCollector) Collect() (collectErr error) {
false,
func(fim hugofs.FileMetaInfo) bool {
if fim.IsDir() {
return id.isStructuralChange()
return true
}
fimp := fim.Meta().PathInfo
if fimp == nil {
@@ -160,7 +160,7 @@ func (c *pagesCollector) Collect() (collectErr error) {
} else {
// We always start from a directory.
collectErr = c.collectDir(id.p, id.isDir, func(fim hugofs.FileMetaInfo) bool {
if id.isStructuralChange() {
if id.delete || id.isDir {
if id.isDir && fim.Meta().PathInfo.IsLeafBundle() {
return strings.HasPrefix(fim.Meta().PathInfo.Path(), paths.AddTrailingSlash(id.p.Path()))
}
+17 -144
View File
@@ -11,7 +11,6 @@ import (
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/htesting"
"github.com/gohugoio/hugo/markup/asciidocext"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss"
)
@@ -53,11 +52,6 @@ title: "Home"
Home Content.
-- content/hometext.txt --
Home Text Content.
-- content/myothersection/myothersectionpage.md --
---
title: "myothersectionpage"
---
myothersectionpage Content.
-- layouts/_default/single.html --
Single: {{ .Title }}|{{ .Content }}$
Resources: {{ range $i, $e := .Resources }}{{ $i }}:{{ .RelPermalink }}|{{ .Content }}|{{ end }}$
@@ -126,23 +120,14 @@ func TestRebuildEditTextFileInBranchBundle(t *testing.T) {
b.AssertRenderCountContent(1)
}
func testRebuildBothWatchingAndRunning(t *testing.T, files string, withB func(b *IntegrationTestBuilder)) {
t.Helper()
for _, opt := range []TestOpt{TestOptWatching(), TestOptRunning()} {
b := Test(t, files, opt)
withB(b)
}
}
func TestRebuildRenameTextFileInLeafBundle(t *testing.T) {
testRebuildBothWatchingAndRunning(t, rebuildFilesSimple, func(b *IntegrationTestBuilder) {
b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
b := TestRunning(t, rebuildFilesSimple)
b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
b.RenameFile("content/mysection/mysectionbundle/mysectionbundletext.txt", "content/mysection/mysectionbundle/mysectionbundletext2.txt").Build()
b.AssertFileContent("public/mysection/mysectionbundle/index.html", "mysectionbundletext2", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
b.AssertRenderCountPage(5)
b.AssertRenderCountContent(6)
})
b.RenameFile("content/mysection/mysectionbundle/mysectionbundletext.txt", "content/mysection/mysectionbundle/mysectionbundletext2.txt").Build()
b.AssertFileContent("public/mysection/mysectionbundle/index.html", "mysectionbundletext2", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
b.AssertRenderCountPage(3)
b.AssertRenderCountContent(3)
}
func TestRebuilEditContentFileInLeafBundle(t *testing.T) {
@@ -152,19 +137,6 @@ func TestRebuilEditContentFileInLeafBundle(t *testing.T) {
b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Content Content Edited.")
}
func TestRebuilEditContentFileThenAnother(t *testing.T) {
b := TestRunning(t, rebuildFilesSimple)
b.EditFileReplaceAll("content/mysection/mysectionbundle/mysectionbundlecontent.md", "Content Content.", "Content Content Edited.").Build()
b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Content Content Edited.")
b.AssertRenderCountPage(1)
b.AssertRenderCountContent(2)
b.EditFileReplaceAll("content/myothersection/myothersectionpage.md", "myothersectionpage Content.", "myothersectionpage Content Edited.").Build()
b.AssertFileContent("public/myothersection/myothersectionpage/index.html", "myothersectionpage Content Edited")
b.AssertRenderCountPage(1)
b.AssertRenderCountContent(1)
}
func TestRebuildRenameTextFileInBranchBundle(t *testing.T) {
b := TestRunning(t, rebuildFilesSimple)
b.AssertFileContent("public/mysection/index.html", "My Section")
@@ -181,7 +153,7 @@ func TestRebuildRenameTextFileInHomeBundle(t *testing.T) {
b.RenameFile("content/hometext.txt", "content/hometext2.txt").Build()
b.AssertFileContent("public/index.html", "hometext2", "Home Text Content.")
b.AssertRenderCountPage(3)
b.AssertRenderCountPage(2)
}
func TestRebuildRenameDirectoryWithLeafBundle(t *testing.T) {
@@ -197,7 +169,7 @@ func TestRebuildRenameDirectoryWithBranchBundle(t *testing.T) {
b.AssertFileContent("public/mysectionrenamed/index.html", "My Section")
b.AssertFileContent("public/mysectionrenamed/mysectionbundle/index.html", "My Section Bundle")
b.AssertFileContent("public/mysectionrenamed/mysectionbundle/mysectionbundletext.txt", "My Section Bundle Text 2 Content.")
b.AssertRenderCountPage(3)
b.AssertRenderCountPage(2)
}
func TestRebuildRenameDirectoryWithRegularPageUsedInHome(t *testing.T) {
@@ -296,7 +268,7 @@ func TestRebuildRenameDirectoryWithBranchBundleFastRender(t *testing.T) {
b.AssertFileContent("public/mysectionrenamed/index.html", "My Section")
b.AssertFileContent("public/mysectionrenamed/mysectionbundle/index.html", "My Section Bundle")
b.AssertFileContent("public/mysectionrenamed/mysectionbundle/mysectionbundletext.txt", "My Section Bundle Text 2 Content.")
b.AssertRenderCountPage(3)
b.AssertRenderCountPage(2)
}
func TestRebuilErrorRecovery(t *testing.T) {
@@ -394,6 +366,8 @@ My short.
}
func TestRebuildBaseof(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
title = "Hugo Site"
@@ -408,13 +382,12 @@ Baseof: {{ .Title }}|
Home: {{ .Title }}|{{ .Content }}|
{{ end }}
`
testRebuildBothWatchingAndRunning(t, files, func(b *IntegrationTestBuilder) {
b.AssertFileContent("public/index.html", "Baseof: Hugo Site|", "Home: Hugo Site||")
b.EditFileReplaceFunc("layouts/_default/baseof.html", func(s string) string {
return strings.Replace(s, "Baseof", "Baseof Edited", 1)
}).Build()
b.AssertFileContent("public/index.html", "Baseof Edited: Hugo Site|", "Home: Hugo Site||")
})
b := Test(t, files, TestOptRunning())
b.AssertFileContent("public/index.html", "Baseof: Hugo Site|", "Home: Hugo Site||")
b.EditFileReplaceFunc("layouts/_default/baseof.html", func(s string) string {
return strings.Replace(s, "Baseof", "Baseof Edited", 1)
}).Build()
b.AssertFileContent("public/index.html", "Baseof Edited: Hugo Site|", "Home: Hugo Site||")
}
func TestRebuildSingleWithBaseof(t *testing.T) {
@@ -1541,103 +1514,3 @@ MyTemplate: {{ partial "MyTemplate.html" . }}|
b.AssertFileContent("public/index.html", "MyTemplate: MyTemplate Edited")
}
func TestRebuildEditAsciidocContentFile(t *testing.T) {
if !asciidocext.Supports() {
t.Skip("skip asciidoc")
}
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableLiveReload = true
disableKinds = ["taxonomy", "term", "sitemap", "robotsTXT", "404", "rss", "home", "section"]
[security]
[security.exec]
allow = ['^python$', '^rst2html.*', '^asciidoctor$']
-- content/posts/p1.adoc --
---
title: "P1"
---
P1 Content.
-- content/posts/p2.adoc --
---
title: "P2"
---
P2 Content.
-- layouts/_default/single.html --
Single: {{ .Title }}|{{ .Content }}|
`
b := TestRunning(t, files)
b.AssertFileContent("public/posts/p1/index.html",
"Single: P1|<div class=\"paragraph\">\n<p>P1 Content.</p>\n</div>\n|")
b.AssertRenderCountPage(2)
b.AssertRenderCountContent(2)
b.EditFileReplaceAll("content/posts/p1.adoc", "P1 Content.", "P1 Content Edited.").Build()
b.AssertFileContent("public/posts/p1/index.html", "Single: P1|<div class=\"paragraph\">\n<p>P1 Content Edited.</p>\n</div>\n|")
b.AssertRenderCountPage(1)
b.AssertRenderCountContent(1)
}
func TestRebuildEditSingleListChangeUbuntuIssue12362(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['rss','section','sitemap','taxonomy','term']
disableLiveReload = true
-- layouts/_default/list.html --
{{ range .Pages }}{{ .Title }}|{{ end }}
-- layouts/_default/single.html --
{{ .Title }}
-- content/p1.md --
---
title: p1
---
`
b := TestRunning(t, files)
b.AssertFileContent("public/index.html", "p1|")
b.AddFiles("content/p2.md", "---\ntitle: p2\n---").Build()
b.AssertFileContent("public/index.html", "p1|p2|") // this test passes, which doesn't match reality
}
func TestRebuildHomeThenPageIssue12436(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ['sitemap','taxonomy','term']
disableLiveReload = true
-- layouts/_default/list.html --
{{ .Content }}
-- layouts/_default/single.html --
{{ .Content }}
-- content/_index.md --
---
title: home
---
home-content|
-- content/p1/index.md --
---
title: p1
---
p1-content|
`
b := TestRunning(t, files)
b.AssertFileContent("public/index.html", "home-content|")
b.AssertFileContent("public/p1/index.html", "p1-content|")
b.AssertRenderCountPage(3)
b.EditFileReplaceAll("content/_index.md", "home-content", "home-content-foo").Build()
b.AssertFileContent("public/index.html", "home-content-foo")
b.AssertRenderCountPage(2) // Home page rss + html
b.EditFileReplaceAll("content/p1/index.md", "p1-content", "p1-content-foo").Build()
b.AssertFileContent("public/p1/index.html", "p1-content-foo")
}
-133
View File
@@ -200,136 +200,3 @@ Myshort Original.
b.Build()
b.AssertFileContent("public/p1/index.html", "Edited")
}
func TestRenderShortcodesEditSectionContentWithShortcodeInIncludedPageIssue12458(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableLiveReload = true
disableKinds = ["home", "taxonomy", "term", "rss", "sitemap", "robotsTXT", "404"]
-- content/mysection/_index.md --
---
title: "My Section"
---
## p1-h1
{{% include "p2" %}}
-- content/mysection/p2.md --
---
title: "p2"
---
### Original
{{% myshort %}}
-- layouts/shortcodes/include.html --
{{ $p := .Page.GetPage (.Get 0) }}
{{ $p.RenderShortcodes }}
-- layouts/shortcodes/myshort.html --
Myshort Original.
-- layouts/_default/list.html --
{{ .Content }}
`
b := TestRunning(t, files)
b.AssertFileContent("public/mysection/index.html", "p1-h1")
b.EditFileReplaceAll("content/mysection/_index.md", "p1-h1", "p1-h1 Edited").Build()
b.AssertFileContent("public/mysection/index.html", "p1-h1 Edited")
}
func TestRenderShortcodesNestedPageContextIssue12356(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss", "sitemap", "robotsTXT", "404"]
-- layouts/_default/_markup/render-image.html --
{{- with .PageInner.Resources.Get .Destination -}}Image: {{ .RelPermalink }}|{{- end -}}
-- layouts/_default/_markup/render-link.html --
{{- with .PageInner.GetPage .Destination -}}Link: {{ .RelPermalink }}|{{- end -}}
-- layouts/_default/_markup/render-heading.html --
Heading: {{ .PageInner.Title }}: {{ .PlainText }}|
-- layouts/_default/_markup/render-codeblock.html --
CodeBlock: {{ .PageInner.Title }}: {{ .Type }}|
-- layouts/_default/list.html --
Content:{{ .Content }}|
Fragments: {{ with .Fragments }}{{.Identifiers }}{{ end }}|
-- layouts/_default/single.html --
Content:{{ .Content }}|
-- layouts/shortcodes/include.html --
{{ with site.GetPage (.Get 0) }}
{{ .RenderShortcodes }}
{{ end }}
-- content/markdown/_index.md --
---
title: "Markdown"
---
# H1
|{{% include "/posts/p1" %}}|
![kitten](pixel3.png "Pixel 3")
§§§go
fmt.Println("Hello")
§§§
-- content/markdown2/_index.md --
---
title: "Markdown 2"
---
|{{< include "/posts/p1" >}}|
-- content/html/_index.html --
---
title: "HTML"
---
|{{% include "/posts/p1" %}}|
-- content/posts/p1/index.md --
---
title: "p1"
---
## H2-p1
![kitten](pixel1.png "Pixel 1")
![kitten](pixel2.png "Pixel 2")
[p2](p2)
§§§bash
echo "Hello"
§§§
-- content/posts/p2/index.md --
---
title: "p2"
---
-- content/posts/p1/pixel1.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- content/posts/p1/pixel2.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- content/markdown/pixel3.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- content/html/pixel4.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
`
b := Test(t, files)
b.AssertFileContent("public/markdown/index.html",
// Images.
"Image: /posts/p1/pixel1.png|\nImage: /posts/p1/pixel2.png|\n|\nImage: /markdown/pixel3.png|</p>\n|",
// Links.
"Link: /posts/p2/|",
// Code blocks
"CodeBlock: p1: bash|", "CodeBlock: Markdown: go|",
// Headings.
"Heading: Markdown: H1|", "Heading: p1: H2-p1|",
// Fragments.
"Fragments: [h1 h2-p1]|",
// Check that the special context markup is not rendered.
"! hugo_ctx",
)
b.AssertFileContent("public/markdown2/index.html", "! hugo_ctx", "Content:<p>|\n ![kitten](pixel1.png \"Pixel 1\")\n![kitten](pixel2.png \"Pixel 2\")\n|</p>\n|")
b.AssertFileContent("public/html/index.html", "! hugo_ctx")
}
-6
View File
@@ -321,16 +321,10 @@ func prepareShortcode(
// Allow the caller to delay the rendering of the shortcode if needed.
var fn shortcodeRenderFunc = func(ctx context.Context) ([]byte, bool, error) {
if p.m.pageConfig.IsGoldmark && sc.doMarkup {
// Signal downwards that the content rendered will be
// parsed and rendered by Goldmark.
ctx = tpl.Context.IsInGoldmark.Set(ctx, true)
}
r, err := doRenderShortcode(ctx, level, s, tplVariants, sc, parent, p, isRenderString)
if err != nil {
return nil, false, toParseErr(err)
}
b, hasVariants, err := r.renderShortcode(ctx)
if err != nil {
return nil, false, toParseErr(err)
+32 -36
View File
@@ -424,35 +424,7 @@ func (h *HugoSites) fileEventsFilter(events []fsnotify.Event) []fsnotify.Event {
events[n] = ev
n++
}
events = events[:n]
eventOrdinal := func(e fsnotify.Event) int {
// Pull the structural changes to the top.
if e.Op.Has(fsnotify.Create) {
return 1
}
if e.Op.Has(fsnotify.Remove) {
return 2
}
if e.Op.Has(fsnotify.Rename) {
return 3
}
if e.Op.Has(fsnotify.Write) {
return 4
}
return 5
}
sort.Slice(events, func(i, j int) bool {
// First sort by event type.
if eventOrdinal(events[i]) != eventOrdinal(events[j]) {
return eventOrdinal(events[i]) < eventOrdinal(events[j])
}
// Then sort by name.
return events[i].Name < events[j].Name
})
return events
return events[:n]
}
type fileEventInfo struct {
@@ -522,17 +494,41 @@ func (h *HugoSites) fileEventsApplyInfo(events []fsnotify.Event) []fileEventInfo
return infos
}
func (h *HugoSites) fileEventsTrim(events []fsnotify.Event) []fsnotify.Event {
seen := make(map[string]bool)
func (h *HugoSites) fileEventsTranslate(events []fsnotify.Event) []fsnotify.Event {
eventMap := make(map[string][]fsnotify.Event)
// We often get a Remove etc. followed by a Create, a Create followed by a Write.
// Remove the superfluous events to make the update logic simpler.
for _, ev := range events {
eventMap[ev.Name] = append(eventMap[ev.Name], ev)
}
n := 0
for _, ev := range events {
if seen[ev.Name] {
continue
mapped := eventMap[ev.Name]
// Keep one
found := false
var kept fsnotify.Event
for i, ev2 := range mapped {
if i == 0 {
kept = ev2
}
if ev2.Op&fsnotify.Write == fsnotify.Write {
kept = ev2
found = true
}
if !found && ev2.Op&fsnotify.Create == fsnotify.Create {
kept = ev2
}
}
seen[ev.Name] = true
events[n] = ev
events[n] = kept
n++
}
return events
}
@@ -659,7 +655,7 @@ func (s *Site) assembleMenus() error {
if sectionPagesMenu != "" {
if err := s.pageMap.forEachPage(pagePredicates.ShouldListGlobal, func(p *pageState) (bool, error) {
if p.Kind() != kinds.KindSection || !p.m.shouldBeCheckedForMenuDefinitions() {
if p.IsHome() || !p.m.shouldBeCheckedForMenuDefinitions() {
return false, nil
}
+2 -2
View File
@@ -123,14 +123,14 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
HandlerPost: logHookLast,
Stdout: cfg.LogOut,
Stderr: cfg.LogOut,
StoreErrors: conf.Watching(),
StoreErrors: conf.Running(),
SuppressStatements: conf.IgnoredLogs(),
}
logger = loggers.New(logOpts)
}
memCache := dynacache.New(dynacache.Options{Watching: conf.Watching(), Log: logger})
memCache := dynacache.New(dynacache.Options{Running: conf.Running(), Log: logger})
firstSiteDeps := &deps.Deps{
Fs: cfg.Fs,
+2 -8
View File
@@ -1,13 +1,7 @@
# Release env.
# These will be replaced by script before release.
HUGORELEASER_TAG=v0.125.7
HUGORELEASER_COMMITISH=b1d808bc373f53ad37c8966bb02a6aea095db5f8
HUGORELEASER_TAG=v0.124.1
HUGORELEASER_COMMITISH=db083b05f16c945fec04f745f0ca8640560cf1ec
+4 -5
View File
@@ -135,11 +135,10 @@ func (b Bytes) Bytes() []byte {
// DocumentContext holds contextual information about the document to convert.
type DocumentContext struct {
Document any // May be nil. Usually a page.Page
DocumentLookup func(uint64) any // May be nil.
DocumentID string
DocumentName string
Filename string
Document any // May be nil. Usually a page.Page
DocumentID string
DocumentName string
Filename string
}
// RenderContext holds contextual information about the content to render.
+7 -13
View File
@@ -32,7 +32,8 @@ type AttributesProvider interface {
// LinkContext is the context passed to a link render hook.
type LinkContext interface {
PageProvider
// The Page being rendered.
Page() any
// The link URL.
Destination() string
@@ -63,7 +64,6 @@ type ImageLinkContext interface {
type CodeblockContext interface {
AttributesProvider
text.Positioner
PageProvider
// Chroma highlighting processing options. This will only be filled if Type is a known Chroma Lexer.
Options() map[string]any
@@ -76,6 +76,9 @@ type CodeblockContext interface {
// Zero-based ordinal for all code blocks in the current document.
Ordinal() int
// The owning Page.
Page() any
}
type AttributesOptionsSliceProvider interface {
@@ -98,7 +101,8 @@ type IsDefaultCodeBlockRendererProvider interface {
// HeadingContext contains accessors to all attributes that a HeadingRenderer
// can use to render a heading.
type HeadingContext interface {
PageProvider
// Page is the page containing the heading.
Page() any
// Level is the level of the header (i.e. 1 for top-level, 2 for sub-level, etc.).
Level() int
// Anchor is the HTML id assigned to the heading.
@@ -112,16 +116,6 @@ type HeadingContext interface {
AttributesProvider
}
type PageProvider interface {
// Page is the page being rendered.
Page() any
// PageInner may be different than Page when .RenderShortcodes is in play.
// The main use case for this is to include other pages' markdown into the current page
// but resolve resources and pages relative to the original.
PageInner() any
}
// HeadingRenderer describes a uniquely identifiable rendering hook.
type HeadingRenderer interface {
// RenderHeading writes the rendered content to w using the data in w.
+5 -22
View File
@@ -108,7 +108,6 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
}
cbctx := &codeBlockContext{
page: ctx.DocumentContext().Document,
pageInner: r.getPageInner(ctx),
lang: lang,
code: s,
ordinal: ordinal,
@@ -133,6 +132,7 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
w,
cbctx,
)
if err != nil {
return ast.WalkContinue, herrors.NewFileErrorFromPos(err, cbctx.createPos())
}
@@ -140,24 +140,11 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
return ast.WalkContinue, nil
}
func (r *htmlRenderer) getPageInner(rctx *render.Context) any {
pid := rctx.PeekPid()
if pid > 0 {
if lookup := rctx.DocumentContext().DocumentLookup; lookup != nil {
if v := rctx.DocumentContext().DocumentLookup(pid); v != nil {
return v
}
}
}
return rctx.DocumentContext().Document
}
type codeBlockContext struct {
page any
pageInner any
lang string
code string
ordinal int
page any
lang string
code string
ordinal int
// This is only used in error situations and is expensive to create,
// to delay creation until needed.
@@ -172,10 +159,6 @@ func (c *codeBlockContext) Page() any {
return c.page
}
func (c *codeBlockContext) PageInner() any {
return c.pageInner
}
func (c *codeBlockContext) Type() string {
return c.lang
}
-2
View File
@@ -18,7 +18,6 @@ import (
"bytes"
"github.com/gohugoio/hugo-goldmark-extensions/passthrough"
"github.com/gohugoio/hugo/markup/goldmark/hugocontext"
"github.com/yuin/goldmark/util"
"github.com/gohugoio/hugo/markup/goldmark/codeblocks"
@@ -104,7 +103,6 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
renderer.WithNodeRenderers(util.Prioritized(emoji.NewHTMLRenderer(), 200)))
var (
extensions = []goldmark.Extender{
hugocontext.New(),
newLinks(cfg),
newTocExtension(tocRendererOptions),
}
-165
View File
@@ -1,165 +0,0 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugocontext
import (
"bytes"
"fmt"
"strconv"
"github.com/gohugoio/hugo/bufferpool"
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
func New() goldmark.Extender {
return &hugoContextExtension{}
}
// Wrap wraps the given byte slice in a Hugo context that used to determine the correct Page
// in .RenderShortcodes.
func Wrap(b []byte, pid uint64) string {
buf := bufferpool.GetBuffer()
defer bufferpool.PutBuffer(buf)
buf.Write(prefix)
buf.WriteString(" pid=")
buf.WriteString(strconv.FormatUint(pid, 10))
buf.Write(endDelim)
buf.WriteByte('\n')
buf.Write(b)
buf.Write(prefix)
buf.Write(closingDelimAndNewline)
return buf.String()
}
var kindHugoContext = ast.NewNodeKind("HugoContext")
// HugoContext is a node that represents a Hugo context.
type HugoContext struct {
ast.BaseInline
Closing bool
// Internal page ID. Not persisted.
Pid uint64
}
// Dump implements Node.Dump.
func (n *HugoContext) Dump(source []byte, level int) {
m := map[string]string{}
m["Pid"] = fmt.Sprintf("%v", n.Pid)
ast.DumpHelper(n, source, level, m, nil)
}
func (n *HugoContext) parseAttrs(attrBytes []byte) {
keyPairs := bytes.Split(attrBytes, []byte(" "))
for _, keyPair := range keyPairs {
kv := bytes.Split(keyPair, []byte("="))
if len(kv) != 2 {
continue
}
key := string(kv[0])
val := string(kv[1])
switch key {
case "pid":
pid, _ := strconv.ParseUint(val, 10, 64)
n.Pid = pid
}
}
}
func (h *HugoContext) Kind() ast.NodeKind {
return kindHugoContext
}
var (
prefix = []byte("{{__hugo_ctx")
endDelim = []byte("}}")
closingDelimAndNewline = []byte("/}}\n")
)
var _ parser.InlineParser = (*hugoContextParser)(nil)
type hugoContextParser struct{}
func (s *hugoContextParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
line, _ := block.PeekLine()
if !bytes.HasPrefix(line, prefix) {
return nil
}
end := bytes.Index(line, endDelim)
if end == -1 {
return nil
}
block.Advance(end + len(endDelim) + 1) // +1 for the newline
if line[end-1] == '/' {
return &HugoContext{Closing: true}
}
attrBytes := line[len(prefix)+1 : end]
h := &HugoContext{}
h.parseAttrs(attrBytes)
return h
}
func (a *hugoContextParser) Trigger() []byte {
return []byte{'{'}
}
type hugoContextRenderer struct{}
func (r *hugoContextRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(kindHugoContext, r.handleHugoContext)
}
func (r *hugoContextRenderer) handleHugoContext(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
hctx := node.(*HugoContext)
ctx, ok := w.(*render.Context)
if !ok {
return ast.WalkContinue, nil
}
if hctx.Closing {
_ = ctx.PopPid()
} else {
ctx.PushPid(hctx.Pid)
}
return ast.WalkContinue, nil
}
type hugoContextExtension struct{}
func (a *hugoContextExtension) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(
parser.WithInlineParsers(
util.Prioritized(&hugoContextParser{}, 50),
),
)
m.Renderer().AddOptions(
renderer.WithNodeRenderers(
util.Prioritized(&hugoContextRenderer{}, 50),
),
)
}
@@ -1,34 +0,0 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugocontext
import (
"testing"
qt "github.com/frankban/quicktest"
)
func TestWrap(t *testing.T) {
c := qt.New(t)
b := []byte("test")
c.Assert(Wrap(b, 42), qt.Equals, "{{__hugo_ctx pid=42}}\ntest{{__hugo_ctx/}}\n")
}
func BenchmarkWrap(b *testing.B) {
for i := 0; i < b.N; i++ {
Wrap([]byte("test"), 42)
}
}
@@ -41,7 +41,6 @@ func (b *BufWriter) Flush() error {
type Context struct {
*BufWriter
positions []int
pids []uint64
ContextData
}
@@ -56,30 +55,6 @@ func (ctx *Context) PopPos() int {
return p
}
// PushPid pushes a new page ID to the stack.
func (ctx *Context) PushPid(pid uint64) {
ctx.pids = append(ctx.pids, pid)
}
// PeekPid returns the current page ID without removing it from the stack.
func (ctx *Context) PeekPid() uint64 {
if len(ctx.pids) == 0 {
return 0
}
return ctx.pids[len(ctx.pids)-1]
}
// PopPid pops the last page ID from the stack.
func (ctx *Context) PopPid() uint64 {
if len(ctx.pids) == 0 {
return 0
}
i := len(ctx.pids) - 1
p := ctx.pids[i]
ctx.pids = ctx.pids[:i]
return p
}
type ContextData interface {
RenderContext() converter.RenderContext
DocumentContext() converter.DocumentContext
-26
View File
@@ -49,7 +49,6 @@ func newLinks(cfg goldmark_config.Config) goldmark.Extender {
type linkContext struct {
page any
pageInner any
destination string
title string
text hstring.RenderedString
@@ -65,10 +64,6 @@ func (ctx linkContext) Page() any {
return ctx.page
}
func (ctx linkContext) PageInner() any {
return ctx.pageInner
}
func (ctx linkContext) Text() hstring.RenderedString {
return ctx.text
}
@@ -97,7 +92,6 @@ func (ctx imageLinkContext) Ordinal() int {
type headingContext struct {
page any
pageInner any
level int
anchor string
text hstring.RenderedString
@@ -109,10 +103,6 @@ func (ctx headingContext) Page() any {
return ctx.page
}
func (ctx headingContext) PageInner() any {
return ctx.pageInner
}
func (ctx headingContext) Level() int {
return ctx.level
}
@@ -196,7 +186,6 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
imageLinkContext{
linkContext: linkContext{
page: ctx.DocumentContext().Document,
pageInner: r.getPageInner(ctx),
destination: string(n.Destination),
title: string(n.Title),
text: hstring.RenderedString(text),
@@ -211,18 +200,6 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
return ast.WalkContinue, err
}
func (r *hookedRenderer) getPageInner(rctx *render.Context) any {
pid := rctx.PeekPid()
if pid > 0 {
if lookup := rctx.DocumentContext().DocumentLookup; lookup != nil {
if v := rctx.DocumentContext().DocumentLookup(pid); v != nil {
return v
}
}
}
return rctx.DocumentContext().Document
}
func (r *hookedRenderer) filterInternalAttributes(attrs []ast.Attribute) []ast.Attribute {
n := 0
for _, x := range attrs {
@@ -297,7 +274,6 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No
w,
linkContext{
page: ctx.DocumentContext().Document,
pageInner: r.getPageInner(ctx),
destination: string(n.Destination),
title: string(n.Title),
text: hstring.RenderedString(text),
@@ -363,7 +339,6 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
w,
linkContext{
page: ctx.DocumentContext().Document,
pageInner: r.getPageInner(ctx),
destination: url,
text: hstring.RenderedString(label),
plainText: label,
@@ -448,7 +423,6 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast
w,
headingContext{
page: ctx.DocumentContext().Document,
pageInner: r.getPageInner(ctx),
level: n.Level,
anchor: string(anchor),
text: hstring.RenderedString(text),
+1 -4
View File
@@ -261,10 +261,7 @@ func (c *collector) add(owner *moduleAdapter, moduleImport Import) (*moduleAdapt
// This will select the latest release-version (not beta etc.).
versionQuery = "upgrade"
}
// Note that we cannot use c.Get for this, as that may
// trigger a new module collection and potentially create a infinite loop.
if err := c.get(fmt.Sprintf("%s@%s", modulePath, versionQuery)); err != nil {
if err := c.Get(fmt.Sprintf("%s@%s", modulePath, versionQuery)); err != nil {
return nil, err
}
if err := c.loadModules(); err != nil {
+1 -1
View File
@@ -128,7 +128,7 @@ func (e *errorResource) Exif() *exif.ExifInfo {
panic(e.ResourceError)
}
func (e *errorResource) Colors() ([]images.Color, error) {
func (e *errorResource) Colors() ([]string, error) {
panic(e.ResourceError)
}
+3 -3
View File
@@ -67,7 +67,7 @@ type imageResource struct {
meta *imageMeta
dominantColorInit sync.Once
dominantColors []images.Color
dominantColors []string
baseResource
}
@@ -143,7 +143,7 @@ func (i *imageResource) getExif() *exif.ExifInfo {
// Colors returns a slice of the most dominant colors in an image
// using a simple histogram method.
func (i *imageResource) Colors() ([]images.Color, error) {
func (i *imageResource) Colors() ([]string, error) {
var err error
i.dominantColorInit.Do(func() {
var img image.Image
@@ -153,7 +153,7 @@ func (i *imageResource) Colors() ([]images.Color, error) {
}
colors := color_extractor.ExtractColors(img)
for _, c := range colors {
i.dominantColors = append(i.dominantColors, images.ColorGoToColor(c))
i.dominantColors = append(i.dominantColors, images.ColorToHexString(c))
}
})
return i.dominantColors, nil
+2 -27
View File
@@ -85,16 +85,9 @@ func TestImageTransformBasic(t *testing.T) {
assertWidthHeight(c, img, w, h)
}
gotColors, err := image.Colors()
colors, err := image.Colors()
c.Assert(err, qt.IsNil)
expectedColors := images.HexStringsToColors("#2d2f33", "#a49e93", "#d39e59", "#a76936", "#737a84", "#7c838b")
c.Assert(len(gotColors), qt.Equals, len(expectedColors))
for i := range gotColors {
c1, c2 := gotColors[i], expectedColors[i]
c.Assert(c1.ColorHex(), qt.Equals, c2.ColorHex())
c.Assert(c1.ColorGo(), qt.DeepEquals, c2.ColorGo())
c.Assert(c1.Luminance(), qt.Equals, c2.Luminance())
}
c.Assert(colors, qt.DeepEquals, []string{"#2d2f33", "#a49e93", "#d39e59", "#a76936", "#737a84", "#7c838b"})
c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.jpg")
c.Assert(image.ResourceType(), qt.Equals, "image")
@@ -452,24 +445,6 @@ func TestImageExif(t *testing.T) {
getAndCheckExif(c, image)
}
func TestImageColorsLuminance(t *testing.T) {
c := qt.New(t)
_, image := fetchSunset(c)
c.Assert(image, qt.Not(qt.IsNil))
colors, err := image.Colors()
c.Assert(err, qt.IsNil)
c.Assert(len(colors), qt.Equals, 6)
var prevLuminance float64
for i, color := range colors {
luminance := color.Luminance()
c.Assert(err, qt.IsNil)
c.Assert(luminance > 0, qt.IsTrue)
c.Assert(luminance, qt.Not(qt.Equals), prevLuminance, qt.Commentf("i=%d", i))
prevLuminance = luminance
}
}
func BenchmarkImageExif(b *testing.B) {
getImages := func(c *qt.C, b *testing.B, fs afero.Fs) []images.ImageResource {
spec := newTestResourceSpec(specDescriptor{fs: fs, c: c})
+4 -116
View File
@@ -16,76 +16,10 @@ package images
import (
"encoding/hex"
"fmt"
"hash/fnv"
"image/color"
"math"
"strings"
"github.com/gohugoio/hugo/common/hstrings"
)
type colorGoProvider interface {
ColorGo() color.Color
}
type Color struct {
// The color.
color color.Color
// The color prefixed with a #.
hex string
// The relative luminance of the color.
luminance float64
}
// Luminance as defined by w3.org.
// See https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
func (c Color) Luminance() float64 {
return c.luminance
}
// ColorGo returns the color as a color.Color.
// For internal use only.
func (c Color) ColorGo() color.Color {
return c.color
}
// ColorHex returns the color as a hex string prefixed with a #.
func (c Color) ColorHex() string {
return c.hex
}
// String returns the color as a hex string prefixed with a #.
func (c Color) String() string {
return c.hex
}
// For hashstructure. This struct is used in template func options
// that needs to be able to hash a Color.
// For internal use only.
func (c Color) Hash() (uint64, error) {
h := fnv.New64a()
h.Write([]byte(c.hex))
return h.Sum64(), nil
}
func (c *Color) init() error {
c.hex = ColorGoToHexString(c.color)
r, g, b, _ := c.color.RGBA()
c.luminance = 0.2126*c.toSRGB(uint8(r)) + 0.7152*c.toSRGB(uint8(g)) + 0.0722*c.toSRGB(uint8(b))
return nil
}
func (c Color) toSRGB(i uint8) float64 {
v := float64(i) / 255
if v <= 0.04045 {
return v / 12.92
} else {
return math.Pow((v+0.055)/1.055, 2.4)
}
}
// AddColorToPalette adds c as the first color in p if not already there.
// Note that it does no additional checks, so callers must make sure
// that the palette is valid for the relevant format.
@@ -111,60 +45,14 @@ func ReplaceColorInPalette(c color.Color, p color.Palette) {
p[p.Index(c)] = c
}
// ColorGoToHexString converts a color.Color to a hex string.
func ColorGoToHexString(c color.Color) string {
// ColorToHexString converts a color to a hex string.
func ColorToHexString(c color.Color) string {
r, g, b, a := c.RGBA()
rgba := color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
if rgba.A == 0xff {
return fmt.Sprintf("#%.2x%.2x%.2x", rgba.R, rgba.G, rgba.B)
}
return fmt.Sprintf("#%.2x%.2x%.2x%.2x", rgba.R, rgba.G, rgba.B, rgba.A)
return fmt.Sprintf("#%.2x%.2x%.2x", rgba.R, rgba.G, rgba.B)
}
// ColorGoToColor converts a color.Color to a Color.
func ColorGoToColor(c color.Color) Color {
cc := Color{color: c}
if err := cc.init(); err != nil {
panic(err)
}
return cc
}
func hexStringToColor(s string) Color {
c, err := hexStringToColorGo(s)
if err != nil {
panic(err)
}
return ColorGoToColor(c)
}
// HexStringsToColors converts a slice of hex strings to a slice of Colors.
func HexStringsToColors(s ...string) []Color {
var colors []Color
for _, v := range s {
colors = append(colors, hexStringToColor(v))
}
return colors
}
func toColorGo(v any) (color.Color, bool, error) {
switch vv := v.(type) {
case colorGoProvider:
return vv.ColorGo(), true, nil
default:
s, ok := hstrings.ToString(v)
if !ok {
return nil, false, nil
}
c, err := hexStringToColorGo(s)
if err != nil {
return nil, false, err
}
return c, true, nil
}
}
func hexStringToColorGo(s string) (color.Color, error) {
func hexStringToColor(s string) (color.Color, error) {
s = strings.TrimPrefix(s, "#")
if len(s) != 3 && len(s) != 4 && len(s) != 6 && len(s) != 8 {
+6 -20
View File
@@ -18,7 +18,6 @@ import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/htesting/hqt"
)
func TestHexStringToColor(t *testing.T) {
@@ -47,7 +46,7 @@ func TestHexStringToColor(t *testing.T) {
c.Run(test.arg, func(c *qt.C) {
c.Parallel()
result, err := hexStringToColorGo(test.arg)
result, err := hexStringToColor(test.arg)
if b, ok := test.expect.(bool); ok && !b {
c.Assert(err, qt.Not(qt.IsNil))
@@ -71,18 +70,13 @@ func TestColorToHexString(t *testing.T) {
{color.White, "#ffffff"},
{color.Black, "#000000"},
{color.RGBA{R: 0x42, G: 0x87, B: 0xf5, A: 0xff}, "#4287f5"},
// 50% opacity.
// Note that the .Colors (dominant colors) received from the Image resource
// will always have an alpha value of 0xff.
{color.RGBA{R: 0x42, G: 0x87, B: 0xf5, A: 0x80}, "#4287f580"},
} {
test := test
c.Run(test.expect, func(c *qt.C) {
c.Parallel()
result := ColorGoToHexString(test.arg)
result := ColorToHexString(test.arg)
c.Assert(result, qt.Equals, test.expect)
})
@@ -97,9 +91,9 @@ func TestAddColorToPalette(t *testing.T) {
c.Assert(AddColorToPalette(color.White, palette), qt.HasLen, 2)
blue1, _ := hexStringToColorGo("34c3eb")
blue2, _ := hexStringToColorGo("34c3eb")
white, _ := hexStringToColorGo("fff")
blue1, _ := hexStringToColor("34c3eb")
blue2, _ := hexStringToColor("34c3eb")
white, _ := hexStringToColor("fff")
c.Assert(AddColorToPalette(white, palette), qt.HasLen, 2)
c.Assert(AddColorToPalette(blue1, palette), qt.HasLen, 3)
@@ -110,18 +104,10 @@ func TestReplaceColorInPalette(t *testing.T) {
c := qt.New(t)
palette := color.Palette{color.White, color.Black}
offWhite, _ := hexStringToColorGo("fcfcfc")
offWhite, _ := hexStringToColor("fcfcfc")
ReplaceColorInPalette(offWhite, palette)
c.Assert(palette, qt.HasLen, 2)
c.Assert(palette[0], qt.Equals, offWhite)
}
func TestColorLuminance(t *testing.T) {
c := qt.New(t)
c.Assert(hexStringToColor("#000000").Luminance(), hqt.IsSameFloat64, 0.0)
c.Assert(hexStringToColor("#768a9a").Luminance(), hqt.IsSameFloat64, 0.24361603589088263)
c.Assert(hexStringToColor("#d5bc9f").Luminance(), hqt.IsSameFloat64, 0.5261577672685374)
c.Assert(hexStringToColor("#ffffff").Luminance(), hqt.IsSameFloat64, 1.0)
}
+3 -3
View File
@@ -171,7 +171,7 @@ func DecodeConfig(in map[string]any) (*config.ConfigNamespace[ImagingConfig, Ima
return i, nil, err
}
i.BgColor, err = hexStringToColorGo(i.Imaging.BgColor)
i.BgColor, err = hexStringToColor(i.Imaging.BgColor)
if err != nil {
return i, nil, err
}
@@ -230,7 +230,7 @@ func DecodeImageConfig(action string, options []string, defaults *config.ConfigN
c.Hint = hint
} else if part[0] == '#' {
c.BgColorStr = part[1:]
c.BgColor, err = hexStringToColorGo(c.BgColorStr)
c.BgColor, err = hexStringToColor(c.BgColorStr)
if err != nil {
return c, err
}
@@ -424,7 +424,7 @@ type ImagingConfigInternal struct {
func (i *ImagingConfigInternal) Compile(externalCfg *ImagingConfig) error {
var err error
i.BgColor, err = hexStringToColorGo(externalCfg.BgColor)
i.BgColor, err = hexStringToColor(externalCfg.BgColor)
if err != nil {
return err
}
+1 -1
View File
@@ -132,7 +132,7 @@ func newImageConfig(action string, width, height, quality, rotate int, filter, a
c.qualitySetForImage = quality != 75
c.Rotate = rotate
c.BgColorStr = bgColor
c.BgColor, _ = hexStringToColorGo(bgColor)
c.BgColor, _ = hexStringToColor(bgColor)
if filter != "" {
filter = strings.ToLower(filter)
+10 -14
View File
@@ -1,4 +1,4 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -65,7 +65,7 @@ func (*Filters) Opacity(opacity any) gift.Filter {
func (*Filters) Text(text string, options ...any) gift.Filter {
tf := textFilter{
text: text,
color: color.White,
color: "#ffffff",
size: 20,
x: 10,
y: 10,
@@ -78,9 +78,7 @@ func (*Filters) Text(text string, options ...any) gift.Filter {
for option, v := range opt {
switch option {
case "color":
if color, ok, _ := toColorGo(v); ok {
tf.color = color
}
tf.color = cast.ToString(v)
case "size":
tf.size = cast.ToFloat64(v)
case "x":
@@ -130,14 +128,15 @@ func (*Filters) Padding(args ...any) gift.Filter {
var top, right, bottom, left int
var ccolor color.Color = color.White // canvas color
var err error
_args := args // preserve original args for most stable hash
if vcs, ok, err := toColorGo(args[len(args)-1]); ok || err != nil {
if vcs, ok := (args[len(args)-1]).(string); ok {
ccolor, err = hexStringToColor(vcs)
if err != nil {
panic("invalid canvas color: specify RGB or RGBA using hex notation")
}
ccolor = vcs
args = args[:len(args)-1]
if len(args) == 0 {
panic("not enough arguments: provide one or more padding values using the CSS shorthand property syntax")
@@ -181,11 +180,12 @@ func (*Filters) Padding(args ...any) gift.Filter {
// Dither creates a filter that dithers an image.
func (*Filters) Dither(options ...any) gift.Filter {
ditherOptions := struct {
Colors []any
Colors []string
Method string
Serpentine bool
Strength float32
}{
Colors: []string{"000000ff", "ffffffff"},
Method: "floydsteinberg",
Serpentine: true,
Strength: 1.0,
@@ -198,18 +198,14 @@ func (*Filters) Dither(options ...any) gift.Filter {
}
}
if len(ditherOptions.Colors) == 0 {
ditherOptions.Colors = []any{"000000ff", "ffffffff"}
}
if len(ditherOptions.Colors) < 2 {
panic("palette must have at least two colors")
}
var palette []color.Color
for _, c := range ditherOptions.Colors {
cc, ok, err := toColorGo(c)
if !ok || err != nil {
cc, err := hexStringToColor(c)
if err != nil {
panic(fmt.Sprintf("%q is an invalid color: specify RGB or RGBA using hexadecimal notation", c))
}
palette = append(palette, cc)
+1 -1
View File
@@ -63,7 +63,7 @@ type ImageResourceOps interface {
// Colors returns a slice of the most dominant colors in an image
// using a simple histogram method.
Colors() ([]Color, error)
Colors() ([]string, error)
// For internal use.
DecodeImage() (image.Image, error)
+7 -4
View File
@@ -15,7 +15,6 @@ package images
import (
"image"
"image/color"
"image/draw"
"io"
"strings"
@@ -32,8 +31,7 @@ import (
var _ gift.Filter = (*textFilter)(nil)
type textFilter struct {
text string
color color.Color
text, color string
x, y int
size float64
linespacing int
@@ -41,6 +39,11 @@ type textFilter struct {
}
func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options) {
color, err := hexStringToColor(f.color)
if err != nil {
panic(err)
}
// Load and parse font
ttf := goregular.TTF
if f.fontSource != nil {
@@ -71,7 +74,7 @@ func (f textFilter) Draw(dst draw.Image, src image.Image, options *gift.Options)
d := font.Drawer{
Dst: dst,
Src: image.NewUniform(f.color),
Src: image.NewUniform(color),
Face: face,
}
+3 -7
View File
@@ -225,6 +225,9 @@ type PageMetaProvider interface {
// to the source of this Page. It will be relative to any content root.
Path() string
// This is for internal use only.
PathInfo() *paths.Path
// The slug, typically defined in front matter.
Slug() string
@@ -250,12 +253,6 @@ type PageMetaProvider interface {
Weight() int
}
// PageMetaInternalProvider provides internal page metadata.
type PageMetaInternalProvider interface {
// This is for internal use only.
PathInfo() *paths.Path
}
// PageRenderProvider provides a way for a Page to render content.
type PageRenderProvider interface {
// Render renders the given layout with this Page as context.
@@ -276,7 +273,6 @@ type PageWithoutContent interface {
RenderShortcodesProvider
resource.Resource
PageMetaProvider
PageMetaInternalProvider
resource.LanguageProvider
// For pages backed by a file.
+5 -1
View File
@@ -17,8 +17,9 @@ package page
import (
"encoding/json"
"github.com/gohugoio/hugo/config"
"time"
"github.com/gohugoio/hugo/config"
)
func MarshalPageToJSON(p Page) ([]byte, error) {
@@ -38,6 +39,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
isNode := p.IsNode()
isPage := p.IsPage()
path := p.Path()
pathc := p.Path()
slug := p.Slug()
lang := p.Lang()
isSection := p.IsSection()
@@ -63,6 +65,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
IsNode bool
IsPage bool
Path string
Pathc string
Slug string
Lang string
IsSection bool
@@ -87,6 +90,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
IsNode: isNode,
IsPage: isPage,
Path: path,
Pathc: pathc,
Slug: slug,
Lang: lang,
IsSection: isSection,
@@ -88,9 +88,6 @@ type PageConfig struct {
// User defined params.
Params maps.Params
// Compiled values.
IsGoldmark bool `json:"-"`
}
// FrontMatterHandler maps front matter into Page fields and .Params.
+7 -10
View File
@@ -296,19 +296,16 @@ type hashProvider interface {
hash() string
}
var _ resource.StaleInfo = (*StaleValue[any])(nil)
type StaleValue[V any] struct {
// The value.
Value V
// StaleVersionFunc reports the current version of the value.
// This always starts out at 0 and get incremented on staleness.
StaleVersionFunc func() uint32
// IsStaleFunc reports whether the value is stale.
IsStaleFunc func() bool
}
func (s *StaleValue[V]) StaleVersion() uint32 {
return s.StaleVersionFunc()
func (s *StaleValue[V]) IsStale() bool {
return s.IsStaleFunc()
}
type AtomicStaler struct {
@@ -316,11 +313,11 @@ type AtomicStaler struct {
}
func (s *AtomicStaler) MarkStale() {
atomic.AddUint32(&s.stale, 1)
atomic.StoreUint32(&s.stale, 1)
}
func (s *AtomicStaler) StaleVersion() uint32 {
return atomic.LoadUint32(&(s.stale))
func (s *AtomicStaler) IsStale() bool {
return atomic.LoadUint32(&(s.stale)) > 0
}
// For internal use.
+7 -17
View File
@@ -233,27 +233,17 @@ type StaleMarker interface {
// StaleInfo tells if a resource is marked as stale.
type StaleInfo interface {
StaleVersion() uint32
IsStale() bool
}
// StaleVersion returns the StaleVersion for the given os,
// or 0 if not set.
func StaleVersion(os any) uint32 {
if s, ok := os.(StaleInfo); ok {
return s.StaleVersion()
}
return 0
}
// StaleVersionSum calculates the sum of the StaleVersionSum for the given oss.
func StaleVersionSum(oss ...any) uint32 {
var version uint32
for _, o := range oss {
if s, ok := o.(StaleInfo); ok && s.StaleVersion() > 0 {
version += s.StaleVersion()
// IsStaleAny reports whether any of the os is marked as stale.
func IsStaleAny(os ...any) bool {
for _, o := range os {
if s, ok := o.(StaleInfo); ok && s.IsStale() {
return true
}
}
return version
return false
}
// MarkStale will mark any of the oses as stale, if possible.
@@ -1,4 +1,4 @@
// Copyright 2024 The Hugo Authors. All rights reserved.
// Copyright 2021 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -327,93 +327,3 @@ Styles: {{ $r.RelPermalink }}
b.AssertFileContent("public/index.html", "Styles: /scss/main.css")
}
// Issue #1239.
func TestRebuildAssetGetMatch(t *testing.T) {
t.Parallel()
if !scss.Supports() {
t.Skip()
}
files := `
-- assets/scss/main.scss --
b {
color: red;
}
-- layouts/index.html --
{{ $r := resources.GetMatch "scss/main.scss" | toCSS }}
T1: {{ $r.Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: true,
Running: true,
}).Build()
b.AssertFileContent("public/index.html", `color: red`)
b.EditFiles("assets/scss/main.scss", `b { color: blue; }`).Build()
b.AssertFileContent("public/index.html", `color: blue`)
}
func TestRebuildAssetMatchIssue12456(t *testing.T) {
t.Parallel()
if !scss.Supports() {
t.Skip()
}
files := `
-- hugo.toml --
disableKinds = ["term", "taxonomy", "section", "page"]
disableLiveReload = true
-- assets/a.scss --
h1 {
color: red;
}
-- assets/dir/b.scss --
h2 {
color: blue;
}
-- assets/dir/c.scss --
h3 {
color: green;
}
-- layouts/index.html --
{{ $a := slice (resources.Get "a.scss") }}
{{ $b := resources.Match "dir/*.scss" }}
{{/* Add styles in a specific order. */}}
{{ $styles := slice $a $b }}
{{ $stylesheets := slice }}
{{ range $styles }}
{{ $stylesheets = $stylesheets | collections.Append . }}
{{ end }}
{{ range $stylesheets }}
{{ with . | resources.ToCSS | fingerprint }}
<link as="style" href="{{ .RelPermalink }}" rel="preload stylesheet">
{{ end }}
{{ end }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: true,
Running: true,
// LogLevel: logg.LevelTrace,
}).Build()
b.AssertFileContent("public/index.html", `b.60a9f3bdc189ee8a857afd5b7e1b93ad1644de0873761a7c9bc84f781a821942.css`)
b.EditFiles("assets/dir/b.scss", `h2 { color: orange; }`).Build()
b.AssertFileContent("public/index.html", `b.46b2d77c7ffe37ee191678f72df991ecb1319f849957151654362f09b0ef467f.css`)
}
+3 -9
View File
@@ -49,7 +49,6 @@ var (
_ resource.ReadSeekCloserResource = (*resourceAdapter)(nil)
_ resource.Resource = (*resourceAdapter)(nil)
_ resource.Staler = (*resourceAdapterInner)(nil)
_ identity.IdentityGroupProvider = (*resourceAdapterInner)(nil)
_ resource.Source = (*resourceAdapter)(nil)
_ resource.Identifier = (*resourceAdapter)(nil)
_ resource.ResourceNameTitleProvider = (*resourceAdapter)(nil)
@@ -264,7 +263,7 @@ func (r *resourceAdapter) Exif() *exif.ExifInfo {
return r.getImageOps().Exif()
}
func (r *resourceAdapter) Colors() ([]images.Color, error) {
func (r *resourceAdapter) Colors() ([]string, error) {
return r.getImageOps().Colors()
}
@@ -658,13 +657,8 @@ type resourceAdapterInner struct {
*publishOnce
}
func (r *resourceAdapterInner) GetIdentityGroup() identity.Identity {
return r.target.GetIdentityGroup()
}
func (r *resourceAdapterInner) StaleVersion() uint32 {
// Both of these are incremented on change.
return r.Staler.StaleVersion() + r.target.StaleVersion()
func (r *resourceAdapterInner) IsStale() bool {
return r.Staler.IsStale() || r.target.IsStale()
}
type resourceTransformations struct {
-2
View File
@@ -165,12 +165,10 @@ var Context = struct {
SetDependencyManagerInCurrentScope func(context.Context, identity.Manager) context.Context
DependencyScope hcontext.ContextDispatcher[int]
Page hcontext.ContextDispatcher[page]
IsInGoldmark hcontext.ContextDispatcher[bool]
}{
DependencyManagerScopedProvider: hcontext.NewContextDispatcher[identity.DependencyManagerScopedProvider](contextKey("DependencyManagerScopedProvider")),
DependencyScope: hcontext.NewContextDispatcher[int](contextKey("DependencyScope")),
Page: hcontext.NewContextDispatcher[page](contextKey("Page")),
IsInGoldmark: hcontext.NewContextDispatcher[bool](contextKey("IsInGoldmark")),
}
func init() {
@@ -1,11 +1,11 @@
{{- $u := urls.Parse .Destination -}}
{{- $src := $u.String -}}
{{- if not $u.IsAbs -}}
{{- with or (.PageInner.Resources.Get $u.Path) (resources.Get $u.Path) -}}
{{- with or (.Page.Resources.Get $u.Path) (resources.Get $u.Path) -}}
{{- $src = .RelPermalink -}}
{{- end -}}
{{- end -}}
{{- $attributes := merge .Attributes (dict "alt" .Text "src" $src "title" (.Title | transform.HTMLEscape)) -}}
{{- $attributes := merge .Attributes (dict "alt" .Text "src" $src "title" .Title) -}}
<img
{{- range $k, $v := $attributes -}}
{{- if $v -}}
@@ -1,11 +1,11 @@
{{- $u := urls.Parse .Destination -}}
{{- $href := $u.String -}}
{{- if strings.HasPrefix $u.String "#" }}
{{- $href = printf "%s#%s" .PageInner.RelPermalink $u.Fragment }}
{{- $href = printf "%s#%s" .Page.RelPermalink $u.Fragment }}
{{- else if not $u.IsAbs -}}
{{- with or
($.PageInner.GetPage $u.Path)
($.PageInner.Resources.Get $u.Path)
($.Page.GetPage $u.Path)
($.Page.Resources.Get $u.Path)
(resources.Get $u.Path)
-}}
{{- $href = .RelPermalink -}}
@@ -17,7 +17,7 @@
{{- end -}}
{{- end -}}
{{- end -}}
{{- $attributes := dict "href" $href "title" (.Title | transform.HTMLEscape) -}}
{{- $attributes := dict "href" $href "title" .Title -}}
<a
{{- range $k, $v := $attributes -}}
{{- if $v -}}
@@ -48,7 +48,7 @@
<title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} on {{ end }}{{ .Site.Title }}{{ end }}</title>
<link>{{ .Permalink }}</link>
<description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{ . }} {{ end }}{{ end }}on {{ .Site.Title }}</description>
<generator>Hugo</generator>
<generator>Hugo {{ hugo.Version }}</generator>
<language>{{ site.Language.LanguageCode }}</language>{{ with $authorEmail }}
<managingEditor>{{.}}{{ with $authorName }} ({{ . }}){{ end }}</managingEditor>{{ end }}{{ with $authorEmail }}
<webMaster>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</webMaster>{{ end }}{{ with .Site.Copyright }}
@@ -4,11 +4,11 @@
<meta property="og:site_name" content="{{ . }}">
{{- end }}
{{- with or .Title site.Title site.Params.title | plainify }}
{{- with or .Title site.Title site.Params.title | plainify}}
<meta property="og:title" content="{{ . }}">
{{- end }}
{{- with or .Description .Summary site.Params.description | plainify | htmlUnescape | chomp }}
{{- with or .Description .Summary site.Params.description | plainify }}
<meta property="og:description" content="{{ . }}">
{{- end }}
@@ -18,9 +18,7 @@
{{- if .IsPage }}
<meta property="og:type" content="article">
{{- with .Section }}
<meta property="article:section" content="{{ . }}">
{{- end }}
<meta property="article:section" content="{{ .Section }}">
{{- $ISO8601 := "2006-01-02T15:04:05-07:00" }}
{{- with .PublishDate }}
<meta property="article:published_time" {{ .Format $ISO8601 | printf "content=%q" | safeHTMLAttr }}>
@@ -8,10 +8,10 @@ Renders an embedded YouTube video.
@param {int} [end] The time, measured in seconds from the start of the video, when the player should stop playing the video.
@param {string} [id] The video id. Optional if the id is provided as first positional argument.
@param {string} [loading=eager] The loading attribute of the iframe element.
@param {bool} [loop=false] Whether to indefinitely repeat the video. Ignores the start and end arguments after the first play.
@param {bool} [loop=false] Whether to indefinitely repeat the video.
@param {bool} [mute=false] Whether to mute the video. Always true when autoplay is true.
@param {int} [start] The time, measured in seconds from the start of the video, when the player should start playing the video.
@param {string} [title] The title attribute of the iframe element. Defaults to "YouTube video".
@param {string} [title] The title attribute of the iframe element. Defaults to the title returned by YouTube oEmbed API.
@returns {template.HTML}
@@ -22,10 +22,23 @@ Renders an embedded YouTube video.
*/}}
{{- $pc := .Page.Site.Config.Privacy.YouTube }}
{{- $remoteErrID := "err-youtube-remote" }}
{{- if not $pc.Disable }}
{{- with $id := or (.Get "id") (.Get 0) }}
{{- /* Get data from the YouTube oEmbed API. */}}
{{- $q := querify "url" (printf "https://www.youtube.com/watch?v=%s" $id) "format" "json" }}
{{- $url := printf "https://www.youtube.com/oembed?%s" $q }}
{{- $data := dict }}
{{- with resources.GetRemote $url }}
{{- with .Err }}
{{- errorf "The %q shortcode was unable to get remote resource %q. %s. See %s" $.Name $url . $.Position }}
{{- else }}
{{- $data = .Content | transform.Unmarshal }}
{{- end }}
{{- else }}
{{- errorf "The %q shortcode was unable to get remote resource %q. See %s" $.Name $url $.Position }}
{{- end }}
{{/* Set defaults. */}}
{{- $allowFullScreen := "allowfullscreen" }}
{{- $autoplay := 0 }}
@@ -36,7 +49,7 @@ Renders an embedded YouTube video.
{{- $loop := 0 }}
{{- $mute := 0 }}
{{- $start := 0 }}
{{- $title := "YouTube video" }}
{{- $title := $data.title }}
{{- /* Get arguments. */}}
{{- if in (slice "false" false 0) ($.Get "allowFullScreen") }}
+5 -5
View File
@@ -71,7 +71,7 @@ var (
)
type templateExecHelper struct {
watching bool // whether we're in server/watch mode.
running bool // whether we're in server mode.
site reflect.Value
siteParams reflect.Value
funcs map[string]reflect.Value
@@ -95,7 +95,7 @@ func (t *templateExecHelper) GetFunc(ctx context.Context, tmpl texttemplate.Prep
}
func (t *templateExecHelper) Init(ctx context.Context, tmpl texttemplate.Preparer) {
if t.watching {
if t.running {
_, ok := tmpl.(identity.IdentityProvider)
if ok {
t.trackDependencies(ctx, tmpl, "", reflect.Value{})
@@ -129,7 +129,7 @@ func (t *templateExecHelper) GetMethod(ctx context.Context, tmpl texttemplate.Pr
name = "MainSections"
}
if t.watching {
if t.running {
ctx = t.trackDependencies(ctx, tmpl, name, receiver)
}
@@ -151,7 +151,7 @@ func (t *templateExecHelper) GetMethod(ctx context.Context, tmpl texttemplate.Pr
}
func (t *templateExecHelper) OnCalled(ctx context.Context, tmpl texttemplate.Preparer, name string, args []reflect.Value, result reflect.Value) {
if !t.watching {
if !t.running {
return
}
@@ -238,7 +238,7 @@ func newTemplateExecuter(d *deps.Deps) (texttemplate.Executer, map[string]reflec
}
exeHelper := &templateExecHelper{
watching: d.Conf.Watching(),
running: d.Conf.Running(),
funcs: funcsv,
site: reflect.ValueOf(d.Site),
siteParams: reflect.ValueOf(d.Site.Params()),
-106
View File
@@ -305,109 +305,3 @@ title: p2
"<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\"\n xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\n <url>\n <loc>/p2/</loc>\n </url>\n</urlset>\n",
)
}
// Issue 12418
func TestOpengraph(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
capitalizeListTitles = false
disableKinds = ['rss','sitemap']
languageCode = 'en-US'
[markup.goldmark.renderer]
unsafe = true
[params]
description = "m <em>n</em> and **o** can't."
[params.social]
facebook_admin = 'foo'
[taxonomies]
series = 'series'
tag = 'tags'
-- layouts/_default/list.html --
{{ template "_internal/opengraph.html" . }}
-- layouts/_default/single.html --
{{ template "_internal/opengraph.html" . }}
-- content/s1/p1.md --
---
title: p1
date: 2024-04-24T08:00:00-07:00
lastmod: 2024-04-24T11:00:00-07:00
images: [a.jpg,b.jpg]
audio: [c.mp3,d.mp3]
videos: [e.mp4,f.mp4]
series: [series-1]
tags: [t1,t2]
---
a <em>b</em> and **c** can't.
-- content/s1/p2.md --
---
title: p2
series: [series-1]
---
d <em>e</em> and **f** can't.
<!--more-->
-- content/s1/p3.md --
---
title: p3
series: [series-1]
summary: g <em>h</em> and **i** can't.
---
-- content/s1/p4.md --
---
title: p4
series: [series-1]
description: j <em>k</em> and **l** can't.
---
-- content/s1/p5.md --
---
title: p5
series: [series-1]
---
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/s1/p1/index.html", `
<meta property="og:url" content="/s1/p1/">
<meta property="og:title" content="p1">
<meta property="og:description" content="a b and c cant.">
<meta property="og:locale" content="en-US">
<meta property="og:type" content="article">
<meta property="article:section" content="s1">
<meta property="article:published_time" content="2024-04-24T08:00:00-07:00">
<meta property="article:modified_time" content="2024-04-24T11:00:00-07:00">
<meta property="article:tag" content="t1">
<meta property="article:tag" content="t2">
<meta property="og:image" content="/a.jpg">
<meta property="og:image" content="/b.jpg">
<meta property="og:audio" content="/c.mp3">
<meta property="og:audio" content="/d.mp3">
<meta property="og:video" content="/e.mp4">
<meta property="og:video" content="/f.mp4">
<meta property="og:see_also" content="/s1/p2/">
<meta property="og:see_also" content="/s1/p3/">
<meta property="og:see_also" content="/s1/p4/">
<meta property="og:see_also" content="/s1/p5/">
<meta property="fb:admins" content="foo">
`,
)
b.AssertFileContent("public/s1/p2/index.html",
`<meta property="og:description" content="d e and f cant.">`,
)
b.AssertFileContent("public/s1/p3/index.html",
`<meta property="og:description" content="g h and i cant.">`,
)
// The markdown is intentionally not rendered to HTML.
b.AssertFileContent("public/s1/p4/index.html",
`<meta property="og:description" content="j k and **l** can&#39;t.">`,
)
// The markdown is intentionally not rendered to HTML.
b.AssertFileContent("public/s1/p5/index.html",
`<meta property="og:description" content="m n and **o** can&#39;t.">`,
)
}
+1 -1
View File
@@ -167,7 +167,7 @@ func (ns *Namespace) Markdownify(ctx context.Context, s any) (template.HTML, err
}
// Strip if this is a short inline type of text.
bb := ns.deps.ContentSpec.TrimShortHTML([]byte(ss), "markdown")
bb := ns.deps.ContentSpec.TrimShortHTML([]byte(ss))
return helpers.BytesToHTML(bb), nil
}
+4 -4
View File
@@ -95,8 +95,8 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) {
return &resources.StaleValue[any]{
Value: v,
StaleVersionFunc: func() uint32 {
return resource.StaleVersion(r)
IsStaleFunc: func() bool {
return resource.IsStaleAny(r)
},
}, nil
})
@@ -132,8 +132,8 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) {
return &resources.StaleValue[any]{
Value: v,
StaleVersionFunc: func() uint32 {
return 0
IsStaleFunc: func() bool {
return false
},
}, nil
})