Go's stdlid now has a maps package, which is very useful. We have been using imports on the form `xmaps "maps"`, but auto import tools doesn't handle this, which is annoying.
I have been thinking about this for some time, but have been holding back because of all the import changes. However, now is a good time to do this, with very little unmerged code.
"strings"
"time"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/mitchellh/mapstructure"
_, isOsFs := fs.(*afero.OsFs)
for k, v := range m {
- if _, ok := v.(maps.Params); !ok {
+ if _, ok := v.(hmaps.Params); !ok {
continue
}
cc := defaultCacheConfig
"time"
"github.com/bep/simplecobra"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
"github.com/gohugoio/hugo/modules"
if err := json.Unmarshal(buf.Bytes(), &m); err != nil {
return err
}
- maps.ConvertFloat64WithNoDecimalsToInt(m)
+ hmaps.ConvertFloat64WithNoDecimalsToInt(m)
switch format {
case "yaml":
return parser.InterfaceToConfig(m, metadecoders.YAML, os.Stdout)
"github.com/bep/simplecobra"
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/terminal"
"github.com/gohugoio/hugo/common/types"
}
cfg.Set("environment", c.r.environment)
- cfg.Set("internal", maps.Params{
+ cfg.Set("internal", hmaps.Params{
"running": running,
"watch": watch,
"verbose": c.r.isVerbose(),
"unicode"
"github.com/bep/simplecobra"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugio"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/parser"
}
func (c *importCommand) convertJekyllContent(m any, content string) (string, error) {
- metadata, _ := maps.ToStringMapE(m)
+ metadata, _ := hmaps.ToStringMapE(m)
lines := strings.Split(content, "\n")
var resultLines []string
}
func (c *importCommand) convertJekyllMetaData(m any, postName string, postDate time.Time, draft bool) (any, error) {
- metadata, err := maps.ToStringMapE(m)
+ metadata, err := hmaps.ToStringMapE(m)
if err != nil {
return nil, err
}
"sync"
"github.com/bep/logg"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/security"
)
workingDir: workingDir,
infol: log.InfoCommand("exec"),
baseEnviron: baseEnviron,
- newNPXRunnerCache: maps.NewCache[string, func(arg ...any) (Runner, error)](),
+ newNPXRunnerCache: hmaps.NewCache[string, func(arg ...any) (Runner, error)](),
}
}
// os.Environ filtered by the Exec.OsEnviron whitelist filter.
baseEnviron []string
- newNPXRunnerCache *maps.Cache[string, func(arg ...any) (Runner, error)]
+ newNPXRunnerCache *hmaps.Cache[string, func(arg ...any) (Runner, error)]
npxInit sync.Once
npxAvailable bool
}
--- /dev/null
+// Copyright 2026 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 hmaps
+
+import (
+ "sync"
+)
+
+// Cache is a simple thread safe cache backed by a map.
+type Cache[K comparable, T any] struct {
+ m map[K]T
+ opts CacheOptions
+ hasBeenInitialized bool
+ sync.RWMutex
+}
+
+// CacheOptions are the options for the Cache.
+type CacheOptions struct {
+ // If set, the cache will not grow beyond this size.
+ Size uint64
+}
+
+var defaultCacheOptions = CacheOptions{}
+
+// NewCache creates a new Cache with default options.
+func NewCache[K comparable, T any]() *Cache[K, T] {
+ return &Cache[K, T]{m: make(map[K]T), opts: defaultCacheOptions}
+}
+
+// NewCacheWithOptions creates a new Cache with the given options.
+func NewCacheWithOptions[K comparable, T any](opts CacheOptions) *Cache[K, T] {
+ return &Cache[K, T]{m: make(map[K]T), opts: opts}
+}
+
+// Delete deletes the given key from the cache.
+// If c is nil, this method is a no-op.
+func (c *Cache[K, T]) Get(key K) (T, bool) {
+ if c == nil {
+ var zero T
+ return zero, false
+ }
+ c.RLock()
+ v, found := c.get(key)
+ c.RUnlock()
+ return v, found
+}
+
+func (c *Cache[K, T]) get(key K) (T, bool) {
+ v, found := c.m[key]
+ return v, found
+}
+
+// GetOrCreate gets the value for the given key if it exists, or creates it if not.
+func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
+ c.RLock()
+ v, found := c.m[key]
+ c.RUnlock()
+ if found {
+ return v, nil
+ }
+ c.Lock()
+ defer c.Unlock()
+ v, found = c.m[key]
+ if found {
+ return v, nil
+ }
+ v, err := create()
+ if err != nil {
+ return v, err
+ }
+ c.clearIfNeeded()
+ c.m[key] = v
+ return v, nil
+}
+
+// Contains returns whether the given key exists in the cache.
+func (c *Cache[K, T]) Contains(key K) bool {
+ c.RLock()
+ _, found := c.m[key]
+ c.RUnlock()
+ return found
+}
+
+// InitAndGet initializes the cache if not already done and returns the value for the given key.
+// The init state will be reset on Reset or Drain.
+func (c *Cache[K, T]) InitAndGet(key K, init func(get func(key K) (T, bool), set func(key K, value T)) error) (T, error) {
+ var v T
+ c.RLock()
+ if !c.hasBeenInitialized {
+ c.RUnlock()
+ if err := func() error {
+ c.Lock()
+ defer c.Unlock()
+ // Double check in case another goroutine has initialized it in the meantime.
+ if !c.hasBeenInitialized {
+ err := init(c.get, c.set)
+ if err != nil {
+ return err
+ }
+ c.hasBeenInitialized = true
+ }
+ return nil
+ }(); err != nil {
+ return v, err
+ }
+ // Reacquire the read lock.
+ c.RLock()
+ }
+
+ v = c.m[key]
+ c.RUnlock()
+
+ return v, nil
+}
+
+// Set sets the given key to the given value.
+func (c *Cache[K, T]) Set(key K, value T) {
+ c.Lock()
+ c.set(key, value)
+ c.Unlock()
+}
+
+// SetIfAbsent sets the given key to the given value if the key does not already exist in the cache.
+func (c *Cache[K, T]) SetIfAbsent(key K, value T) {
+ c.RLock()
+ if _, found := c.get(key); !found {
+ c.RUnlock()
+ c.Set(key, value)
+ } else {
+ c.RUnlock()
+ }
+}
+
+func (c *Cache[K, T]) clearIfNeeded() {
+ if c.opts.Size > 0 && uint64(len(c.m)) >= c.opts.Size {
+ // clear the map
+ clear(c.m)
+ }
+}
+
+func (c *Cache[K, T]) set(key K, value T) {
+ c.clearIfNeeded()
+ c.m[key] = value
+}
+
+// ForEeach calls the given function for each key/value pair in the cache.
+// If the function returns false, the iteration stops.
+func (c *Cache[K, T]) ForEeach(f func(K, T) bool) {
+ c.RLock()
+ defer c.RUnlock()
+ for k, v := range c.m {
+ if !f(k, v) {
+ return
+ }
+ }
+}
+
+func (c *Cache[K, T]) Drain() map[K]T {
+ c.Lock()
+ m := c.m
+ c.m = make(map[K]T)
+ c.hasBeenInitialized = false
+ c.Unlock()
+ return m
+}
+
+func (c *Cache[K, T]) Len() int {
+ c.RLock()
+ defer c.RUnlock()
+ return len(c.m)
+}
+
+func (c *Cache[K, T]) Reset() {
+ c.Lock()
+ clear(c.m)
+ c.hasBeenInitialized = false
+ c.Unlock()
+}
+
+// SliceCache is a simple thread safe cache backed by a map.
+type SliceCache[T any] struct {
+ m map[string][]T
+ sync.RWMutex
+}
+
+func NewSliceCache[T any]() *SliceCache[T] {
+ return &SliceCache[T]{m: make(map[string][]T)}
+}
+
+func (c *SliceCache[T]) Get(key string) ([]T, bool) {
+ c.RLock()
+ v, found := c.m[key]
+ c.RUnlock()
+ return v, found
+}
+
+func (c *SliceCache[T]) Append(key string, values ...T) {
+ c.Lock()
+ c.m[key] = append(c.m[key], values...)
+ c.Unlock()
+}
+
+func (c *SliceCache[T]) Reset() {
+ c.Lock()
+ c.m = make(map[string][]T)
+ c.Unlock()
+}
--- /dev/null
+// Copyright 2026 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 hmaps
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestCacheSize(t *testing.T) {
+ c := qt.New(t)
+
+ cache := NewCacheWithOptions[string, string](CacheOptions{Size: 10})
+
+ for i := range 30 {
+ cache.Set(string(rune('a'+i)), "value")
+ }
+
+ c.Assert(len(cache.m), qt.Equals, 10)
+
+ for i := 20; i < 50; i++ {
+ cache.GetOrCreate(string(rune('a'+i)), func() (string, error) {
+ return "value", nil
+ })
+ }
+
+ c.Assert(len(cache.m), qt.Equals, 10)
+
+ for i := 100; i < 200; i++ {
+ cache.SetIfAbsent(string(rune('a'+i)), "value")
+ }
+
+ c.Assert(len(cache.m), qt.Equals, 10)
+
+ cache.InitAndGet("foo", func(
+ get func(key string) (string, bool), set func(key string, value string),
+ ) error {
+ for i := 50; i < 100; i++ {
+ set(string(rune('a'+i)), "value")
+ }
+ return nil
+ })
+
+ c.Assert(len(cache.m), qt.Equals, 10)
+}
--- /dev/null
+// Copyright 2026 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 hmaps
+
+import (
+ "iter"
+ "sync"
+)
+
+func NewMap[K comparable, T any]() *Map[K, T] {
+ return &Map[K, T]{
+ m: make(map[K]T),
+ }
+}
+
+// Map is a thread safe map backed by a Go map.
+type Map[K comparable, T any] struct {
+ m map[K]T
+ mu sync.RWMutex
+}
+
+// Get gets the value for the given key.
+// It returns the zero value of T if the key is not found.
+func (m *Map[K, T]) Get(key K) T {
+ v, _ := m.Lookup(key)
+ return v
+}
+
+// Lookup looks up the given key in the map.
+// It returns the value and a boolean indicating whether the key was found.
+func (m *Map[K, T]) Lookup(key K) (T, bool) {
+ m.mu.RLock()
+ v, found := m.m[key]
+ m.mu.RUnlock()
+ return v, found
+}
+
+// GetOrCreate gets the value for the given key if it exists, or creates it if not.
+func (m *Map[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
+ v, found := m.Lookup(key)
+ if found {
+ return v, nil
+ }
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ v, found = m.m[key]
+ if found {
+ return v, nil
+ }
+ v, err := create()
+ if err != nil {
+ return v, err
+ }
+ m.m[key] = v
+ return v, nil
+}
+
+// Set sets the given key to the given value.
+func (m *Map[K, T]) Set(key K, value T) {
+ m.mu.Lock()
+ m.m[key] = value
+ m.mu.Unlock()
+}
+
+// Delete deletes the given key from the map.
+// It returns true if the key was found and deleted, false otherwise.
+func (m *Map[K, T]) Delete(key K) bool {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ if _, found := m.m[key]; found {
+ delete(m.m, key)
+ return true
+ }
+ return false
+}
+
+// WithWriteLock executes the given function with a write lock on the map.
+func (m *Map[K, T]) WithWriteLock(f func(m map[K]T) error) error {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ return f(m.m)
+}
+
+// SetIfAbsent sets the given key to the given value if the key does not already exist in the map.
+// It returns true if the value was set, false otherwise.
+func (m *Map[K, T]) SetIfAbsent(key K, value T) bool {
+ m.mu.RLock()
+ if _, found := m.m[key]; !found {
+ m.mu.RUnlock()
+ return m.doSetIfAbsent(key, value)
+ }
+ m.mu.RUnlock()
+ return false
+}
+
+func (m *Map[K, T]) doSetIfAbsent(key K, value T) bool {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ if _, found := m.m[key]; !found {
+ m.m[key] = value
+ return true
+ }
+ return false
+}
+
+// All returns an iterator over all key/value pairs in the map.
+// A read lock is held during the iteration.
+func (m *Map[K, T]) All() iter.Seq2[K, T] {
+ return func(yield func(K, T) bool) {
+ m.mu.RLock()
+ defer m.mu.RUnlock()
+ for k, v := range m.m {
+ if !yield(k, v) {
+ return
+ }
+ }
+ }
+}
--- /dev/null
+// Copyright 2026 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 hmaps
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestMap(t *testing.T) {
+ c := qt.New(t)
+
+ m := NewMap[string, int]()
+
+ m.Set("b", 42)
+ v, found := m.Lookup("b")
+ c.Assert(found, qt.Equals, true)
+ c.Assert(v, qt.Equals, 42)
+ v = m.Get("b")
+ c.Assert(v, qt.Equals, 42)
+ v, found = m.Lookup("c")
+ c.Assert(found, qt.Equals, false)
+ c.Assert(v, qt.Equals, 0)
+ v = m.Get("c")
+ c.Assert(v, qt.Equals, 0)
+ v, err := m.GetOrCreate("d", func() (int, error) {
+ return 100, nil
+ })
+ c.Assert(err, qt.IsNil)
+ c.Assert(v, qt.Equals, 100)
+ v, found = m.Lookup("d")
+ c.Assert(found, qt.Equals, true)
+ c.Assert(v, qt.Equals, 100)
+
+ v, err = m.GetOrCreate("d", func() (int, error) {
+ return 200, nil
+ })
+ c.Assert(err, qt.IsNil)
+ c.Assert(v, qt.Equals, 100)
+
+ wasSet := m.SetIfAbsent("e", 300)
+ c.Assert(wasSet, qt.Equals, true)
+ v, found = m.Lookup("e")
+ c.Assert(found, qt.Equals, true)
+ c.Assert(v, qt.Equals, 300)
+
+ wasSet = m.SetIfAbsent("e", 400)
+ c.Assert(wasSet, qt.Equals, false)
+ v, found = m.Lookup("e")
+ c.Assert(found, qt.Equals, true)
+ c.Assert(v, qt.Equals, 300)
+
+ m.WithWriteLock(func(m map[string]int) error {
+ m["f"] = 500
+ return nil
+ })
+ v, found = m.Lookup("f")
+ c.Assert(found, qt.Equals, true)
+ c.Assert(v, qt.Equals, 500)
+}
--- /dev/null
+// Copyright 2026 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 hmaps
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/gohugoio/hugo/common/types"
+
+ "github.com/gobwas/glob"
+ "github.com/spf13/cast"
+)
+
+// ToStringMapE converts in to map[string]interface{}.
+func ToStringMapE(in any) (map[string]any, error) {
+ switch vv := in.(type) {
+ case Params:
+ return vv, nil
+ case map[string]string:
+ m := map[string]any{}
+ for k, v := range vv {
+ m[k] = v
+ }
+ return m, nil
+
+ default:
+ return cast.ToStringMapE(in)
+ }
+}
+
+// ToParamsAndPrepare converts in to Params and prepares it for use.
+// If in is nil, an empty map is returned.
+// See PrepareParams.
+func ToParamsAndPrepare(in any) (Params, error) {
+ if types.IsNil(in) {
+ return Params{}, nil
+ }
+ m, err := ToStringMapE(in)
+ if err != nil {
+ return nil, err
+ }
+ PrepareParams(m)
+ return m, nil
+}
+
+// MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails.
+func MustToParamsAndPrepare(in any) Params {
+ p, err := ToParamsAndPrepare(in)
+ if err != nil {
+ panic(fmt.Sprintf("cannot convert %T to maps.Params: %s", in, err))
+ }
+ return p
+}
+
+// ToStringMap converts in to map[string]interface{}.
+func ToStringMap(in any) map[string]any {
+ m, _ := ToStringMapE(in)
+ return m
+}
+
+// ToStringMapStringE converts in to map[string]string.
+func ToStringMapStringE(in any) (map[string]string, error) {
+ m, err := ToStringMapE(in)
+ if err != nil {
+ return nil, err
+ }
+ return cast.ToStringMapStringE(m)
+}
+
+// ToStringMapString converts in to map[string]string.
+func ToStringMapString(in any) map[string]string {
+ m, _ := ToStringMapStringE(in)
+ return m
+}
+
+// ToStringMapBool converts in to bool.
+func ToStringMapBool(in any) map[string]bool {
+ m, _ := ToStringMapE(in)
+ return cast.ToStringMapBool(m)
+}
+
+// ToSliceStringMap converts in to []map[string]interface{}.
+func ToSliceStringMap(in any) ([]map[string]any, error) {
+ switch v := in.(type) {
+ case []map[string]any:
+ return v, nil
+ case Params:
+ return []map[string]any{v}, nil
+ case map[string]any:
+ return []map[string]any{v}, nil
+ case []any:
+ var s []map[string]any
+ for _, entry := range v {
+ if vv, ok := entry.(map[string]any); ok {
+ s = append(s, vv)
+ }
+ }
+ return s, nil
+ default:
+ return nil, fmt.Errorf("unable to cast %#v of type %T to []map[string]interface{}", in, in)
+ }
+}
+
+// LookupEqualFold finds key in m with case insensitive equality checks.
+func LookupEqualFold[T any | string](m map[string]T, key string) (T, string, bool) {
+ if v, found := m[key]; found {
+ return v, key, true
+ }
+ for k, v := range m {
+ if strings.EqualFold(k, key) {
+ return v, k, true
+ }
+ }
+ var s T
+ return s, "", false
+}
+
+// MergeShallow merges src into dst, but only if the key does not already exist in dst.
+// The keys are compared case insensitively.
+func MergeShallow(dst, src map[string]any) {
+ for k, v := range src {
+ found := false
+ for dk := range dst {
+ if strings.EqualFold(dk, k) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ dst[k] = v
+ }
+ }
+}
+
+type keyRename struct {
+ pattern glob.Glob
+ newKey string
+}
+
+// KeyRenamer supports renaming of keys in a map.
+type KeyRenamer struct {
+ renames []keyRename
+}
+
+// NewKeyRenamer creates a new KeyRenamer given a list of pattern and new key
+// value pairs.
+func NewKeyRenamer(patternKeys ...string) (KeyRenamer, error) {
+ var renames []keyRename
+ for i := 0; i < len(patternKeys); i += 2 {
+ g, err := glob.Compile(strings.ToLower(patternKeys[i]), '/')
+ if err != nil {
+ return KeyRenamer{}, err
+ }
+ renames = append(renames, keyRename{pattern: g, newKey: patternKeys[i+1]})
+ }
+
+ return KeyRenamer{renames: renames}, nil
+}
+
+func (r KeyRenamer) getNewKey(keyPath string) string {
+ for _, matcher := range r.renames {
+ if matcher.pattern.Match(keyPath) {
+ return matcher.newKey
+ }
+ }
+
+ return ""
+}
+
+// Rename renames the keys in the given map according
+// to the patterns in the current KeyRenamer.
+func (r KeyRenamer) Rename(m map[string]any) {
+ r.renamePath("", m)
+}
+
+func (KeyRenamer) keyPath(k1, k2 string) string {
+ k1, k2 = strings.ToLower(k1), strings.ToLower(k2)
+ if k1 == "" {
+ return k2
+ }
+ return k1 + "/" + k2
+}
+
+func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]any) {
+ for k, v := range m {
+ keyPath := r.keyPath(parentKeyPath, k)
+ switch vv := v.(type) {
+ case map[any]any:
+ r.renamePath(keyPath, cast.ToStringMap(vv))
+ case map[string]any:
+ r.renamePath(keyPath, vv)
+ }
+
+ newKey := r.getNewKey(keyPath)
+
+ if newKey != "" {
+ delete(m, k)
+ m[newKey] = v
+ }
+ }
+}
+
+// ConvertFloat64WithNoDecimalsToInt converts float64 values with no decimals to int recursively.
+func ConvertFloat64WithNoDecimalsToInt(m map[string]any) {
+ for k, v := range m {
+ switch vv := v.(type) {
+ case float64:
+ if v == float64(int64(vv)) {
+ m[k] = int64(vv)
+ }
+ case map[string]any:
+ ConvertFloat64WithNoDecimalsToInt(vv)
+ case []any:
+ for i, vvv := range vv {
+ switch vvvv := vvv.(type) {
+ case float64:
+ if vvv == float64(int64(vvvv)) {
+ vv[i] = int64(vvvv)
+ }
+ case map[string]any:
+ ConvertFloat64WithNoDecimalsToInt(vvvv)
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// Copyright 2026 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 hmaps
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestPrepareParams(t *testing.T) {
+ tests := []struct {
+ input Params
+ expected Params
+ }{
+ {
+ map[string]any{
+ "abC": 32,
+ },
+ Params{
+ "abc": 32,
+ },
+ },
+ {
+ map[string]any{
+ "abC": 32,
+ "deF": map[any]any{
+ 23: "A value",
+ 24: map[string]any{
+ "AbCDe": "A value",
+ "eFgHi": "Another value",
+ },
+ },
+ "gHi": map[string]any{
+ "J": 25,
+ },
+ "jKl": map[string]string{
+ "M": "26",
+ },
+ },
+ Params{
+ "abc": 32,
+ "def": Params{
+ "23": "A value",
+ "24": Params{
+ "abcde": "A value",
+ "efghi": "Another value",
+ },
+ },
+ "ghi": Params{
+ "j": 25,
+ },
+ "jkl": Params{
+ "m": "26",
+ },
+ },
+ },
+ }
+
+ for i, test := range tests {
+ t.Run(fmt.Sprint(i), func(t *testing.T) {
+ // PrepareParams modifies input.
+ prepareClone := PrepareParamsClone(test.input)
+ PrepareParams(test.input)
+ if !reflect.DeepEqual(test.expected, test.input) {
+ t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)
+ }
+ if !reflect.DeepEqual(test.expected, prepareClone) {
+ t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, prepareClone)
+ }
+ })
+ }
+}
+
+func TestToSliceStringMap(t *testing.T) {
+ c := qt.New(t)
+
+ tests := []struct {
+ input any
+ expected []map[string]any
+ }{
+ {
+ input: []map[string]any{
+ {"abc": 123},
+ },
+ expected: []map[string]any{
+ {"abc": 123},
+ },
+ }, {
+ input: []any{
+ map[string]any{
+ "def": 456,
+ },
+ },
+ expected: []map[string]any{
+ {"def": 456},
+ },
+ },
+ }
+
+ for _, test := range tests {
+ v, err := ToSliceStringMap(test.input)
+ c.Assert(err, qt.IsNil)
+ c.Assert(v, qt.DeepEquals, test.expected)
+ }
+}
+
+func TestToParamsAndPrepare(t *testing.T) {
+ c := qt.New(t)
+ _, err := ToParamsAndPrepare(map[string]any{"A": "av"})
+ c.Assert(err, qt.IsNil)
+
+ params, err := ToParamsAndPrepare(nil)
+ c.Assert(err, qt.IsNil)
+ c.Assert(params, qt.DeepEquals, Params{})
+}
+
+func TestRenameKeys(t *testing.T) {
+ c := qt.New(t)
+
+ m := map[string]any{
+ "a": 32,
+ "ren1": "m1",
+ "ren2": "m1_2",
+ "sub": map[string]any{
+ "subsub": map[string]any{
+ "REN1": "m2",
+ "ren2": "m2_2",
+ },
+ },
+ "no": map[string]any{
+ "ren1": "m2",
+ "ren2": "m2_2",
+ },
+ }
+
+ expected := map[string]any{
+ "a": 32,
+ "new1": "m1",
+ "new2": "m1_2",
+ "sub": map[string]any{
+ "subsub": map[string]any{
+ "new1": "m2",
+ "ren2": "m2_2",
+ },
+ },
+ "no": map[string]any{
+ "ren1": "m2",
+ "ren2": "m2_2",
+ },
+ }
+
+ renamer, err := NewKeyRenamer(
+ "{ren1,sub/*/ren1}", "new1",
+ "{Ren2,sub/ren2}", "new2",
+ )
+ c.Assert(err, qt.IsNil)
+
+ renamer.Rename(m)
+
+ if !reflect.DeepEqual(expected, m) {
+ t.Errorf("Expected\n%#v, got\n%#v\n", expected, m)
+ }
+}
+
+func TestLookupEqualFold(t *testing.T) {
+ c := qt.New(t)
+
+ m1 := map[string]any{
+ "a": "av",
+ "B": "bv",
+ }
+
+ v, k, found := LookupEqualFold(m1, "b")
+ c.Assert(found, qt.IsTrue)
+ c.Assert(v, qt.Equals, "bv")
+ c.Assert(k, qt.Equals, "B")
+
+ m2 := map[string]string{
+ "a": "av",
+ "B": "bv",
+ }
+
+ v, k, found = LookupEqualFold(m2, "b")
+ c.Assert(found, qt.IsTrue)
+ c.Assert(k, qt.Equals, "B")
+ c.Assert(v, qt.Equals, "bv")
+}
--- /dev/null
+// Copyright 2026 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 hmaps
+
+import (
+ "slices"
+
+ "github.com/gohugoio/hugo/common/hashing"
+)
+
+// Ordered is a map that can be iterated in the order of insertion.
+// Note that insertion order is not affected if a key is re-inserted into the map.
+// In a nil map, all operations are no-ops.
+// This is not thread safe.
+type Ordered[K comparable, T any] struct {
+ // The keys in the order they were added.
+ keys []K
+ // The values.
+ values map[K]T
+}
+
+// NewOrdered creates a new Ordered map.
+func NewOrdered[K comparable, T any]() *Ordered[K, T] {
+ return &Ordered[K, T]{values: make(map[K]T)}
+}
+
+// Set sets the value for the given key.
+// Note that insertion order is not affected if a key is re-inserted into the map.
+func (m *Ordered[K, T]) Set(key K, value T) {
+ if m == nil {
+ return
+ }
+ // Check if key already exists.
+ if _, found := m.values[key]; !found {
+ m.keys = append(m.keys, key)
+ }
+ m.values[key] = value
+}
+
+// Get gets the value for the given key.
+func (m *Ordered[K, T]) Get(key K) (T, bool) {
+ if m == nil {
+ var v T
+ return v, false
+ }
+ value, found := m.values[key]
+ return value, found
+}
+
+// Has returns whether the given key exists in the map.
+func (m *Ordered[K, T]) Has(key K) bool {
+ if m == nil {
+ return false
+ }
+ _, found := m.values[key]
+ return found
+}
+
+// Delete deletes the value for the given key.
+func (m *Ordered[K, T]) Delete(key K) {
+ if m == nil {
+ return
+ }
+ delete(m.values, key)
+ for i, k := range m.keys {
+ if k == key {
+ m.keys = slices.Delete(m.keys, i, i+1)
+ break
+ }
+ }
+}
+
+// Clone creates a shallow copy of the map.
+func (m *Ordered[K, T]) Clone() *Ordered[K, T] {
+ if m == nil {
+ return nil
+ }
+ clone := NewOrdered[K, T]()
+ for _, k := range m.keys {
+ clone.Set(k, m.values[k])
+ }
+ return clone
+}
+
+// Keys returns the keys in the order they were added.
+func (m *Ordered[K, T]) Keys() []K {
+ if m == nil {
+ return nil
+ }
+ return m.keys
+}
+
+// Values returns the values in the order they were added.
+func (m *Ordered[K, T]) Values() []T {
+ if m == nil {
+ return nil
+ }
+ var values []T
+ for _, k := range m.keys {
+ values = append(values, m.values[k])
+ }
+ return values
+}
+
+// Len returns the number of items in the map.
+func (m *Ordered[K, T]) Len() int {
+ if m == nil {
+ return 0
+ }
+ return len(m.keys)
+}
+
+// Range calls f sequentially for each key and value present in the map.
+// If f returns false, range stops the iteration.
+// TODO(bep) replace with iter.Seq2 when we bump go Go 1.24.
+func (m *Ordered[K, T]) Range(f func(key K, value T) bool) {
+ if m == nil {
+ return
+ }
+ for _, k := range m.keys {
+ if !f(k, m.values[k]) {
+ return
+ }
+ }
+}
+
+// Hash calculates a hash from the values.
+func (m *Ordered[K, T]) Hash() (uint64, error) {
+ if m == nil {
+ return 0, nil
+ }
+ return hashing.Hash(m.values)
+}
--- /dev/null
+// Copyright 2026 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 hmaps
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestOrdered(t *testing.T) {
+ c := qt.New(t)
+
+ m := NewOrdered[string, int]()
+ m.Set("a", 1)
+ m.Set("b", 2)
+ m.Set("c", 3)
+
+ c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "b", "c"})
+ c.Assert(m.Values(), qt.DeepEquals, []int{1, 2, 3})
+
+ v, found := m.Get("b")
+ c.Assert(found, qt.Equals, true)
+ c.Assert(v, qt.Equals, 2)
+
+ m.Set("b", 22)
+ c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "b", "c"})
+ c.Assert(m.Values(), qt.DeepEquals, []int{1, 22, 3})
+
+ m.Delete("b")
+
+ c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "c"})
+ c.Assert(m.Values(), qt.DeepEquals, []int{1, 3})
+}
+
+func TestOrderedHash(t *testing.T) {
+ c := qt.New(t)
+
+ m := NewOrdered[string, int]()
+ m.Set("a", 1)
+ m.Set("b", 2)
+ m.Set("c", 3)
+
+ h1, err := m.Hash()
+ c.Assert(err, qt.IsNil)
+
+ m.Set("d", 4)
+
+ h2, err := m.Hash()
+ c.Assert(err, qt.IsNil)
+
+ c.Assert(h1, qt.Not(qt.Equals), h2)
+
+ m = NewOrdered[string, int]()
+ m.Set("b", 2)
+ m.Set("a", 1)
+ m.Set("c", 3)
+
+ h3, err := m.Hash()
+ c.Assert(err, qt.IsNil)
+ // Order does not matter.
+ c.Assert(h1, qt.Equals, h3)
+}
+
+func TestOrderedNil(t *testing.T) {
+ c := qt.New(t)
+
+ var m *Ordered[string, int]
+
+ m.Set("a", 1)
+ c.Assert(m.Keys(), qt.IsNil)
+ c.Assert(m.Values(), qt.IsNil)
+ v, found := m.Get("a")
+ c.Assert(found, qt.Equals, false)
+ c.Assert(v, qt.Equals, 0)
+ m.Delete("a")
+ var b bool
+ m.Range(func(k string, v int) bool {
+ b = true
+ return true
+ })
+ c.Assert(b, qt.Equals, false)
+ c.Assert(m.Len(), qt.Equals, 0)
+ c.Assert(m.Clone(), qt.IsNil)
+ h, err := m.Hash()
+ c.Assert(err, qt.IsNil)
+ c.Assert(h, qt.Equals, uint64(0))
+}
--- /dev/null
+// Copyright 2026 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 hmaps
+
+import (
+ "fmt"
+ "slices"
+
+ "github.com/bits-and-blooms/bitset"
+)
+
+type OrderedIntSet struct {
+ keys []int
+ values *bitset.BitSet
+}
+
+// NewOrderedIntSet creates a new OrderedIntSet.
+// Note that this is backed by https://github.com/bits-and-blooms/bitset
+func NewOrderedIntSet(vals ...int) *OrderedIntSet {
+ m := &OrderedIntSet{
+ keys: make([]int, 0, len(vals)),
+ values: bitset.New(uint(len(vals))),
+ }
+ for _, v := range vals {
+ m.Set(v)
+ }
+ return m
+}
+
+// Set sets the value for the given key.
+// Note that insertion order is not affected if a key is re-inserted into the set.
+func (m *OrderedIntSet) Set(key int) {
+ if m == nil {
+ panic("nil OrderedIntSet")
+ }
+ keyu := uint(key)
+ if m.values.Test(keyu) {
+ return
+ }
+ m.values.Set(keyu)
+ m.keys = append(m.keys, key)
+}
+
+// SetFrom sets the values from another OrderedIntSet.
+func (m *OrderedIntSet) SetFrom(other *OrderedIntSet) {
+ if m == nil || other == nil {
+ return
+ }
+ for _, key := range other.keys {
+ m.Set(key)
+ }
+}
+
+func (m *OrderedIntSet) Clone() *OrderedIntSet {
+ if m == nil {
+ return nil
+ }
+ newSet := &OrderedIntSet{
+ keys: slices.Clone(m.keys),
+ values: m.values.Clone(),
+ }
+ return newSet
+}
+
+// Next returns the next key in the set possibly including the given key.
+// It returns -1 if the key is not found or if there are no keys greater than the given key.
+func (m *OrderedIntSet) Next(i int) int {
+ n, ok := m.values.NextSet(uint(i))
+ if !ok {
+ return -1
+ }
+ return int(n)
+}
+
+// The reason we don't use iter.Seq is https://github.com/golang/go/issues/69015
+// This is 70% faster than using iter.Seq2[int, int] for the keys.
+// It returns false if the iteration was stopped early.
+func (m *OrderedIntSet) ForEachKey(yield func(int) bool) bool {
+ if m == nil {
+ return true
+ }
+ for _, key := range m.keys {
+ if !yield(key) {
+ return false
+ }
+ }
+ return true
+}
+
+func (m *OrderedIntSet) Has(key int) bool {
+ if m == nil {
+ return false
+ }
+ return m.values.Test(uint(key))
+}
+
+func (m *OrderedIntSet) Len() int {
+ if m == nil {
+ return 0
+ }
+ return len(m.keys)
+}
+
+// KeysSorted returns the keys in sorted order.
+func (m *OrderedIntSet) KeysSorted() []int {
+ if m == nil {
+ return nil
+ }
+ keys := slices.Clone(m.keys)
+ slices.Sort(keys)
+ return m.keys
+}
+
+func (m *OrderedIntSet) String() string {
+ if m == nil {
+ return "[]"
+ }
+ return fmt.Sprintf("%v", m.keys)
+}
+
+func (m *OrderedIntSet) Values() *bitset.BitSet {
+ if m == nil {
+ return nil
+ }
+ return m.values
+}
+
+func (m *OrderedIntSet) IsSuperSet(other *OrderedIntSet) bool {
+ if m == nil || other == nil {
+ return false
+ }
+ return m.values.IsSuperSet(other.values)
+}
+
+// Words returns the bitset as array of 64-bit words, giving direct access to the internal representation.
+// It is not a copy, so changes to the returned slice will affect the bitset.
+// It is meant for advanced users.
+func (m *OrderedIntSet) Words() []uint64 {
+ if m == nil {
+ return nil
+ }
+ return m.values.Words()
+}
--- /dev/null
+// Copyright 2026 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 hmaps
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestOrderedIntSet(t *testing.T) {
+ c := qt.New(t)
+
+ m := NewOrderedIntSet(2, 1, 3, 7)
+
+ c.Assert(m.Len(), qt.Equals, 4)
+ c.Assert(m.Has(1), qt.Equals, true)
+ c.Assert(m.Has(4), qt.Equals, false)
+ c.Assert(m.String(), qt.Equals, "[2 1 3 7]")
+ m.Set(4)
+ c.Assert(m.Len(), qt.Equals, 5)
+ c.Assert(m.Has(4), qt.Equals, true)
+ c.Assert(m.Next(0), qt.Equals, 1)
+ c.Assert(m.Next(1), qt.Equals, 1)
+ c.Assert(m.Next(2), qt.Equals, 2)
+ c.Assert(m.Next(3), qt.Equals, 3)
+ c.Assert(m.Next(4), qt.Equals, 4)
+ c.Assert(m.Next(7), qt.Equals, 7)
+ c.Assert(m.Next(8), qt.Equals, -1)
+ c.Assert(m.String(), qt.Equals, "[2 1 3 7 4]")
+
+ var nilset *OrderedIntSet
+ c.Assert(nilset.Len(), qt.Equals, 0)
+ c.Assert(nilset.Has(1), qt.Equals, false)
+ c.Assert(nilset.String(), qt.Equals, "[]")
+
+ var collected []int
+ m.ForEachKey(func(key int) bool {
+ collected = append(collected, key)
+ return true
+ })
+ c.Assert(collected, qt.DeepEquals, []int{2, 1, 3, 7, 4})
+}
+
+func BenchmarkOrderedIntSet(b *testing.B) {
+ smallSet := NewOrderedIntSet()
+ for i := range 8 {
+ smallSet.Set(i)
+ }
+ mediumSet := NewOrderedIntSet()
+ for i := range 64 {
+ mediumSet.Set(i)
+ }
+ largeSet := NewOrderedIntSet()
+ for i := range 1024 {
+ largeSet.Set(i)
+ }
+
+ b.Run("New", func(b *testing.B) {
+ for b.Loop() {
+ NewOrderedIntSet(1, 2, 3, 4, 5, 6, 7, 8)
+ }
+ })
+
+ b.Run("Has small", func(b *testing.B) {
+ for i := 0; b.Loop(); i++ {
+ smallSet.Has(i % 32)
+ }
+ })
+
+ b.Run("Has medium", func(b *testing.B) {
+ for i := 0; b.Loop(); i++ {
+ mediumSet.Has(i % 32)
+ }
+ })
+
+ b.Run("Next", func(b *testing.B) {
+ for i := 0; b.Loop(); i++ {
+ mediumSet.Next(i % 32)
+ }
+ })
+
+ b.Run("ForEachKey small", func(b *testing.B) {
+ for b.Loop() {
+ smallSet.ForEachKey(func(key int) bool {
+ return true
+ })
+ }
+ })
+
+ b.Run("ForEachKey medium", func(b *testing.B) {
+ for b.Loop() {
+ mediumSet.ForEachKey(func(key int) bool {
+ return true
+ })
+ }
+ })
+
+ b.Run("ForEachKey large", func(b *testing.B) {
+ for b.Loop() {
+ largeSet.ForEachKey(func(key int) bool {
+ return true
+ })
+ }
+ })
+}
--- /dev/null
+// Copyright 2026 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 hmaps
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/spf13/cast"
+)
+
+// Params is a map where all keys are lower case.
+type Params map[string]any
+
+// KeyParams is an utility struct for the WalkParams method.
+type KeyParams struct {
+ Key string
+ Params Params
+}
+
+// GetNested does a lower case and nested search in this map.
+// It will return nil if none found.
+// Make all of these methods internal somehow.
+func (p Params) GetNested(indices ...string) any {
+ v, _, _ := getNested(p, indices)
+ return v
+}
+
+// SetParams overwrites values in dst with values in src for common or new keys.
+// This is done recursively.
+func SetParams(dst, src Params) {
+ setParams(dst, src, 0)
+}
+
+func setParams(dst, src Params, depth int) {
+ const maxDepth = 1000
+ if depth > maxDepth {
+ panic(errors.New("max depth exceeded"))
+ }
+ for k, v := range src {
+ vv, found := dst[k]
+ if !found {
+ dst[k] = v
+ } else {
+ switch vvv := vv.(type) {
+ case Params:
+ if pv, ok := v.(Params); ok {
+ setParams(vvv, pv, depth+1)
+ } else {
+ dst[k] = v
+ }
+ default:
+ dst[k] = v
+ }
+ }
+ }
+}
+
+// IsZero returns true if p is considered empty.
+func (p Params) IsZero() bool {
+ if len(p) == 0 {
+ return true
+ }
+
+ if len(p) > 1 {
+ return false
+ }
+
+ for k := range p {
+ return k == MergeStrategyKey
+ }
+
+ return false
+}
+
+// MergeParamsWithStrategy transfers values from src to dst for new keys using the merge strategy given.
+// This is done recursively.
+func MergeParamsWithStrategy(strategy string, dst, src Params) {
+ dst.merge(ParamsMergeStrategy(strategy), src)
+}
+
+// MergeParams transfers values from src to dst for new keys using the merge encoded in dst.
+// This is done recursively.
+func MergeParams(dst, src Params) {
+ ms, _ := dst.GetMergeStrategy()
+ dst.merge(ms, src)
+}
+
+func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
+ ns, found := p.GetMergeStrategy()
+
+ ms := ns
+ if !found && ps != "" {
+ ms = ps
+ }
+
+ noUpdate := ms == ParamsMergeStrategyNone
+ noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow)
+
+ for k, v := range pp {
+ if k == MergeStrategyKey {
+ continue
+ }
+ vv, found := p[k]
+
+ if found {
+ // Key matches, if both sides are Params, we try to merge.
+ if vvv, ok := vv.(Params); ok {
+ if pv, ok := v.(Params); ok {
+ vvv.merge(ms, pv)
+ }
+ }
+ } else if !noUpdate {
+ p[k] = v
+ }
+
+ }
+}
+
+// For internal use.
+func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) {
+ if v, found := p[MergeStrategyKey]; found {
+ if s, ok := v.(ParamsMergeStrategy); ok {
+ return s, true
+ }
+ }
+ return ParamsMergeStrategyShallow, false
+}
+
+// For internal use.
+func (p Params) DeleteMergeStrategy() bool {
+ if _, found := p[MergeStrategyKey]; found {
+ delete(p, MergeStrategyKey)
+ return true
+ }
+ return false
+}
+
+// For internal use.
+func (p Params) SetMergeStrategy(s ParamsMergeStrategy) {
+ switch s {
+ case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
+ default:
+ panic(fmt.Sprintf("invalid merge strategy %q", s))
+ }
+ p[MergeStrategyKey] = s
+}
+
+func getNested(m map[string]any, indices []string) (any, string, map[string]any) {
+ if len(indices) == 0 {
+ return nil, "", nil
+ }
+
+ first := indices[0]
+ v, found := m[strings.ToLower(cast.ToString(first))]
+ if !found {
+ if len(indices) == 1 {
+ return nil, first, m
+ }
+ return nil, "", nil
+
+ }
+
+ if len(indices) == 1 {
+ return v, first, m
+ }
+
+ switch m2 := v.(type) {
+ case Params:
+ return getNested(m2, indices[1:])
+ case map[string]any:
+ return getNested(m2, indices[1:])
+ default:
+ return nil, "", nil
+ }
+}
+
+// CreateNestedParamsFromSegements creates empty nested maps for the given keySegments in the target map.
+func CreateNestedParamsFromSegements(target Params, keySegments ...string) Params {
+ if len(keySegments) == 0 {
+ return target
+ }
+
+ m := target
+ for i, key := range keySegments {
+ v, found := m[key]
+ if !found {
+ nm := Params{}
+ m[key] = nm
+ m = nm
+ if i == len(keySegments)-1 {
+ return nm
+ }
+ continue
+ }
+ m = v.(Params)
+ }
+
+ return m
+}
+
+// CreateNestedParamsSepString creates empty nested maps for the given keyStr in the target map
+// It returns the last map created.
+func CreateNestedParamsSepString(keyStr, separator string, target Params) Params {
+ keySegments := strings.Split(keyStr, separator)
+ return CreateNestedParamsFromSegements(target, keySegments...)
+}
+
+// SetNestedParamIfNotSet sets the value for the given keyStr in the target map if it does not exist.
+// It assumes that all but the last key in keyStr is a Params map or should be one.
+func SetNestedParamIfNotSet(keyStr, separator string, value any, target Params) Params {
+ keySegments := strings.Split(keyStr, separator)
+ if len(keySegments) == 0 {
+ return target
+ }
+ base := keySegments[:len(keySegments)-1]
+ last := keySegments[len(keySegments)-1]
+
+ m := CreateNestedParamsFromSegements(target, base...)
+
+ if _, ok := m[last]; !ok {
+ m[last] = value
+ }
+
+ return target
+}
+
+// GetNestedParam gets the first match of the keyStr in the candidates given.
+// It will first try the exact match and then try to find it as a nested map value,
+// using the given separator, e.g. "mymap.name".
+// It assumes that all the maps given have lower cased keys.
+func GetNestedParam(keyStr, separator string, candidates ...Params) (any, error) {
+ keyStr = strings.ToLower(keyStr)
+
+ // Try exact match first
+ for _, m := range candidates {
+ if v, ok := m[keyStr]; ok {
+ return v, nil
+ }
+ }
+
+ keySegments := strings.Split(keyStr, separator)
+ for _, m := range candidates {
+ if v := m.GetNested(keySegments...); v != nil {
+ return v, nil
+ }
+ }
+
+ return nil, nil
+}
+
+func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) any) (any, string, map[string]any, error) {
+ keySegments := strings.Split(keyStr, separator)
+ if len(keySegments) == 0 {
+ return nil, "", nil, nil
+ }
+
+ first := lookupFn(keySegments[0])
+ if first == nil {
+ return nil, "", nil, nil
+ }
+
+ if len(keySegments) == 1 {
+ return first, keySegments[0], nil, nil
+ }
+
+ switch m := first.(type) {
+ case map[string]any:
+ v, key, owner := getNested(m, keySegments[1:])
+ return v, key, owner, nil
+ case Params:
+ v, key, owner := getNested(m, keySegments[1:])
+ return v, key, owner, nil
+ }
+
+ return nil, "", nil, nil
+}
+
+// ParamsMergeStrategy tells what strategy to use in Params.Merge.
+type ParamsMergeStrategy string
+
+const (
+ // Do not merge.
+ ParamsMergeStrategyNone ParamsMergeStrategy = "none"
+ // Only add new keys.
+ ParamsMergeStrategyShallow ParamsMergeStrategy = "shallow"
+ // Add new keys, merge existing.
+ ParamsMergeStrategyDeep ParamsMergeStrategy = "deep"
+
+ MergeStrategyKey = "_merge"
+)
+
+// CleanConfigStringMapString removes any processing instructions from m,
+// m will never be modified.
+func CleanConfigStringMapString(m map[string]string) map[string]string {
+ if len(m) == 0 {
+ return m
+ }
+ if _, found := m[MergeStrategyKey]; !found {
+ return m
+ }
+ // Create a new map and copy all the keys except the merge strategy key.
+ m2 := make(map[string]string, len(m)-1)
+ for k, v := range m {
+ if k != MergeStrategyKey {
+ m2[k] = v
+ }
+ }
+ return m2
+}
+
+// CleanConfigStringMap is the same as CleanConfigStringMapString but for
+// map[string]any.
+func CleanConfigStringMap(m map[string]any) map[string]any {
+ return doCleanConfigStringMap(m, 0)
+}
+
+func doCleanConfigStringMap(m map[string]any, depth int) map[string]any {
+ if len(m) == 0 {
+ return m
+ }
+ const maxDepth = 1000
+ if depth > maxDepth {
+ panic(errors.New("max depth exceeded"))
+ }
+ if _, found := m[MergeStrategyKey]; !found {
+ return m
+ }
+ // Create a new map and copy all the keys except the merge strategy key.
+ m2 := make(map[string]any, len(m)-1)
+ for k, v := range m {
+ if k != MergeStrategyKey {
+ m2[k] = v
+ }
+ switch v2 := v.(type) {
+ case map[string]any:
+ m2[k] = doCleanConfigStringMap(v2, depth+1)
+ case Params:
+ var p Params = doCleanConfigStringMap(v2, depth+1)
+ m2[k] = p
+ case map[string]string:
+ m2[k] = CleanConfigStringMapString(v2)
+ }
+
+ }
+ return m2
+}
+
+func toMergeStrategy(v any) ParamsMergeStrategy {
+ s := ParamsMergeStrategy(cast.ToString(v))
+ switch s {
+ case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
+ return s
+ default:
+ return ParamsMergeStrategyDeep
+ }
+}
+
+// PrepareParams
+// * makes all the keys in the given map lower cased and will do so recursively.
+// * This will modify the map given.
+// * Any nested map[interface{}]interface{}, map[string]interface{},map[string]string will be converted to Params.
+// * Any _merge value will be converted to proper type and value.
+func PrepareParams(m Params) {
+ for k, v := range m {
+ var retyped bool
+ lKey := strings.ToLower(k)
+ if lKey == MergeStrategyKey {
+ v = toMergeStrategy(v)
+ retyped = true
+ } else {
+ switch vv := v.(type) {
+ case map[any]any:
+ var p Params = cast.ToStringMap(v)
+ v = p
+ PrepareParams(p)
+ retyped = true
+ case map[string]any:
+ var p Params = v.(map[string]any)
+ v = p
+ PrepareParams(p)
+ retyped = true
+ case map[string]string:
+ p := make(Params)
+ for k, v := range vv {
+ p[k] = v
+ }
+ v = p
+ PrepareParams(p)
+ retyped = true
+ }
+ }
+
+ if retyped || k != lKey {
+ delete(m, k)
+ m[lKey] = v
+ }
+ }
+}
+
+// CloneParamsDeep does a deep clone of the given Params,
+// meaning that any nested Params will be cloned as well.
+func CloneParamsDeep(m Params) Params {
+ return cloneParamsDeep(m, 0)
+}
+
+func cloneParamsDeep(m Params, depth int) Params {
+ const maxDepth = 1000
+ if depth > maxDepth {
+ panic(errors.New("max depth exceeded"))
+ }
+ m2 := make(Params)
+ for k, v := range m {
+ switch vv := v.(type) {
+ case Params:
+ m2[k] = cloneParamsDeep(vv, depth+1)
+ default:
+ m2[k] = v
+ }
+ }
+ return m2
+}
+
+// PrepareParamsClone is like PrepareParams, but it does not modify the input.
+func PrepareParamsClone(m Params) Params {
+ m2 := make(Params)
+ for k, v := range m {
+ var retyped bool
+ lKey := strings.ToLower(k)
+ if lKey == MergeStrategyKey {
+ v = toMergeStrategy(v)
+ retyped = true
+ } else {
+ switch vv := v.(type) {
+ case map[any]any:
+ var p Params = cast.ToStringMap(v)
+ v = PrepareParamsClone(p)
+ retyped = true
+ case map[string]any:
+ var p Params = v.(map[string]any)
+ v = PrepareParamsClone(p)
+ retyped = true
+ case map[string]string:
+ p := make(Params)
+ for k, v := range vv {
+ p[k] = v
+ }
+ v = p
+ PrepareParams(p)
+ retyped = true
+ }
+ }
+
+ if retyped || k != lKey {
+ m2[lKey] = v
+ } else {
+ m2[k] = v
+ }
+ }
+ return m2
+}
--- /dev/null
+// Copyright 2026 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 hmaps
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestGetNestedParam(t *testing.T) {
+ m := map[string]any{
+ "string": "value",
+ "first": 1,
+ "with_underscore": 2,
+ "nested": map[string]any{
+ "color": "blue",
+ "nestednested": map[string]any{
+ "color": "green",
+ },
+ },
+ }
+
+ c := qt.New(t)
+
+ must := func(keyStr, separator string, candidates ...Params) any {
+ v, err := GetNestedParam(keyStr, separator, candidates...)
+ c.Assert(err, qt.IsNil)
+ return v
+ }
+
+ c.Assert(must("first", "_", m), qt.Equals, 1)
+ c.Assert(must("First", "_", m), qt.Equals, 1)
+ c.Assert(must("with_underscore", "_", m), qt.Equals, 2)
+ c.Assert(must("nested_color", "_", m), qt.Equals, "blue")
+ c.Assert(must("nested.nestednested.color", ".", m), qt.Equals, "green")
+ c.Assert(must("string.name", ".", m), qt.IsNil)
+ c.Assert(must("nested.foo", ".", m), qt.IsNil)
+}
+
+// https://github.com/gohugoio/hugo/issues/7903
+func TestGetNestedParamFnNestedNewKey(t *testing.T) {
+ c := qt.New(t)
+
+ nested := map[string]any{
+ "color": "blue",
+ }
+ m := map[string]any{
+ "nested": nested,
+ }
+
+ existing, nestedKey, owner, err := GetNestedParamFn("nested.new", ".", func(key string) any {
+ return m[key]
+ })
+
+ c.Assert(err, qt.IsNil)
+ c.Assert(existing, qt.IsNil)
+ c.Assert(nestedKey, qt.Equals, "new")
+ c.Assert(owner, qt.DeepEquals, nested)
+}
+
+func TestParamsSetAndMerge(t *testing.T) {
+ c := qt.New(t)
+
+ createParamsPair := func() (Params, Params) {
+ p1 := Params{"a": "av", "c": "cv", "nested": Params{"al2": "al2v", "cl2": "cl2v"}}
+ p2 := Params{"b": "bv", "a": "abv", "nested": Params{"bl2": "bl2v", "al2": "al2bv"}, MergeStrategyKey: ParamsMergeStrategyDeep}
+ return p1, p2
+ }
+
+ p1, p2 := createParamsPair()
+
+ SetParams(p1, p2)
+
+ c.Assert(p1, qt.DeepEquals, Params{
+ "a": "abv",
+ "c": "cv",
+ "nested": Params{
+ "al2": "al2bv",
+ "cl2": "cl2v",
+ "bl2": "bl2v",
+ },
+ "b": "bv",
+ MergeStrategyKey: ParamsMergeStrategyDeep,
+ })
+
+ p1, p2 = createParamsPair()
+
+ MergeParamsWithStrategy("", p1, p2)
+
+ // Default is to do a shallow merge.
+ c.Assert(p1, qt.DeepEquals, Params{
+ "c": "cv",
+ "nested": Params{
+ "al2": "al2v",
+ "cl2": "cl2v",
+ },
+ "b": "bv",
+ "a": "av",
+ })
+
+ p1, p2 = createParamsPair()
+ p1.SetMergeStrategy(ParamsMergeStrategyNone)
+ MergeParamsWithStrategy("", p1, p2)
+ p1.DeleteMergeStrategy()
+
+ c.Assert(p1, qt.DeepEquals, Params{
+ "a": "av",
+ "c": "cv",
+ "nested": Params{
+ "al2": "al2v",
+ "cl2": "cl2v",
+ },
+ })
+
+ p1, p2 = createParamsPair()
+ p1.SetMergeStrategy(ParamsMergeStrategyShallow)
+ MergeParamsWithStrategy("", p1, p2)
+ p1.DeleteMergeStrategy()
+
+ c.Assert(p1, qt.DeepEquals, Params{
+ "a": "av",
+ "c": "cv",
+ "nested": Params{
+ "al2": "al2v",
+ "cl2": "cl2v",
+ },
+ "b": "bv",
+ })
+
+ p1, p2 = createParamsPair()
+ p1.SetMergeStrategy(ParamsMergeStrategyDeep)
+ MergeParamsWithStrategy("", p1, p2)
+ p1.DeleteMergeStrategy()
+
+ c.Assert(p1, qt.DeepEquals, Params{
+ "nested": Params{
+ "al2": "al2v",
+ "cl2": "cl2v",
+ "bl2": "bl2v",
+ },
+ "b": "bv",
+ "a": "av",
+ "c": "cv",
+ })
+}
+
+func TestParamsIsZero(t *testing.T) {
+ c := qt.New(t)
+
+ var nilParams Params
+
+ c.Assert(Params{}.IsZero(), qt.IsTrue)
+ c.Assert(nilParams.IsZero(), qt.IsTrue)
+ c.Assert(Params{"foo": "bar"}.IsZero(), qt.IsFalse)
+ c.Assert(Params{"_merge": "foo", "foo": "bar"}.IsZero(), qt.IsFalse)
+ c.Assert(Params{"_merge": "foo"}.IsZero(), qt.IsTrue)
+}
+
+func TestSetNestedParamIfNotSet(t *testing.T) {
+ c := qt.New(t)
+
+ m := Params{}
+
+ SetNestedParamIfNotSet("a.b.c", ".", "value", m)
+ c.Assert(m, qt.DeepEquals, Params{
+ "a": Params{
+ "b": Params{
+ "c": "value",
+ },
+ },
+ })
+
+ m = Params{
+ "a": Params{
+ "b": Params{
+ "c": "existingValue",
+ },
+ },
+ }
+
+ SetNestedParamIfNotSet("a.b.c", ".", "value", m)
+ c.Assert(m, qt.DeepEquals, Params{
+ "a": Params{
+ "b": Params{
+ "c": "existingValue",
+ },
+ },
+ })
+}
"sync"
"time"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/htime"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
)
var contextInterface = reflect.TypeOf((*context.Context)(nil)).Elem()
-var isContextCache = maps.NewCache[reflect.Type, bool]()
+var isContextCache = hmaps.NewCache[reflect.Type, bool]()
type k string
+++ /dev/null
-// 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 maps
-
-import (
- "sync"
-)
-
-// Cache is a simple thread safe cache backed by a map.
-type Cache[K comparable, T any] struct {
- m map[K]T
- opts CacheOptions
- hasBeenInitialized bool
- sync.RWMutex
-}
-
-// CacheOptions are the options for the Cache.
-type CacheOptions struct {
- // If set, the cache will not grow beyond this size.
- Size uint64
-}
-
-var defaultCacheOptions = CacheOptions{}
-
-// NewCache creates a new Cache with default options.
-func NewCache[K comparable, T any]() *Cache[K, T] {
- return &Cache[K, T]{m: make(map[K]T), opts: defaultCacheOptions}
-}
-
-// NewCacheWithOptions creates a new Cache with the given options.
-func NewCacheWithOptions[K comparable, T any](opts CacheOptions) *Cache[K, T] {
- return &Cache[K, T]{m: make(map[K]T), opts: opts}
-}
-
-// Delete deletes the given key from the cache.
-// If c is nil, this method is a no-op.
-func (c *Cache[K, T]) Get(key K) (T, bool) {
- if c == nil {
- var zero T
- return zero, false
- }
- c.RLock()
- v, found := c.get(key)
- c.RUnlock()
- return v, found
-}
-
-func (c *Cache[K, T]) get(key K) (T, bool) {
- v, found := c.m[key]
- return v, found
-}
-
-// GetOrCreate gets the value for the given key if it exists, or creates it if not.
-func (c *Cache[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
- c.RLock()
- v, found := c.m[key]
- c.RUnlock()
- if found {
- return v, nil
- }
- c.Lock()
- defer c.Unlock()
- v, found = c.m[key]
- if found {
- return v, nil
- }
- v, err := create()
- if err != nil {
- return v, err
- }
- c.clearIfNeeded()
- c.m[key] = v
- return v, nil
-}
-
-// Contains returns whether the given key exists in the cache.
-func (c *Cache[K, T]) Contains(key K) bool {
- c.RLock()
- _, found := c.m[key]
- c.RUnlock()
- return found
-}
-
-// InitAndGet initializes the cache if not already done and returns the value for the given key.
-// The init state will be reset on Reset or Drain.
-func (c *Cache[K, T]) InitAndGet(key K, init func(get func(key K) (T, bool), set func(key K, value T)) error) (T, error) {
- var v T
- c.RLock()
- if !c.hasBeenInitialized {
- c.RUnlock()
- if err := func() error {
- c.Lock()
- defer c.Unlock()
- // Double check in case another goroutine has initialized it in the meantime.
- if !c.hasBeenInitialized {
- err := init(c.get, c.set)
- if err != nil {
- return err
- }
- c.hasBeenInitialized = true
- }
- return nil
- }(); err != nil {
- return v, err
- }
- // Reacquire the read lock.
- c.RLock()
- }
-
- v = c.m[key]
- c.RUnlock()
-
- return v, nil
-}
-
-// Set sets the given key to the given value.
-func (c *Cache[K, T]) Set(key K, value T) {
- c.Lock()
- c.set(key, value)
- c.Unlock()
-}
-
-// SetIfAbsent sets the given key to the given value if the key does not already exist in the cache.
-func (c *Cache[K, T]) SetIfAbsent(key K, value T) {
- c.RLock()
- if _, found := c.get(key); !found {
- c.RUnlock()
- c.Set(key, value)
- } else {
- c.RUnlock()
- }
-}
-
-func (c *Cache[K, T]) clearIfNeeded() {
- if c.opts.Size > 0 && uint64(len(c.m)) >= c.opts.Size {
- // clear the map
- clear(c.m)
- }
-}
-
-func (c *Cache[K, T]) set(key K, value T) {
- c.clearIfNeeded()
- c.m[key] = value
-}
-
-// ForEeach calls the given function for each key/value pair in the cache.
-// If the function returns false, the iteration stops.
-func (c *Cache[K, T]) ForEeach(f func(K, T) bool) {
- c.RLock()
- defer c.RUnlock()
- for k, v := range c.m {
- if !f(k, v) {
- return
- }
- }
-}
-
-func (c *Cache[K, T]) Drain() map[K]T {
- c.Lock()
- m := c.m
- c.m = make(map[K]T)
- c.hasBeenInitialized = false
- c.Unlock()
- return m
-}
-
-func (c *Cache[K, T]) Len() int {
- c.RLock()
- defer c.RUnlock()
- return len(c.m)
-}
-
-func (c *Cache[K, T]) Reset() {
- c.Lock()
- clear(c.m)
- c.hasBeenInitialized = false
- c.Unlock()
-}
-
-// SliceCache is a simple thread safe cache backed by a map.
-type SliceCache[T any] struct {
- m map[string][]T
- sync.RWMutex
-}
-
-func NewSliceCache[T any]() *SliceCache[T] {
- return &SliceCache[T]{m: make(map[string][]T)}
-}
-
-func (c *SliceCache[T]) Get(key string) ([]T, bool) {
- c.RLock()
- v, found := c.m[key]
- c.RUnlock()
- return v, found
-}
-
-func (c *SliceCache[T]) Append(key string, values ...T) {
- c.Lock()
- c.m[key] = append(c.m[key], values...)
- c.Unlock()
-}
-
-func (c *SliceCache[T]) Reset() {
- c.Lock()
- c.m = make(map[string][]T)
- c.Unlock()
-}
+++ /dev/null
-// Copyright 2025 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 maps
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestCacheSize(t *testing.T) {
- c := qt.New(t)
-
- cache := NewCacheWithOptions[string, string](CacheOptions{Size: 10})
-
- for i := range 30 {
- cache.Set(string(rune('a'+i)), "value")
- }
-
- c.Assert(len(cache.m), qt.Equals, 10)
-
- for i := 20; i < 50; i++ {
- cache.GetOrCreate(string(rune('a'+i)), func() (string, error) {
- return "value", nil
- })
- }
-
- c.Assert(len(cache.m), qt.Equals, 10)
-
- for i := 100; i < 200; i++ {
- cache.SetIfAbsent(string(rune('a'+i)), "value")
- }
-
- c.Assert(len(cache.m), qt.Equals, 10)
-
- cache.InitAndGet("foo", func(
- get func(key string) (string, bool), set func(key string, value string),
- ) error {
- for i := 50; i < 100; i++ {
- set(string(rune('a'+i)), "value")
- }
- return nil
- })
-
- c.Assert(len(cache.m), qt.Equals, 10)
-}
+++ /dev/null
-// Copyright 2025 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 maps
-
-import (
- "iter"
- "sync"
-)
-
-func NewMap[K comparable, T any]() *Map[K, T] {
- return &Map[K, T]{
- m: make(map[K]T),
- }
-}
-
-// Map is a thread safe map backed by a Go map.
-type Map[K comparable, T any] struct {
- m map[K]T
- mu sync.RWMutex
-}
-
-// Get gets the value for the given key.
-// It returns the zero value of T if the key is not found.
-func (m *Map[K, T]) Get(key K) T {
- v, _ := m.Lookup(key)
- return v
-}
-
-// Lookup looks up the given key in the map.
-// It returns the value and a boolean indicating whether the key was found.
-func (m *Map[K, T]) Lookup(key K) (T, bool) {
- m.mu.RLock()
- v, found := m.m[key]
- m.mu.RUnlock()
- return v, found
-}
-
-// GetOrCreate gets the value for the given key if it exists, or creates it if not.
-func (m *Map[K, T]) GetOrCreate(key K, create func() (T, error)) (T, error) {
- v, found := m.Lookup(key)
- if found {
- return v, nil
- }
- m.mu.Lock()
- defer m.mu.Unlock()
- v, found = m.m[key]
- if found {
- return v, nil
- }
- v, err := create()
- if err != nil {
- return v, err
- }
- m.m[key] = v
- return v, nil
-}
-
-// Set sets the given key to the given value.
-func (m *Map[K, T]) Set(key K, value T) {
- m.mu.Lock()
- m.m[key] = value
- m.mu.Unlock()
-}
-
-// Delete deletes the given key from the map.
-// It returns true if the key was found and deleted, false otherwise.
-func (m *Map[K, T]) Delete(key K) bool {
- m.mu.Lock()
- defer m.mu.Unlock()
- if _, found := m.m[key]; found {
- delete(m.m, key)
- return true
- }
- return false
-}
-
-// WithWriteLock executes the given function with a write lock on the map.
-func (m *Map[K, T]) WithWriteLock(f func(m map[K]T) error) error {
- m.mu.Lock()
- defer m.mu.Unlock()
- return f(m.m)
-}
-
-// SetIfAbsent sets the given key to the given value if the key does not already exist in the map.
-// It returns true if the value was set, false otherwise.
-func (m *Map[K, T]) SetIfAbsent(key K, value T) bool {
- m.mu.RLock()
- if _, found := m.m[key]; !found {
- m.mu.RUnlock()
- return m.doSetIfAbsent(key, value)
- }
- m.mu.RUnlock()
- return false
-}
-
-func (m *Map[K, T]) doSetIfAbsent(key K, value T) bool {
- m.mu.Lock()
- defer m.mu.Unlock()
- if _, found := m.m[key]; !found {
- m.m[key] = value
- return true
- }
- return false
-}
-
-// All returns an iterator over all key/value pairs in the map.
-// A read lock is held during the iteration.
-func (m *Map[K, T]) All() iter.Seq2[K, T] {
- return func(yield func(K, T) bool) {
- m.mu.RLock()
- defer m.mu.RUnlock()
- for k, v := range m.m {
- if !yield(k, v) {
- return
- }
- }
- }
-}
+++ /dev/null
-// Copyright 2025 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 maps
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestMap(t *testing.T) {
- c := qt.New(t)
-
- m := NewMap[string, int]()
-
- m.Set("b", 42)
- v, found := m.Lookup("b")
- c.Assert(found, qt.Equals, true)
- c.Assert(v, qt.Equals, 42)
- v = m.Get("b")
- c.Assert(v, qt.Equals, 42)
- v, found = m.Lookup("c")
- c.Assert(found, qt.Equals, false)
- c.Assert(v, qt.Equals, 0)
- v = m.Get("c")
- c.Assert(v, qt.Equals, 0)
- v, err := m.GetOrCreate("d", func() (int, error) {
- return 100, nil
- })
- c.Assert(err, qt.IsNil)
- c.Assert(v, qt.Equals, 100)
- v, found = m.Lookup("d")
- c.Assert(found, qt.Equals, true)
- c.Assert(v, qt.Equals, 100)
-
- v, err = m.GetOrCreate("d", func() (int, error) {
- return 200, nil
- })
- c.Assert(err, qt.IsNil)
- c.Assert(v, qt.Equals, 100)
-
- wasSet := m.SetIfAbsent("e", 300)
- c.Assert(wasSet, qt.Equals, true)
- v, found = m.Lookup("e")
- c.Assert(found, qt.Equals, true)
- c.Assert(v, qt.Equals, 300)
-
- wasSet = m.SetIfAbsent("e", 400)
- c.Assert(wasSet, qt.Equals, false)
- v, found = m.Lookup("e")
- c.Assert(found, qt.Equals, true)
- c.Assert(v, qt.Equals, 300)
-
- m.WithWriteLock(func(m map[string]int) error {
- m["f"] = 500
- return nil
- })
- v, found = m.Lookup("f")
- c.Assert(found, qt.Equals, true)
- c.Assert(v, qt.Equals, 500)
-}
+++ /dev/null
-// Copyright 2018 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 maps
-
-import (
- "fmt"
- "strings"
-
- "github.com/gohugoio/hugo/common/types"
-
- "github.com/gobwas/glob"
- "github.com/spf13/cast"
-)
-
-// ToStringMapE converts in to map[string]interface{}.
-func ToStringMapE(in any) (map[string]any, error) {
- switch vv := in.(type) {
- case Params:
- return vv, nil
- case map[string]string:
- m := map[string]any{}
- for k, v := range vv {
- m[k] = v
- }
- return m, nil
-
- default:
- return cast.ToStringMapE(in)
- }
-}
-
-// ToParamsAndPrepare converts in to Params and prepares it for use.
-// If in is nil, an empty map is returned.
-// See PrepareParams.
-func ToParamsAndPrepare(in any) (Params, error) {
- if types.IsNil(in) {
- return Params{}, nil
- }
- m, err := ToStringMapE(in)
- if err != nil {
- return nil, err
- }
- PrepareParams(m)
- return m, nil
-}
-
-// MustToParamsAndPrepare calls ToParamsAndPrepare and panics if it fails.
-func MustToParamsAndPrepare(in any) Params {
- p, err := ToParamsAndPrepare(in)
- if err != nil {
- panic(fmt.Sprintf("cannot convert %T to maps.Params: %s", in, err))
- }
- return p
-}
-
-// ToStringMap converts in to map[string]interface{}.
-func ToStringMap(in any) map[string]any {
- m, _ := ToStringMapE(in)
- return m
-}
-
-// ToStringMapStringE converts in to map[string]string.
-func ToStringMapStringE(in any) (map[string]string, error) {
- m, err := ToStringMapE(in)
- if err != nil {
- return nil, err
- }
- return cast.ToStringMapStringE(m)
-}
-
-// ToStringMapString converts in to map[string]string.
-func ToStringMapString(in any) map[string]string {
- m, _ := ToStringMapStringE(in)
- return m
-}
-
-// ToStringMapBool converts in to bool.
-func ToStringMapBool(in any) map[string]bool {
- m, _ := ToStringMapE(in)
- return cast.ToStringMapBool(m)
-}
-
-// ToSliceStringMap converts in to []map[string]interface{}.
-func ToSliceStringMap(in any) ([]map[string]any, error) {
- switch v := in.(type) {
- case []map[string]any:
- return v, nil
- case Params:
- return []map[string]any{v}, nil
- case map[string]any:
- return []map[string]any{v}, nil
- case []any:
- var s []map[string]any
- for _, entry := range v {
- if vv, ok := entry.(map[string]any); ok {
- s = append(s, vv)
- }
- }
- return s, nil
- default:
- return nil, fmt.Errorf("unable to cast %#v of type %T to []map[string]interface{}", in, in)
- }
-}
-
-// LookupEqualFold finds key in m with case insensitive equality checks.
-func LookupEqualFold[T any | string](m map[string]T, key string) (T, string, bool) {
- if v, found := m[key]; found {
- return v, key, true
- }
- for k, v := range m {
- if strings.EqualFold(k, key) {
- return v, k, true
- }
- }
- var s T
- return s, "", false
-}
-
-// MergeShallow merges src into dst, but only if the key does not already exist in dst.
-// The keys are compared case insensitively.
-func MergeShallow(dst, src map[string]any) {
- for k, v := range src {
- found := false
- for dk := range dst {
- if strings.EqualFold(dk, k) {
- found = true
- break
- }
- }
- if !found {
- dst[k] = v
- }
- }
-}
-
-type keyRename struct {
- pattern glob.Glob
- newKey string
-}
-
-// KeyRenamer supports renaming of keys in a map.
-type KeyRenamer struct {
- renames []keyRename
-}
-
-// NewKeyRenamer creates a new KeyRenamer given a list of pattern and new key
-// value pairs.
-func NewKeyRenamer(patternKeys ...string) (KeyRenamer, error) {
- var renames []keyRename
- for i := 0; i < len(patternKeys); i += 2 {
- g, err := glob.Compile(strings.ToLower(patternKeys[i]), '/')
- if err != nil {
- return KeyRenamer{}, err
- }
- renames = append(renames, keyRename{pattern: g, newKey: patternKeys[i+1]})
- }
-
- return KeyRenamer{renames: renames}, nil
-}
-
-func (r KeyRenamer) getNewKey(keyPath string) string {
- for _, matcher := range r.renames {
- if matcher.pattern.Match(keyPath) {
- return matcher.newKey
- }
- }
-
- return ""
-}
-
-// Rename renames the keys in the given map according
-// to the patterns in the current KeyRenamer.
-func (r KeyRenamer) Rename(m map[string]any) {
- r.renamePath("", m)
-}
-
-func (KeyRenamer) keyPath(k1, k2 string) string {
- k1, k2 = strings.ToLower(k1), strings.ToLower(k2)
- if k1 == "" {
- return k2
- }
- return k1 + "/" + k2
-}
-
-func (r KeyRenamer) renamePath(parentKeyPath string, m map[string]any) {
- for k, v := range m {
- keyPath := r.keyPath(parentKeyPath, k)
- switch vv := v.(type) {
- case map[any]any:
- r.renamePath(keyPath, cast.ToStringMap(vv))
- case map[string]any:
- r.renamePath(keyPath, vv)
- }
-
- newKey := r.getNewKey(keyPath)
-
- if newKey != "" {
- delete(m, k)
- m[newKey] = v
- }
- }
-}
-
-// ConvertFloat64WithNoDecimalsToInt converts float64 values with no decimals to int recursively.
-func ConvertFloat64WithNoDecimalsToInt(m map[string]any) {
- for k, v := range m {
- switch vv := v.(type) {
- case float64:
- if v == float64(int64(vv)) {
- m[k] = int64(vv)
- }
- case map[string]any:
- ConvertFloat64WithNoDecimalsToInt(vv)
- case []any:
- for i, vvv := range vv {
- switch vvvv := vvv.(type) {
- case float64:
- if vvv == float64(int64(vvvv)) {
- vv[i] = int64(vvvv)
- }
- case map[string]any:
- ConvertFloat64WithNoDecimalsToInt(vvvv)
- }
- }
- }
- }
-}
+++ /dev/null
-// Copyright 2018 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 maps
-
-import (
- "fmt"
- "reflect"
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestPrepareParams(t *testing.T) {
- tests := []struct {
- input Params
- expected Params
- }{
- {
- map[string]any{
- "abC": 32,
- },
- Params{
- "abc": 32,
- },
- },
- {
- map[string]any{
- "abC": 32,
- "deF": map[any]any{
- 23: "A value",
- 24: map[string]any{
- "AbCDe": "A value",
- "eFgHi": "Another value",
- },
- },
- "gHi": map[string]any{
- "J": 25,
- },
- "jKl": map[string]string{
- "M": "26",
- },
- },
- Params{
- "abc": 32,
- "def": Params{
- "23": "A value",
- "24": Params{
- "abcde": "A value",
- "efghi": "Another value",
- },
- },
- "ghi": Params{
- "j": 25,
- },
- "jkl": Params{
- "m": "26",
- },
- },
- },
- }
-
- for i, test := range tests {
- t.Run(fmt.Sprint(i), func(t *testing.T) {
- // PrepareParams modifies input.
- prepareClone := PrepareParamsClone(test.input)
- PrepareParams(test.input)
- if !reflect.DeepEqual(test.expected, test.input) {
- t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input)
- }
- if !reflect.DeepEqual(test.expected, prepareClone) {
- t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, prepareClone)
- }
- })
- }
-}
-
-func TestToSliceStringMap(t *testing.T) {
- c := qt.New(t)
-
- tests := []struct {
- input any
- expected []map[string]any
- }{
- {
- input: []map[string]any{
- {"abc": 123},
- },
- expected: []map[string]any{
- {"abc": 123},
- },
- }, {
- input: []any{
- map[string]any{
- "def": 456,
- },
- },
- expected: []map[string]any{
- {"def": 456},
- },
- },
- }
-
- for _, test := range tests {
- v, err := ToSliceStringMap(test.input)
- c.Assert(err, qt.IsNil)
- c.Assert(v, qt.DeepEquals, test.expected)
- }
-}
-
-func TestToParamsAndPrepare(t *testing.T) {
- c := qt.New(t)
- _, err := ToParamsAndPrepare(map[string]any{"A": "av"})
- c.Assert(err, qt.IsNil)
-
- params, err := ToParamsAndPrepare(nil)
- c.Assert(err, qt.IsNil)
- c.Assert(params, qt.DeepEquals, Params{})
-}
-
-func TestRenameKeys(t *testing.T) {
- c := qt.New(t)
-
- m := map[string]any{
- "a": 32,
- "ren1": "m1",
- "ren2": "m1_2",
- "sub": map[string]any{
- "subsub": map[string]any{
- "REN1": "m2",
- "ren2": "m2_2",
- },
- },
- "no": map[string]any{
- "ren1": "m2",
- "ren2": "m2_2",
- },
- }
-
- expected := map[string]any{
- "a": 32,
- "new1": "m1",
- "new2": "m1_2",
- "sub": map[string]any{
- "subsub": map[string]any{
- "new1": "m2",
- "ren2": "m2_2",
- },
- },
- "no": map[string]any{
- "ren1": "m2",
- "ren2": "m2_2",
- },
- }
-
- renamer, err := NewKeyRenamer(
- "{ren1,sub/*/ren1}", "new1",
- "{Ren2,sub/ren2}", "new2",
- )
- c.Assert(err, qt.IsNil)
-
- renamer.Rename(m)
-
- if !reflect.DeepEqual(expected, m) {
- t.Errorf("Expected\n%#v, got\n%#v\n", expected, m)
- }
-}
-
-func TestLookupEqualFold(t *testing.T) {
- c := qt.New(t)
-
- m1 := map[string]any{
- "a": "av",
- "B": "bv",
- }
-
- v, k, found := LookupEqualFold(m1, "b")
- c.Assert(found, qt.IsTrue)
- c.Assert(v, qt.Equals, "bv")
- c.Assert(k, qt.Equals, "B")
-
- m2 := map[string]string{
- "a": "av",
- "B": "bv",
- }
-
- v, k, found = LookupEqualFold(m2, "b")
- c.Assert(found, qt.IsTrue)
- c.Assert(k, qt.Equals, "B")
- c.Assert(v, qt.Equals, "bv")
-}
+++ /dev/null
-// 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 maps
-
-import (
- "slices"
-
- "github.com/gohugoio/hugo/common/hashing"
-)
-
-// Ordered is a map that can be iterated in the order of insertion.
-// Note that insertion order is not affected if a key is re-inserted into the map.
-// In a nil map, all operations are no-ops.
-// This is not thread safe.
-type Ordered[K comparable, T any] struct {
- // The keys in the order they were added.
- keys []K
- // The values.
- values map[K]T
-}
-
-// NewOrdered creates a new Ordered map.
-func NewOrdered[K comparable, T any]() *Ordered[K, T] {
- return &Ordered[K, T]{values: make(map[K]T)}
-}
-
-// Set sets the value for the given key.
-// Note that insertion order is not affected if a key is re-inserted into the map.
-func (m *Ordered[K, T]) Set(key K, value T) {
- if m == nil {
- return
- }
- // Check if key already exists.
- if _, found := m.values[key]; !found {
- m.keys = append(m.keys, key)
- }
- m.values[key] = value
-}
-
-// Get gets the value for the given key.
-func (m *Ordered[K, T]) Get(key K) (T, bool) {
- if m == nil {
- var v T
- return v, false
- }
- value, found := m.values[key]
- return value, found
-}
-
-// Has returns whether the given key exists in the map.
-func (m *Ordered[K, T]) Has(key K) bool {
- if m == nil {
- return false
- }
- _, found := m.values[key]
- return found
-}
-
-// Delete deletes the value for the given key.
-func (m *Ordered[K, T]) Delete(key K) {
- if m == nil {
- return
- }
- delete(m.values, key)
- for i, k := range m.keys {
- if k == key {
- m.keys = slices.Delete(m.keys, i, i+1)
- break
- }
- }
-}
-
-// Clone creates a shallow copy of the map.
-func (m *Ordered[K, T]) Clone() *Ordered[K, T] {
- if m == nil {
- return nil
- }
- clone := NewOrdered[K, T]()
- for _, k := range m.keys {
- clone.Set(k, m.values[k])
- }
- return clone
-}
-
-// Keys returns the keys in the order they were added.
-func (m *Ordered[K, T]) Keys() []K {
- if m == nil {
- return nil
- }
- return m.keys
-}
-
-// Values returns the values in the order they were added.
-func (m *Ordered[K, T]) Values() []T {
- if m == nil {
- return nil
- }
- var values []T
- for _, k := range m.keys {
- values = append(values, m.values[k])
- }
- return values
-}
-
-// Len returns the number of items in the map.
-func (m *Ordered[K, T]) Len() int {
- if m == nil {
- return 0
- }
- return len(m.keys)
-}
-
-// Range calls f sequentially for each key and value present in the map.
-// If f returns false, range stops the iteration.
-// TODO(bep) replace with iter.Seq2 when we bump go Go 1.24.
-func (m *Ordered[K, T]) Range(f func(key K, value T) bool) {
- if m == nil {
- return
- }
- for _, k := range m.keys {
- if !f(k, m.values[k]) {
- return
- }
- }
-}
-
-// Hash calculates a hash from the values.
-func (m *Ordered[K, T]) Hash() (uint64, error) {
- if m == nil {
- return 0, nil
- }
- return hashing.Hash(m.values)
-}
+++ /dev/null
-// 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 maps
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestOrdered(t *testing.T) {
- c := qt.New(t)
-
- m := NewOrdered[string, int]()
- m.Set("a", 1)
- m.Set("b", 2)
- m.Set("c", 3)
-
- c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "b", "c"})
- c.Assert(m.Values(), qt.DeepEquals, []int{1, 2, 3})
-
- v, found := m.Get("b")
- c.Assert(found, qt.Equals, true)
- c.Assert(v, qt.Equals, 2)
-
- m.Set("b", 22)
- c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "b", "c"})
- c.Assert(m.Values(), qt.DeepEquals, []int{1, 22, 3})
-
- m.Delete("b")
-
- c.Assert(m.Keys(), qt.DeepEquals, []string{"a", "c"})
- c.Assert(m.Values(), qt.DeepEquals, []int{1, 3})
-}
-
-func TestOrderedHash(t *testing.T) {
- c := qt.New(t)
-
- m := NewOrdered[string, int]()
- m.Set("a", 1)
- m.Set("b", 2)
- m.Set("c", 3)
-
- h1, err := m.Hash()
- c.Assert(err, qt.IsNil)
-
- m.Set("d", 4)
-
- h2, err := m.Hash()
- c.Assert(err, qt.IsNil)
-
- c.Assert(h1, qt.Not(qt.Equals), h2)
-
- m = NewOrdered[string, int]()
- m.Set("b", 2)
- m.Set("a", 1)
- m.Set("c", 3)
-
- h3, err := m.Hash()
- c.Assert(err, qt.IsNil)
- // Order does not matter.
- c.Assert(h1, qt.Equals, h3)
-}
-
-func TestOrderedNil(t *testing.T) {
- c := qt.New(t)
-
- var m *Ordered[string, int]
-
- m.Set("a", 1)
- c.Assert(m.Keys(), qt.IsNil)
- c.Assert(m.Values(), qt.IsNil)
- v, found := m.Get("a")
- c.Assert(found, qt.Equals, false)
- c.Assert(v, qt.Equals, 0)
- m.Delete("a")
- var b bool
- m.Range(func(k string, v int) bool {
- b = true
- return true
- })
- c.Assert(b, qt.Equals, false)
- c.Assert(m.Len(), qt.Equals, 0)
- c.Assert(m.Clone(), qt.IsNil)
- h, err := m.Hash()
- c.Assert(err, qt.IsNil)
- c.Assert(h, qt.Equals, uint64(0))
-}
+++ /dev/null
-// Copyright 2025 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 maps
-
-import (
- "fmt"
- "slices"
-
- "github.com/bits-and-blooms/bitset"
-)
-
-type OrderedIntSet struct {
- keys []int
- values *bitset.BitSet
-}
-
-// NewOrderedIntSet creates a new OrderedIntSet.
-// Note that this is backed by https://github.com/bits-and-blooms/bitset
-func NewOrderedIntSet(vals ...int) *OrderedIntSet {
- m := &OrderedIntSet{
- keys: make([]int, 0, len(vals)),
- values: bitset.New(uint(len(vals))),
- }
- for _, v := range vals {
- m.Set(v)
- }
- return m
-}
-
-// Set sets the value for the given key.
-// Note that insertion order is not affected if a key is re-inserted into the set.
-func (m *OrderedIntSet) Set(key int) {
- if m == nil {
- panic("nil OrderedIntSet")
- }
- keyu := uint(key)
- if m.values.Test(keyu) {
- return
- }
- m.values.Set(keyu)
- m.keys = append(m.keys, key)
-}
-
-// SetFrom sets the values from another OrderedIntSet.
-func (m *OrderedIntSet) SetFrom(other *OrderedIntSet) {
- if m == nil || other == nil {
- return
- }
- for _, key := range other.keys {
- m.Set(key)
- }
-}
-
-func (m *OrderedIntSet) Clone() *OrderedIntSet {
- if m == nil {
- return nil
- }
- newSet := &OrderedIntSet{
- keys: slices.Clone(m.keys),
- values: m.values.Clone(),
- }
- return newSet
-}
-
-// Next returns the next key in the set possibly including the given key.
-// It returns -1 if the key is not found or if there are no keys greater than the given key.
-func (m *OrderedIntSet) Next(i int) int {
- n, ok := m.values.NextSet(uint(i))
- if !ok {
- return -1
- }
- return int(n)
-}
-
-// The reason we don't use iter.Seq is https://github.com/golang/go/issues/69015
-// This is 70% faster than using iter.Seq2[int, int] for the keys.
-// It returns false if the iteration was stopped early.
-func (m *OrderedIntSet) ForEachKey(yield func(int) bool) bool {
- if m == nil {
- return true
- }
- for _, key := range m.keys {
- if !yield(key) {
- return false
- }
- }
- return true
-}
-
-func (m *OrderedIntSet) Has(key int) bool {
- if m == nil {
- return false
- }
- return m.values.Test(uint(key))
-}
-
-func (m *OrderedIntSet) Len() int {
- if m == nil {
- return 0
- }
- return len(m.keys)
-}
-
-// KeysSorted returns the keys in sorted order.
-func (m *OrderedIntSet) KeysSorted() []int {
- if m == nil {
- return nil
- }
- keys := slices.Clone(m.keys)
- slices.Sort(keys)
- return m.keys
-}
-
-func (m *OrderedIntSet) String() string {
- if m == nil {
- return "[]"
- }
- return fmt.Sprintf("%v", m.keys)
-}
-
-func (m *OrderedIntSet) Values() *bitset.BitSet {
- if m == nil {
- return nil
- }
- return m.values
-}
-
-func (m *OrderedIntSet) IsSuperSet(other *OrderedIntSet) bool {
- if m == nil || other == nil {
- return false
- }
- return m.values.IsSuperSet(other.values)
-}
-
-// Words returns the bitset as array of 64-bit words, giving direct access to the internal representation.
-// It is not a copy, so changes to the returned slice will affect the bitset.
-// It is meant for advanced users.
-func (m *OrderedIntSet) Words() []uint64 {
- if m == nil {
- return nil
- }
- return m.values.Words()
-}
+++ /dev/null
-// Copyright 2025 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 maps
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestOrderedIntSet(t *testing.T) {
- c := qt.New(t)
-
- m := NewOrderedIntSet(2, 1, 3, 7)
-
- c.Assert(m.Len(), qt.Equals, 4)
- c.Assert(m.Has(1), qt.Equals, true)
- c.Assert(m.Has(4), qt.Equals, false)
- c.Assert(m.String(), qt.Equals, "[2 1 3 7]")
- m.Set(4)
- c.Assert(m.Len(), qt.Equals, 5)
- c.Assert(m.Has(4), qt.Equals, true)
- c.Assert(m.Next(0), qt.Equals, 1)
- c.Assert(m.Next(1), qt.Equals, 1)
- c.Assert(m.Next(2), qt.Equals, 2)
- c.Assert(m.Next(3), qt.Equals, 3)
- c.Assert(m.Next(4), qt.Equals, 4)
- c.Assert(m.Next(7), qt.Equals, 7)
- c.Assert(m.Next(8), qt.Equals, -1)
- c.Assert(m.String(), qt.Equals, "[2 1 3 7 4]")
-
- var nilset *OrderedIntSet
- c.Assert(nilset.Len(), qt.Equals, 0)
- c.Assert(nilset.Has(1), qt.Equals, false)
- c.Assert(nilset.String(), qt.Equals, "[]")
-
- var collected []int
- m.ForEachKey(func(key int) bool {
- collected = append(collected, key)
- return true
- })
- c.Assert(collected, qt.DeepEquals, []int{2, 1, 3, 7, 4})
-}
-
-func BenchmarkOrderedIntSet(b *testing.B) {
- smallSet := NewOrderedIntSet()
- for i := range 8 {
- smallSet.Set(i)
- }
- mediumSet := NewOrderedIntSet()
- for i := range 64 {
- mediumSet.Set(i)
- }
- largeSet := NewOrderedIntSet()
- for i := range 1024 {
- largeSet.Set(i)
- }
-
- b.Run("New", func(b *testing.B) {
- for b.Loop() {
- NewOrderedIntSet(1, 2, 3, 4, 5, 6, 7, 8)
- }
- })
-
- b.Run("Has small", func(b *testing.B) {
- for i := 0; b.Loop(); i++ {
- smallSet.Has(i % 32)
- }
- })
-
- b.Run("Has medium", func(b *testing.B) {
- for i := 0; b.Loop(); i++ {
- mediumSet.Has(i % 32)
- }
- })
-
- b.Run("Next", func(b *testing.B) {
- for i := 0; b.Loop(); i++ {
- mediumSet.Next(i % 32)
- }
- })
-
- b.Run("ForEachKey small", func(b *testing.B) {
- for b.Loop() {
- smallSet.ForEachKey(func(key int) bool {
- return true
- })
- }
- })
-
- b.Run("ForEachKey medium", func(b *testing.B) {
- for b.Loop() {
- mediumSet.ForEachKey(func(key int) bool {
- return true
- })
- }
- })
-
- b.Run("ForEachKey large", func(b *testing.B) {
- for b.Loop() {
- largeSet.ForEachKey(func(key int) bool {
- return true
- })
- }
- })
-}
+++ /dev/null
-// 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.
-// 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 maps
-
-import (
- "errors"
- "fmt"
- "strings"
-
- "github.com/spf13/cast"
-)
-
-// Params is a map where all keys are lower case.
-type Params map[string]any
-
-// KeyParams is an utility struct for the WalkParams method.
-type KeyParams struct {
- Key string
- Params Params
-}
-
-// GetNested does a lower case and nested search in this map.
-// It will return nil if none found.
-// Make all of these methods internal somehow.
-func (p Params) GetNested(indices ...string) any {
- v, _, _ := getNested(p, indices)
- return v
-}
-
-// SetParams overwrites values in dst with values in src for common or new keys.
-// This is done recursively.
-func SetParams(dst, src Params) {
- setParams(dst, src, 0)
-}
-
-func setParams(dst, src Params, depth int) {
- const maxDepth = 1000
- if depth > maxDepth {
- panic(errors.New("max depth exceeded"))
- }
- for k, v := range src {
- vv, found := dst[k]
- if !found {
- dst[k] = v
- } else {
- switch vvv := vv.(type) {
- case Params:
- if pv, ok := v.(Params); ok {
- setParams(vvv, pv, depth+1)
- } else {
- dst[k] = v
- }
- default:
- dst[k] = v
- }
- }
- }
-}
-
-// IsZero returns true if p is considered empty.
-func (p Params) IsZero() bool {
- if len(p) == 0 {
- return true
- }
-
- if len(p) > 1 {
- return false
- }
-
- for k := range p {
- return k == MergeStrategyKey
- }
-
- return false
-}
-
-// MergeParamsWithStrategy transfers values from src to dst for new keys using the merge strategy given.
-// This is done recursively.
-func MergeParamsWithStrategy(strategy string, dst, src Params) {
- dst.merge(ParamsMergeStrategy(strategy), src)
-}
-
-// MergeParams transfers values from src to dst for new keys using the merge encoded in dst.
-// This is done recursively.
-func MergeParams(dst, src Params) {
- ms, _ := dst.GetMergeStrategy()
- dst.merge(ms, src)
-}
-
-func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
- ns, found := p.GetMergeStrategy()
-
- ms := ns
- if !found && ps != "" {
- ms = ps
- }
-
- noUpdate := ms == ParamsMergeStrategyNone
- noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow)
-
- for k, v := range pp {
- if k == MergeStrategyKey {
- continue
- }
- vv, found := p[k]
-
- if found {
- // Key matches, if both sides are Params, we try to merge.
- if vvv, ok := vv.(Params); ok {
- if pv, ok := v.(Params); ok {
- vvv.merge(ms, pv)
- }
- }
- } else if !noUpdate {
- p[k] = v
- }
-
- }
-}
-
-// For internal use.
-func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) {
- if v, found := p[MergeStrategyKey]; found {
- if s, ok := v.(ParamsMergeStrategy); ok {
- return s, true
- }
- }
- return ParamsMergeStrategyShallow, false
-}
-
-// For internal use.
-func (p Params) DeleteMergeStrategy() bool {
- if _, found := p[MergeStrategyKey]; found {
- delete(p, MergeStrategyKey)
- return true
- }
- return false
-}
-
-// For internal use.
-func (p Params) SetMergeStrategy(s ParamsMergeStrategy) {
- switch s {
- case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
- default:
- panic(fmt.Sprintf("invalid merge strategy %q", s))
- }
- p[MergeStrategyKey] = s
-}
-
-func getNested(m map[string]any, indices []string) (any, string, map[string]any) {
- if len(indices) == 0 {
- return nil, "", nil
- }
-
- first := indices[0]
- v, found := m[strings.ToLower(cast.ToString(first))]
- if !found {
- if len(indices) == 1 {
- return nil, first, m
- }
- return nil, "", nil
-
- }
-
- if len(indices) == 1 {
- return v, first, m
- }
-
- switch m2 := v.(type) {
- case Params:
- return getNested(m2, indices[1:])
- case map[string]any:
- return getNested(m2, indices[1:])
- default:
- return nil, "", nil
- }
-}
-
-// CreateNestedParamsFromSegements creates empty nested maps for the given keySegments in the target map.
-func CreateNestedParamsFromSegements(target Params, keySegments ...string) Params {
- if len(keySegments) == 0 {
- return target
- }
-
- m := target
- for i, key := range keySegments {
- v, found := m[key]
- if !found {
- nm := Params{}
- m[key] = nm
- m = nm
- if i == len(keySegments)-1 {
- return nm
- }
- continue
- }
- m = v.(Params)
- }
-
- return m
-}
-
-// CreateNestedParamsSepString creates empty nested maps for the given keyStr in the target map
-// It returns the last map created.
-func CreateNestedParamsSepString(keyStr, separator string, target Params) Params {
- keySegments := strings.Split(keyStr, separator)
- return CreateNestedParamsFromSegements(target, keySegments...)
-}
-
-// SetNestedParamIfNotSet sets the value for the given keyStr in the target map if it does not exist.
-// It assumes that all but the last key in keyStr is a Params map or should be one.
-func SetNestedParamIfNotSet(keyStr, separator string, value any, target Params) Params {
- keySegments := strings.Split(keyStr, separator)
- if len(keySegments) == 0 {
- return target
- }
- base := keySegments[:len(keySegments)-1]
- last := keySegments[len(keySegments)-1]
-
- m := CreateNestedParamsFromSegements(target, base...)
-
- if _, ok := m[last]; !ok {
- m[last] = value
- }
-
- return target
-}
-
-// GetNestedParam gets the first match of the keyStr in the candidates given.
-// It will first try the exact match and then try to find it as a nested map value,
-// using the given separator, e.g. "mymap.name".
-// It assumes that all the maps given have lower cased keys.
-func GetNestedParam(keyStr, separator string, candidates ...Params) (any, error) {
- keyStr = strings.ToLower(keyStr)
-
- // Try exact match first
- for _, m := range candidates {
- if v, ok := m[keyStr]; ok {
- return v, nil
- }
- }
-
- keySegments := strings.Split(keyStr, separator)
- for _, m := range candidates {
- if v := m.GetNested(keySegments...); v != nil {
- return v, nil
- }
- }
-
- return nil, nil
-}
-
-func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) any) (any, string, map[string]any, error) {
- keySegments := strings.Split(keyStr, separator)
- if len(keySegments) == 0 {
- return nil, "", nil, nil
- }
-
- first := lookupFn(keySegments[0])
- if first == nil {
- return nil, "", nil, nil
- }
-
- if len(keySegments) == 1 {
- return first, keySegments[0], nil, nil
- }
-
- switch m := first.(type) {
- case map[string]any:
- v, key, owner := getNested(m, keySegments[1:])
- return v, key, owner, nil
- case Params:
- v, key, owner := getNested(m, keySegments[1:])
- return v, key, owner, nil
- }
-
- return nil, "", nil, nil
-}
-
-// ParamsMergeStrategy tells what strategy to use in Params.Merge.
-type ParamsMergeStrategy string
-
-const (
- // Do not merge.
- ParamsMergeStrategyNone ParamsMergeStrategy = "none"
- // Only add new keys.
- ParamsMergeStrategyShallow ParamsMergeStrategy = "shallow"
- // Add new keys, merge existing.
- ParamsMergeStrategyDeep ParamsMergeStrategy = "deep"
-
- MergeStrategyKey = "_merge"
-)
-
-// CleanConfigStringMapString removes any processing instructions from m,
-// m will never be modified.
-func CleanConfigStringMapString(m map[string]string) map[string]string {
- if len(m) == 0 {
- return m
- }
- if _, found := m[MergeStrategyKey]; !found {
- return m
- }
- // Create a new map and copy all the keys except the merge strategy key.
- m2 := make(map[string]string, len(m)-1)
- for k, v := range m {
- if k != MergeStrategyKey {
- m2[k] = v
- }
- }
- return m2
-}
-
-// CleanConfigStringMap is the same as CleanConfigStringMapString but for
-// map[string]any.
-func CleanConfigStringMap(m map[string]any) map[string]any {
- return doCleanConfigStringMap(m, 0)
-}
-
-func doCleanConfigStringMap(m map[string]any, depth int) map[string]any {
- if len(m) == 0 {
- return m
- }
- const maxDepth = 1000
- if depth > maxDepth {
- panic(errors.New("max depth exceeded"))
- }
- if _, found := m[MergeStrategyKey]; !found {
- return m
- }
- // Create a new map and copy all the keys except the merge strategy key.
- m2 := make(map[string]any, len(m)-1)
- for k, v := range m {
- if k != MergeStrategyKey {
- m2[k] = v
- }
- switch v2 := v.(type) {
- case map[string]any:
- m2[k] = doCleanConfigStringMap(v2, depth+1)
- case Params:
- var p Params = doCleanConfigStringMap(v2, depth+1)
- m2[k] = p
- case map[string]string:
- m2[k] = CleanConfigStringMapString(v2)
- }
-
- }
- return m2
-}
-
-func toMergeStrategy(v any) ParamsMergeStrategy {
- s := ParamsMergeStrategy(cast.ToString(v))
- switch s {
- case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
- return s
- default:
- return ParamsMergeStrategyDeep
- }
-}
-
-// PrepareParams
-// * makes all the keys in the given map lower cased and will do so recursively.
-// * This will modify the map given.
-// * Any nested map[interface{}]interface{}, map[string]interface{},map[string]string will be converted to Params.
-// * Any _merge value will be converted to proper type and value.
-func PrepareParams(m Params) {
- for k, v := range m {
- var retyped bool
- lKey := strings.ToLower(k)
- if lKey == MergeStrategyKey {
- v = toMergeStrategy(v)
- retyped = true
- } else {
- switch vv := v.(type) {
- case map[any]any:
- var p Params = cast.ToStringMap(v)
- v = p
- PrepareParams(p)
- retyped = true
- case map[string]any:
- var p Params = v.(map[string]any)
- v = p
- PrepareParams(p)
- retyped = true
- case map[string]string:
- p := make(Params)
- for k, v := range vv {
- p[k] = v
- }
- v = p
- PrepareParams(p)
- retyped = true
- }
- }
-
- if retyped || k != lKey {
- delete(m, k)
- m[lKey] = v
- }
- }
-}
-
-// CloneParamsDeep does a deep clone of the given Params,
-// meaning that any nested Params will be cloned as well.
-func CloneParamsDeep(m Params) Params {
- return cloneParamsDeep(m, 0)
-}
-
-func cloneParamsDeep(m Params, depth int) Params {
- const maxDepth = 1000
- if depth > maxDepth {
- panic(errors.New("max depth exceeded"))
- }
- m2 := make(Params)
- for k, v := range m {
- switch vv := v.(type) {
- case Params:
- m2[k] = cloneParamsDeep(vv, depth+1)
- default:
- m2[k] = v
- }
- }
- return m2
-}
-
-// PrepareParamsClone is like PrepareParams, but it does not modify the input.
-func PrepareParamsClone(m Params) Params {
- m2 := make(Params)
- for k, v := range m {
- var retyped bool
- lKey := strings.ToLower(k)
- if lKey == MergeStrategyKey {
- v = toMergeStrategy(v)
- retyped = true
- } else {
- switch vv := v.(type) {
- case map[any]any:
- var p Params = cast.ToStringMap(v)
- v = PrepareParamsClone(p)
- retyped = true
- case map[string]any:
- var p Params = v.(map[string]any)
- v = PrepareParamsClone(p)
- retyped = true
- case map[string]string:
- p := make(Params)
- for k, v := range vv {
- p[k] = v
- }
- v = p
- PrepareParams(p)
- retyped = true
- }
- }
-
- if retyped || k != lKey {
- m2[lKey] = v
- } else {
- m2[k] = v
- }
- }
- return m2
-}
+++ /dev/null
-// 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.
-// 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 maps
-
-import (
- "testing"
-
- qt "github.com/frankban/quicktest"
-)
-
-func TestGetNestedParam(t *testing.T) {
- m := map[string]any{
- "string": "value",
- "first": 1,
- "with_underscore": 2,
- "nested": map[string]any{
- "color": "blue",
- "nestednested": map[string]any{
- "color": "green",
- },
- },
- }
-
- c := qt.New(t)
-
- must := func(keyStr, separator string, candidates ...Params) any {
- v, err := GetNestedParam(keyStr, separator, candidates...)
- c.Assert(err, qt.IsNil)
- return v
- }
-
- c.Assert(must("first", "_", m), qt.Equals, 1)
- c.Assert(must("First", "_", m), qt.Equals, 1)
- c.Assert(must("with_underscore", "_", m), qt.Equals, 2)
- c.Assert(must("nested_color", "_", m), qt.Equals, "blue")
- c.Assert(must("nested.nestednested.color", ".", m), qt.Equals, "green")
- c.Assert(must("string.name", ".", m), qt.IsNil)
- c.Assert(must("nested.foo", ".", m), qt.IsNil)
-}
-
-// https://github.com/gohugoio/hugo/issues/7903
-func TestGetNestedParamFnNestedNewKey(t *testing.T) {
- c := qt.New(t)
-
- nested := map[string]any{
- "color": "blue",
- }
- m := map[string]any{
- "nested": nested,
- }
-
- existing, nestedKey, owner, err := GetNestedParamFn("nested.new", ".", func(key string) any {
- return m[key]
- })
-
- c.Assert(err, qt.IsNil)
- c.Assert(existing, qt.IsNil)
- c.Assert(nestedKey, qt.Equals, "new")
- c.Assert(owner, qt.DeepEquals, nested)
-}
-
-func TestParamsSetAndMerge(t *testing.T) {
- c := qt.New(t)
-
- createParamsPair := func() (Params, Params) {
- p1 := Params{"a": "av", "c": "cv", "nested": Params{"al2": "al2v", "cl2": "cl2v"}}
- p2 := Params{"b": "bv", "a": "abv", "nested": Params{"bl2": "bl2v", "al2": "al2bv"}, MergeStrategyKey: ParamsMergeStrategyDeep}
- return p1, p2
- }
-
- p1, p2 := createParamsPair()
-
- SetParams(p1, p2)
-
- c.Assert(p1, qt.DeepEquals, Params{
- "a": "abv",
- "c": "cv",
- "nested": Params{
- "al2": "al2bv",
- "cl2": "cl2v",
- "bl2": "bl2v",
- },
- "b": "bv",
- MergeStrategyKey: ParamsMergeStrategyDeep,
- })
-
- p1, p2 = createParamsPair()
-
- MergeParamsWithStrategy("", p1, p2)
-
- // Default is to do a shallow merge.
- c.Assert(p1, qt.DeepEquals, Params{
- "c": "cv",
- "nested": Params{
- "al2": "al2v",
- "cl2": "cl2v",
- },
- "b": "bv",
- "a": "av",
- })
-
- p1, p2 = createParamsPair()
- p1.SetMergeStrategy(ParamsMergeStrategyNone)
- MergeParamsWithStrategy("", p1, p2)
- p1.DeleteMergeStrategy()
-
- c.Assert(p1, qt.DeepEquals, Params{
- "a": "av",
- "c": "cv",
- "nested": Params{
- "al2": "al2v",
- "cl2": "cl2v",
- },
- })
-
- p1, p2 = createParamsPair()
- p1.SetMergeStrategy(ParamsMergeStrategyShallow)
- MergeParamsWithStrategy("", p1, p2)
- p1.DeleteMergeStrategy()
-
- c.Assert(p1, qt.DeepEquals, Params{
- "a": "av",
- "c": "cv",
- "nested": Params{
- "al2": "al2v",
- "cl2": "cl2v",
- },
- "b": "bv",
- })
-
- p1, p2 = createParamsPair()
- p1.SetMergeStrategy(ParamsMergeStrategyDeep)
- MergeParamsWithStrategy("", p1, p2)
- p1.DeleteMergeStrategy()
-
- c.Assert(p1, qt.DeepEquals, Params{
- "nested": Params{
- "al2": "al2v",
- "cl2": "cl2v",
- "bl2": "bl2v",
- },
- "b": "bv",
- "a": "av",
- "c": "cv",
- })
-}
-
-func TestParamsIsZero(t *testing.T) {
- c := qt.New(t)
-
- var nilParams Params
-
- c.Assert(Params{}.IsZero(), qt.IsTrue)
- c.Assert(nilParams.IsZero(), qt.IsTrue)
- c.Assert(Params{"foo": "bar"}.IsZero(), qt.IsFalse)
- c.Assert(Params{"_merge": "foo", "foo": "bar"}.IsZero(), qt.IsFalse)
- c.Assert(Params{"_merge": "foo"}.IsZero(), qt.IsTrue)
-}
-
-func TestSetNestedParamIfNotSet(t *testing.T) {
- c := qt.New(t)
-
- m := Params{}
-
- SetNestedParamIfNotSet("a.b.c", ".", "value", m)
- c.Assert(m, qt.DeepEquals, Params{
- "a": Params{
- "b": Params{
- "c": "value",
- },
- },
- })
-
- m = Params{
- "a": Params{
- "b": Params{
- "c": "existingValue",
- },
- },
- }
-
- SetNestedParamIfNotSet("a.b.c", ".", "value", m)
- c.Assert(m, qt.DeepEquals, Params{
- "a": Params{
- "b": Params{
- "c": "existingValue",
- },
- },
- })
-}
"strings"
"sync"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
// Below gets created on demand.
initOnce sync.Once
- sitesMatrixCache *maps.Cache[string, sitesmatrix.VectorStore] // Maps language index to sites matrix vector store.
+ sitesMatrixCache *hmaps.Cache[string, sitesmatrix.VectorStore] // Maps language index to sites matrix vector store.
}
func (pp *PathParser) init() {
pp.initOnce.Do(func() {
- pp.sitesMatrixCache = maps.NewCache[string, sitesmatrix.VectorStore]()
+ pp.sitesMatrixCache = hmaps.NewCache[string, sitesmatrix.VectorStore]()
})
}
"github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/cache/httpcache"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/urls"
"github.com/gohugoio/hugo/config"
// User provided parameters.
// <docsmeta>{"refs": ["config:languages:params"] }</docsmeta>
- Params maps.Params `mapstructure:"-"`
+ Params hmaps.Params `mapstructure:"-"`
// UglyURLs configuration. Either a boolean or a sections map.
UglyURLs any `mapstructure:"-"`
if lang == "" {
lang = "en"
}
- res.Cfg.Set("languages", maps.Params{lang: maps.Params{}})
+ res.Cfg.Set("languages", hmaps.Params{lang: hmaps.Params{}})
}
bcfg := res.BaseConfig
cfg := res.Cfg
var differentRootKeys []string
switch x := v.(type) {
case nil:
- case maps.Params:
+ case hmaps.Params:
_, found := x["params"]
if !found {
- x["params"] = maps.Params{
- maps.MergeStrategyKey: maps.ParamsMergeStrategyDeep,
+ x["params"] = hmaps.Params{
+ hmaps.MergeStrategyKey: hmaps.ParamsMergeStrategyDeep,
}
}
for kk, vv := range x {
isMultihost = true
}
- if p, ok := vv.(maps.Params); ok {
+ if p, ok := vv.(hmaps.Params); ok {
// With the introduction of YAML anchor and alias support, language config entries
// may be contain shared references.
// This also break potential cycles.
- vv = maps.CloneParamsDeep(p)
+ vv = hmaps.CloneParamsDeep(p)
}
if kk == "cascade" {
// If not set, add the current language to make sure it does not get applied to other languages.
- page.AddLangToCascadeTargetMap(k, vv.(maps.Params))
+ page.AddLangToCascadeTargetMap(k, vv.(hmaps.Params))
// Always clone cascade config to get the sites matrix right.
differentRootKeys = append(differentRootKeys, kk)
}
// This overrides a root key and potentially needs a merge.
if !reflect.DeepEqual(rootv, vv) {
switch vvv := vv.(type) {
- case maps.Params:
+ case hmaps.Params:
differentRootKeys = append(differentRootKeys, kk)
// Use the language value as base.
// Note that this is already cloned above.
mergedConfigEntry := vvv
// Merge in the root value.
- maps.MergeParams(mergedConfigEntry, rootv.(maps.Params))
+ hmaps.MergeParams(mergedConfigEntry, rootv.(hmaps.Params))
mergedConfig.Set(kk, mergedConfigEntry)
default:
}
} else {
switch vv.(type) {
- case maps.Params:
+ case hmaps.Params:
differentRootKeys = append(differentRootKeys, kk)
default:
// Apply new values to the root.
}
langConfigMap[k] = clone
- case maps.ParamsMergeStrategy:
+ case hmaps.ParamsMergeStrategy:
default:
panic(fmt.Sprintf("unknown type in languages config: %T", v))
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/cache/httpcache"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/privacy"
key: "outputs",
decode: func(d decodeWeight, p decodeConfig) error {
defaults := createDefaultOutputFormats(p.c.OutputFormats.Config)
- m := maps.CleanConfigStringMap(p.p.GetStringMap("outputs"))
+ m := hmaps.CleanConfigStringMap(p.p.GetStringMap("outputs"))
p.c.Outputs = make(map[string][]string)
for k, v := range m {
s := types.ToStringSlicePreserveString(v)
"languages": {
key: "languages",
decode: func(d decodeWeight, p decodeConfig) error {
- m := maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
+ m := hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
if len(m) == 1 {
// In v0.112.4 we moved this to the language config, but it's very commmon for mono language sites to have this at the top level.
- var first maps.Params
+ var first hmaps.Params
var ok bool
for _, v := range m {
- first, ok = v.(maps.Params)
+ first, ok = v.(hmaps.Params)
if ok {
break
}
key: "versions",
decode: func(d decodeWeight, p decodeConfig) error {
var err error
- m := maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
+ m := hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
p.c.Versions, err = versions.DecodeConfig(p.c.RootConfig.DefaultContentVersion, m)
return err
},
err error
defaultContentRole string
)
- m := maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
+ m := hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
p.c.Roles, defaultContentRole, err = roles.DecodeConfig(p.c.RootConfig.DefaultContentRole, m)
p.c.RootConfig.DefaultContentRole = defaultContentRole
"params": {
key: "params",
decode: func(d decodeWeight, p decodeConfig) error {
- p.c.Params = maps.CleanConfigStringMap(p.p.GetStringMap("params"))
+ p.c.Params = hmaps.CleanConfigStringMap(p.p.GetStringMap("params"))
if p.c.Params == nil {
p.c.Params = make(map[string]any)
}
key: "taxonomies",
decode: func(d decodeWeight, p decodeConfig) error {
if p.p.IsSet(d.key) {
- p.c.Taxonomies = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
+ p.c.Taxonomies = hmaps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
}
return nil
},
"author": {
key: "author",
decode: func(d decodeWeight, p decodeConfig) error {
- p.c.Author = maps.CleanConfigStringMap(p.p.GetStringMap(d.key))
+ p.c.Author = hmaps.CleanConfigStringMap(p.p.GetStringMap(d.key))
return nil
},
internalOrDeprecated: true,
"social": {
key: "social",
decode: func(d decodeWeight, p decodeConfig) error {
- p.c.Social = maps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
+ p.c.Social = hmaps.CleanConfigStringMapString(p.p.GetStringMapString(d.key))
return nil
},
internalOrDeprecated: true,
p.c.UglyURLs = vv
case string:
p.c.UglyURLs = vv == "true"
- case maps.Params:
- p.c.UglyURLs = cast.ToStringMapBool(maps.CleanConfigStringMap(vv))
+ case hmaps.Params:
+ p.c.UglyURLs = cast.ToStringMapBool(hmaps.CleanConfigStringMap(vv))
default:
p.c.UglyURLs = cast.ToStringMapBool(v)
}
package allconfig
import (
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/docshelper"
)
if v.internalOrDeprecated {
continue
}
- cfg.Set(configRoot, make(maps.Params))
+ cfg.Set(configRoot, make(hmaps.Params))
}
- lang := maps.Params{
- "en": maps.Params{
- "menus": maps.Params{},
- "params": maps.Params{},
+ lang := hmaps.Params{
+ "en": hmaps.Params{
+ "menus": hmaps.Params{},
+ "params": hmaps.Params{},
},
}
cfg.Set("languages", lang)
"github.com/gobwas/glob"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hexec"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
}
func (l configLoader) applyDefaultConfig() error {
- defaultSettings := maps.Params{
+ defaultSettings := hmaps.Params{
// These dirs are used early/before we build the config struct.
"themesDir": "themes",
"configDir": "config",
} else if b, ok := cfg.Get("minify").(bool); ok {
hugo.Deprecate("site config minify", "Use minify.minifyOutput instead.", "v0.150.0")
if b {
- cfg.Set("minify", maps.Params{"minifyOutput": true})
+ cfg.Set("minify", hmaps.Params{"minifyOutput": true})
}
}
}
for _, env := range hugoEnv {
- existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, l.cfg.Get)
+ existing, nestedKey, owner, err := hmaps.GetNestedParamFn(env.Key, delim, l.cfg.Get)
if err != nil {
return err
}
}
func (l configLoader) deleteMergeStrategies() (err error) {
- l.cfg.WalkParams(func(params ...maps.KeyParams) bool {
+ l.cfg.WalkParams(func(params ...hmaps.KeyParams) bool {
params[len(params)-1].Params.DeleteMergeStrategy()
return false
})
"strings"
"github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/common/paths"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/spf13/afero"
)
return cfg, dirnames, nil
}
-var keyAliases maps.KeyRenamer
+var keyAliases hmaps.KeyRenamer
func init() {
var err error
- keyAliases, err = maps.NewKeyRenamer(
+ keyAliases, err = hmaps.NewKeyRenamer(
// Before 0.53 we used singular for "menu".
"{menu,languages/*/menu}", "menus",
)
import (
"time"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/common/urls"
GetString(key string) string
GetInt(key string) int
GetBool(key string) bool
- GetParams(key string) maps.Params
+ GetParams(key string) hmaps.Params
GetStringMap(key string) map[string]any
GetStringMapString(key string) map[string]string
GetStringSlice(key string) []string
Set(key string, value any)
Keys() []string
Merge(key string, value any)
- SetDefaults(params maps.Params)
+ SetDefaults(params hmaps.Params)
SetDefaultMergeStrategy()
- WalkParams(walkFn func(params ...maps.KeyParams) bool)
+ WalkParams(walkFn func(params ...hmaps.KeyParams) bool)
IsSet(key string) bool
}
"strings"
"sync"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/spf13/cast"
-
- "github.com/gohugoio/hugo/common/maps"
)
-// New creates a Provider backed by an empty maps.Params.
+// New creates a Provider backed by an empty hmaps.Params.
func New() Provider {
return &defaultConfigProvider{
- root: make(maps.Params),
+ root: make(hmaps.Params),
}
}
// NewFrom creates a Provider backed by params.
-func NewFrom(params maps.Params) Provider {
- maps.PrepareParams(params)
+func NewFrom(params hmaps.Params) Provider {
+ hmaps.PrepareParams(params)
return &defaultConfigProvider{
root: params,
}
// All methods are thread safe.
type defaultConfigProvider struct {
mu sync.RWMutex
- root maps.Params
+ root hmaps.Params
keyCache sync.Map
}
return cast.ToString(v)
}
-func (c *defaultConfigProvider) GetParams(k string) maps.Params {
+func (c *defaultConfigProvider) GetParams(k string) hmaps.Params {
v := c.Get(k)
if v == nil {
return nil
}
- return v.(maps.Params)
+ return v.(hmaps.Params)
}
func (c *defaultConfigProvider) GetStringMap(k string) map[string]any {
v := c.Get(k)
- return maps.ToStringMap(v)
+ return hmaps.ToStringMap(v)
}
func (c *defaultConfigProvider) GetStringMapString(k string) map[string]string {
v := c.Get(k)
- return maps.ToStringMapString(v)
+ return hmaps.ToStringMapString(v)
}
func (c *defaultConfigProvider) GetStringSlice(k string) []string {
k = strings.ToLower(k)
if k == "" {
- if p, err := maps.ToParamsAndPrepare(v); err == nil {
+ if p, err := hmaps.ToParamsAndPrepare(v); err == nil {
// Set the values directly in root.
- maps.SetParams(c.root, p)
+ hmaps.SetParams(c.root, p)
} else {
c.root[k] = v
}
switch vv := v.(type) {
case map[string]any, map[any]any, map[string]string:
- p := maps.MustToParamsAndPrepare(vv)
+ p := hmaps.MustToParamsAndPrepare(vv)
v = p
}
}
if existing, found := m[key]; found {
- if p1, ok := existing.(maps.Params); ok {
- if p2, ok := v.(maps.Params); ok {
- maps.SetParams(p1, p2)
+ if p1, ok := existing.(hmaps.Params); ok {
+ if p2, ok := v.(hmaps.Params); ok {
+ hmaps.SetParams(p1, p2)
return
}
}
}
// SetDefaults will set values from params if not already set.
-func (c *defaultConfigProvider) SetDefaults(params maps.Params) {
- maps.PrepareParams(params)
+func (c *defaultConfigProvider) SetDefaults(params hmaps.Params) {
+ hmaps.PrepareParams(params)
for k, v := range params {
if _, found := c.root[k]; !found {
c.root[k] = v
if k == "" {
rs, f := c.root.GetMergeStrategy()
- if f && rs == maps.ParamsMergeStrategyNone {
+ if f && rs == hmaps.ParamsMergeStrategyNone {
// The user has set a "no merge" strategy on this,
// nothing more to do.
return
}
- if p, err := maps.ToParamsAndPrepare(v); err == nil {
+ if p, err := hmaps.ToParamsAndPrepare(v); err == nil {
// As there may be keys in p not in root, we need to handle
// those as a special case.
var keysToDelete []string
for kk, vv := range p {
- if pp, ok := vv.(maps.Params); ok {
+ if pp, ok := vv.(hmaps.Params); ok {
if pppi, ok := c.root[kk]; ok {
- ppp := pppi.(maps.Params)
- maps.MergeParamsWithStrategy("", ppp, pp)
+ ppp := pppi.(hmaps.Params)
+ hmaps.MergeParamsWithStrategy("", ppp, pp)
} else {
// We need to use the default merge strategy for
// this key.
- np := make(maps.Params)
- strategy := c.determineMergeStrategy(maps.KeyParams{Key: "", Params: c.root}, maps.KeyParams{Key: kk, Params: np})
+ np := make(hmaps.Params)
+ strategy := c.determineMergeStrategy(hmaps.KeyParams{Key: "", Params: c.root}, hmaps.KeyParams{Key: kk, Params: np})
np.SetMergeStrategy(strategy)
- maps.MergeParamsWithStrategy("", np, pp)
+ hmaps.MergeParamsWithStrategy("", np, pp)
c.root[kk] = np
if np.IsZero() {
// Just keep it until merge is done.
}
}
// Merge the rest.
- maps.MergeParams(c.root, p)
+ hmaps.MergeParams(c.root, p)
for _, k := range keysToDelete {
delete(c.root, k)
}
switch vv := v.(type) {
case map[string]any, map[any]any, map[string]string:
- p := maps.MustToParamsAndPrepare(vv)
+ p := hmaps.MustToParamsAndPrepare(vv)
v = p
}
}
if existing, found := m[key]; found {
- if p1, ok := existing.(maps.Params); ok {
- if p2, ok := v.(maps.Params); ok {
- maps.MergeParamsWithStrategy("", p1, p2)
+ if p1, ok := existing.(hmaps.Params); ok {
+ if p2, ok := v.(hmaps.Params); ok {
+ hmaps.MergeParamsWithStrategy("", p1, p2)
}
}
} else {
return keys
}
-func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams) bool) {
+func (c *defaultConfigProvider) WalkParams(walkFn func(params ...hmaps.KeyParams) bool) {
maxDepth := 1000
- var walk func(depth int, params ...maps.KeyParams)
- walk = func(depth int, params ...maps.KeyParams) {
+ var walk func(depth int, params ...hmaps.KeyParams)
+ walk = func(depth int, params ...hmaps.KeyParams) {
if depth > maxDepth {
panic(errors.New("max depth exceeded"))
}
p1 := params[len(params)-1]
i := len(params)
for k, v := range p1.Params {
- if p2, ok := v.(maps.Params); ok {
- paramsplus1 := make([]maps.KeyParams, i+1)
+ if p2, ok := v.(hmaps.Params); ok {
+ paramsplus1 := make([]hmaps.KeyParams, i+1)
copy(paramsplus1, params)
- paramsplus1[i] = maps.KeyParams{Key: k, Params: p2}
+ paramsplus1[i] = hmaps.KeyParams{Key: k, Params: p2}
walk(depth+1, paramsplus1...)
}
}
}
- walk(0, maps.KeyParams{Key: "", Params: c.root})
+ walk(0, hmaps.KeyParams{Key: "", Params: c.root})
}
-func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams) maps.ParamsMergeStrategy {
+func (c *defaultConfigProvider) determineMergeStrategy(params ...hmaps.KeyParams) hmaps.ParamsMergeStrategy {
if len(params) == 0 {
- return maps.ParamsMergeStrategyNone
+ return hmaps.ParamsMergeStrategyNone
}
var (
- strategy maps.ParamsMergeStrategy
+ strategy hmaps.ParamsMergeStrategy
prevIsRoot bool
curr = params[len(params)-1]
)
// Don't set a merge strategy on the root unless set by user.
// This will be handled as a special case.
case "params":
- strategy = maps.ParamsMergeStrategyDeep
+ strategy = hmaps.ParamsMergeStrategyDeep
case "outputformats", "mediatypes":
if prevIsRoot {
- strategy = maps.ParamsMergeStrategyShallow
+ strategy = hmaps.ParamsMergeStrategyShallow
}
case "menus":
isMenuKey := prevIsRoot
}
}
if isMenuKey {
- strategy = maps.ParamsMergeStrategyShallow
+ strategy = hmaps.ParamsMergeStrategyShallow
}
default:
if strategy == "" {
- strategy = maps.ParamsMergeStrategyNone
+ strategy = hmaps.ParamsMergeStrategyNone
}
}
}
func (c *defaultConfigProvider) SetDefaultMergeStrategy() {
- c.WalkParams(func(params ...maps.KeyParams) bool {
+ c.WalkParams(func(params ...hmaps.KeyParams) bool {
if len(params) == 0 {
return false
}
})
}
-func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, maps.Params) {
+func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, hmaps.Params) {
var parts []string
v, ok := c.keyCache.Load(key)
if ok {
next, found := current[parts[i]]
if !found {
if create {
- next = make(maps.Params)
+ next = make(hmaps.Params)
current[parts[i]] = next
} else {
return "", nil
}
}
var ok bool
- current, ok = next.(maps.Params)
+ current, ok = next.(hmaps.Params)
if !ok {
// E.g. a string, not a map that we can store values in.
return "", nil
"strings"
"testing"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/para"
- "github.com/gohugoio/hugo/common/maps"
-
qt "github.com/frankban/quicktest"
)
c.Assert(cfg.Get(k), qt.Equals, v)
c.Assert(cfg.GetInt(k), qt.Equals, v)
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"foo": 42,
})
})
"bar": "baz",
})
- c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("foo"), qt.DeepEquals, hmaps.Params{
"bar": "baz",
})
})
cfg.Set("a.c", "cv")
- c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{
"b": "bv",
"c": "cv",
})
c.Assert(cfg.Get("a.c"), qt.Equals, "cv")
cfg.Set("b.a", "av")
- c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("b"), qt.DeepEquals, hmaps.Params{
"a": "av",
})
"b": "bv",
})
- c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("b"), qt.DeepEquals, hmaps.Params{
"a": "av",
"b": "bv",
})
"b": "bv2",
})
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"a": "av2",
"b": "bv2",
})
"b": "bv2",
})
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"a": "av",
"b": "bv2",
})
},
})
- c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("foo"), qt.DeepEquals, hmaps.Params{
"a": "av",
"b": "bv2",
})
"c": "cv2",
})
- c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{
"b": "bv",
"c": "cv2",
})
"b": "bv2",
})
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"a": "av",
})
})
"e": "ev2",
})
- c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{
"e": "ev2",
- "_merge": maps.ParamsMergeStrategyShallow,
+ "_merge": hmaps.ParamsMergeStrategyShallow,
"b": "bv",
- "c": maps.Params{
+ "c": hmaps.Params{
"b": "bv",
},
})
"b": left,
})
- cfg.Merge("", maps.Params{
- "b": maps.Params{
+ cfg.Merge("", hmaps.Params{
+ "b": hmaps.Params{
"c": "cv2",
"d": "dv2",
},
})
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
- "b": maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
+ "b": hmaps.Params{
"c": "cv1",
"d": "dv2",
},
cfg.Merge("a", right)
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
- "a": maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
+ "a": hmaps.Params{
"b": "bv1",
"c": "cv2",
},
},
})
- c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{
+ c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{
"b": "bv",
})
})
return err
}
- m := maps.Params{
+ m := hmaps.Params{
"new": 42,
}
cfg := New()
k := "foo"
- cfg.Set(k, maps.Params{k: true})
- c.Assert(cfg.GetParams(k), qt.DeepEquals, maps.Params{
+ cfg.Set(k, hmaps.Params{k: true})
+ c.Assert(cfg.GetParams(k), qt.DeepEquals, hmaps.Params{
k: true,
})
k := "foo"
k2 := "bar"
- cfg.Set(k, maps.Params{k: struct{}{}})
- cfg.Set(k2, maps.Params{k2: struct{}{}})
+ cfg.Set(k, hmaps.Params{k: struct{}{}})
+ cfg.Set(k2, hmaps.Params{k2: struct{}{}})
c.Assert(len(cfg.Keys()), qt.Equals, 2)
c.Run("WalkParams", func(c *qt.C) {
cfg := New()
- cfg.Set("x", maps.Params{})
- cfg.Set("y", maps.Params{})
+ cfg.Set("x", hmaps.Params{})
+ cfg.Set("y", hmaps.Params{})
var got []string
- cfg.WalkParams(func(params ...maps.KeyParams) bool {
+ cfg.WalkParams(func(params ...hmaps.KeyParams) bool {
got = append(got, params[len(params)-1].Key)
return false
})
c.Assert(got, qt.DeepEquals, want)
cfg = New()
- cfg.WalkParams(func(params ...maps.KeyParams) bool {
+ cfg.WalkParams(func(params ...hmaps.KeyParams) bool {
return true
})
c.Run("SetDefaults", func(c *qt.C) {
cfg := New()
- cfg.SetDefaults(maps.Params{
+ cfg.SetDefaults(hmaps.Params{
"foo": "bar",
"bar": "baz",
})
"testing"
qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/mitchellh/mapstructure"
)
map[string]any{"foo": "bar"},
func(v any) (*tstNsExt, any, error) {
t := &tstNsExt{}
- m, err := maps.ToStringMapE(v)
+ m, err := hmaps.ToStringMapE(v)
if err != nil {
return nil, nil, err
}
import (
_ "unsafe"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/deps"
if err != nil {
panic(err)
}
- p := maps.Params{
+ p := hmaps.Params{
section: config.FromTOMLConfigString(string(data)).Get(""),
}
cfg := config.NewFrom(p)
"github.com/gohugoio/hugo/cache/dynacache"
"github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/common/hexec"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig"
d.BuildState.DeferredExecutionsGroupedByRenderingContext = make(map[tpl.RenderingContext]*DeferredExecutions)
}
d.BuildState.DeferredExecutions = &DeferredExecutions{
- Executions: maps.NewCache[string, *tpl.DeferredExecution](),
- FilenamesWithPostPrefix: maps.NewCache[string, bool](),
+ Executions: hmaps.NewCache[string, *tpl.DeferredExecution](),
+ FilenamesWithPostPrefix: hmaps.NewCache[string, bool](),
}
}
type DeferredExecutions struct {
// A set of filenames in /public that
// contains a post-processing prefix.
- FilenamesWithPostPrefix *maps.Cache[string, bool]
+ FilenamesWithPostPrefix *hmaps.Cache[string, bool]
// Maps a placeholder to a deferred execution.
- Executions *maps.Cache[string, *tpl.DeferredExecution]
+ Executions *hmaps.Cache[string, *tpl.DeferredExecution]
}
var _ identity.SignalRebuilder = (*BuildState)(nil)
func (b *BuildState) StopStageRender(stage tpl.RenderingContext) {
b.DeferredExecutionsGroupedByRenderingContext[stage] = b.DeferredExecutions
b.DeferredExecutions = &DeferredExecutions{
- Executions: maps.NewCache[string, *tpl.DeferredExecution](),
- FilenamesWithPostPrefix: maps.NewCache[string, bool](),
+ Executions: hmaps.NewCache[string, *tpl.DeferredExecution](),
+ FilenamesWithPostPrefix: hmaps.NewCache[string, bool](),
}
}
"github.com/gobwas/glob"
"github.com/gobwas/glob/syntax"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/identity"
)
isWindows = runtime.GOOS == "windows"
defaultGlobCache = &pathGlobCache{
isWindows: isWindows,
- cache: maps.NewCache[string, globErr](),
+ cache: hmaps.NewCache[string, globErr](),
}
- dotGlobCache = maps.NewCache[string, globErr]()
+ dotGlobCache = hmaps.NewCache[string, globErr]()
)
type globErr struct {
isWindows bool
// Cache
- cache *maps.Cache[string, globErr]
+ cache *hmaps.Cache[string, globErr]
}
// GetGlobDot returns a glob.Glob that matches the given pattern, using '.' as the path separator.
iofs "io/fs"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs/hglob"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
var testDims = sitesmatrix.NewTestingDimensions([]string{"en", "no"}, []string{"v1", "v2", "v3"}, []string{"admin", "editor", "viewer", "guest"})
func sitesMatrixForLangs(langs ...int) *sitesmatrix.IntSets {
- return sitesmatrix.NewIntSetsBuilder(testDims).WithSets(maps.NewOrderedIntSet(langs...), nil, nil).Build()
+ return sitesmatrix.NewIntSetsBuilder(testDims).WithSets(hmaps.NewOrderedIntSet(langs...), nil, nil).Build()
}
"testing"
"github.com/bep/logg"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig"
qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/common/maps"
"github.com/spf13/afero"
)
enSite := b.H.Sites[0]
b.Assert(enSite.Title(), qt.Equals, "English Title")
b.Assert(enSite.Home().Title(), qt.Equals, "English Title")
- b.Assert(enSite.Params(), qt.DeepEquals, maps.Params{
- "comments": maps.Params{
+ b.Assert(enSite.Params(), qt.DeepEquals, hmaps.Params{
+ "comments": hmaps.Params{
"color": "blue",
"title": "English Comments Title",
},
got := b.H.Configs.Base
- b.Assert(got.Params, qt.DeepEquals, maps.Params{
- "b": maps.Params{
+ b.Assert(got.Params, qt.DeepEquals, hmaps.Params{
+ "b": hmaps.Params{
"b1": "b1 main",
- "c": maps.Params{
+ "c": hmaps.Params{
"bc1": "bc1 main",
"bc2": "bc2 theme",
- "d": maps.Params{"bcd1": string("bcd1 theme")},
+ "d": hmaps.Params{"bcd1": string("bcd1 theme")},
},
"b2": "b2 theme",
},
got := b.H.Configs.Base.Params
// Shallow merge, only add new keys to params.
- b.Assert(got, qt.DeepEquals, maps.Params{
+ b.Assert(got, qt.DeepEquals, hmaps.Params{
"p1": "p1 main",
- "b": maps.Params{
+ "b": hmaps.Params{
"b1": "b1 main",
- "c": maps.Params{
+ "c": hmaps.Params{
"bc1": "bc1 main",
},
},
got := b.H.Configs.Base.Params
- b.Assert(got, qt.DeepEquals, maps.Params{
+ b.Assert(got, qt.DeepEquals, hmaps.Params{
"p1": "p1 theme",
})
})
got := b.H.Configs.Base.Params
- b.Assert(got, qt.DeepEquals, maps.Params{
+ b.Assert(got, qt.DeepEquals, hmaps.Params{
"m1": "mv1",
"m2": "mv2",
"t1": "tv1",
"github.com/bep/logg"
"github.com/gohugoio/go-radix"
"github.com/gohugoio/hugo/cache/dynacache"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/predicate"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/resources"
- "github.com/gohugoio/hugo/common/maps"
-
"github.com/gohugoio/hugo/resources/kinds"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/resource"
cacheContentPlain *dynacache.Partition[string, *resources.StaleValue[contentPlainPlainWords]]
contentTableOfContents *dynacache.Partition[string, *resources.StaleValue[contentTableOfContents]]
- contentDataFileSeenItems *maps.Cache[string, map[uint64]bool]
+ contentDataFileSeenItems *hmaps.Cache[string, map[uint64]bool]
cfg contentMapConfig
}
cacheContentPlain: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentPlainPlainWords]](mcache, fmt.Sprintf("/cont/pla/%s", languageVersionRole), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
contentTableOfContents: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentTableOfContents]](mcache, fmt.Sprintf("/cont/toc/%s", languageVersionRole), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
- contentDataFileSeenItems: maps.NewCache[string, map[uint64]bool](),
+ contentDataFileSeenItems: hmaps.NewCache[string, map[uint64]bool](),
cfg: contentMapConfig{
lang: s.Lang(),
func newContentTreeTreverseIndex(init func(get func(key any) (contentNode, bool), set func(key any, val contentNode))) *contentTreeReverseIndex {
return &contentTreeReverseIndex{
initFn: init,
- mm: maps.NewCache[any, contentNode](),
+ mm: hmaps.NewCache[any, contentNode](),
}
}
type contentTreeReverseIndex struct {
initFn func(get func(key any) (contentNode, bool), set func(key any, val contentNode))
- mm *maps.Cache[any, contentNode]
+ mm *hmaps.Cache[any, contentNode]
}
func (c *contentTreeReverseIndex) Reset() {
"time"
"github.com/gohugoio/go-radix"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/files"
rwRoot *doctree.NodeShiftTreeWalker[contentNode] // walks resources.
// Walking state.
- seenTerms *maps.Map[term, sitesmatrix.Vectors]
- droppedPages *maps.Map[*Site, []string] // e.g. drafts, expired, future.
- seenRootSections *maps.Map[string, bool]
+ seenTerms *hmaps.Map[term, sitesmatrix.Vectors]
+ droppedPages *hmaps.Map[*Site, []string] // e.g. drafts, expired, future.
+ seenRootSections *hmaps.Map[string, bool]
seenHome bool // set before we fan out to multiple goroutines.
}
pw := rw.Extend()
pw.Tree = m.treePages
- seenRootSections := maps.NewMap[string, bool]()
+ seenRootSections := hmaps.NewMap[string, bool]()
seenRootSections.Set("", true) // home.
return &allPagesAssembler{
h: h,
m: m,
assembleChanges: assembleChanges,
- seenTerms: maps.NewMap[term, sitesmatrix.Vectors](),
- droppedPages: maps.NewMap[*Site, []string](),
+ seenTerms: hmaps.NewMap[term, sitesmatrix.Vectors](),
+ droppedPages: hmaps.NewMap[*Site, []string](),
seenRootSections: seenRootSections,
assembleSectionsInParallel: true,
pwRoot: pw,
radix "github.com/gohugoio/go-radix"
"github.com/gohugoio/hugo/common/collections"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
)
func (ctx *WalkContext[T]) initDataRaw() {
ctx.dataRawInit.Do(func() {
- ctx.dataRaw = maps.NewCache[sitesmatrix.Vector, *SimpleThreadSafeTree[any]]()
+ ctx.dataRaw = hmaps.NewCache[sitesmatrix.Vector, *SimpleThreadSafeTree[any]]()
})
}
data *SimpleThreadSafeTree[any]
dataInit sync.Once
- dataRaw *maps.Cache[sitesmatrix.Vector, *SimpleThreadSafeTree[any]]
+ dataRaw *hmaps.Cache[sitesmatrix.Vector, *SimpleThreadSafeTree[any]]
dataRawInit sync.Once
eventHandlersMu sync.Mutex
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/parser/metadecoders"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hsync"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/para"
"github.com/gohugoio/hugo/common/terminal"
"github.com/gohugoio/hugo/common/types"
// Now the different page dimensions (e.g. language) are built-in to the page trees above.
// But we sill need to support the overridden translationKey, but that should
// be relatively rare and low volume.
- translationKeyPages *maps.SliceCache[page.Page]
+ translationKeyPages *hmaps.SliceCache[page.Page]
pageTrees *pageTrees
- previousPageTreesWalkContext *doctree.WalkContext[contentNode] // Set for rebuilds only.
- previousSeenTerms *maps.Map[term, sitesmatrix.Vectors] // Set for rebuilds only.
+ previousPageTreesWalkContext *doctree.WalkContext[contentNode] // Set for rebuilds only.
+ previousSeenTerms *hmaps.Map[term, sitesmatrix.Vectors] // Set for rebuilds only.
printUnusedTemplatesInit sync.Once
printPathWarningsInit sync.Once
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/himage"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig"
}
if s.Cfg.Running {
- flags.Set("internal", maps.Params{
+ flags.Set("internal", hmaps.Params{
"running": s.Cfg.Running,
"watch": s.Cfg.Running,
})
} else if s.Cfg.Watching {
- flags.Set("internal", maps.Params{
+ flags.Set("internal", hmaps.Params{
"watch": s.Cfg.Watching,
})
}
"github.com/bep/logg"
"github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/predicate"
"github.com/gohugoio/hugo/common/types/hstring"
"github.com/gohugoio/hugo/helpers"
StaleInfo: m,
pi: m.pi,
enableEmoji: s.conf.EnableEmoji,
- scopes: maps.NewCache[string, *cachedContentScope](),
+ scopes: hmaps.NewCache[string, *cachedContentScope](),
}
var hasName predicate.P[string] = m.pi.shortcodeParseInfo.hasName
c.hasShortcode.Store(&hasName)
// Whether the content has a given shortcode name.
hasShortcode atomic.Pointer[predicate.P[string]]
- scopes *maps.Cache[string, *cachedContentScope]
+ scopes *hmaps.Cache[string, *cachedContentScope]
}
func (c *cachedContent) getOrCreateScope(scope string, pco *pageContentOutput) *cachedContentScope {
import (
"fmt"
+ "maps"
"path/filepath"
"regexp"
"strings"
"time"
- xmaps "maps"
-
"github.com/bep/logg"
"github.com/gobuffalo/flect"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/common/hashing"
"github.com/gohugoio/hugo/common/hiter"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
m := &pageMeta{
pageMetaSource: ms,
pageConfig: &pagemeta.PageConfigLate{
- Params: make(maps.Params),
+ Params: make(hmaps.Params),
},
}
return m, nil
return m.Kind() == kinds.KindPage
}
-func (m *pageMeta) Params() maps.Params {
+func (m *pageMeta) Params() hmaps.Params {
return m.pageConfig.Params
}
func (ps *pageState) setMetaPost(cascades *page.PageMatcherParamsConfigs) error {
if ps.m.pageConfigSource.Frontmatter != nil {
if ps.m.pageConfigSource.IsFromContentAdapter {
- ps.m.pageConfig.ContentAdapterData = xmaps.Clone(ps.m.pageConfigSource.Frontmatter)
+ ps.m.pageConfig.ContentAdapterData = maps.Clone(ps.m.pageConfigSource.Frontmatter)
} else {
- ps.m.pageConfig.Params = xmaps.Clone(ps.m.pageConfigSource.Frontmatter)
+ ps.m.pageConfig.Params = maps.Clone(ps.m.pageConfigSource.Frontmatter)
}
}
if ps.m.pageConfig.Params == nil {
- ps.m.pageConfig.Params = make(maps.Params)
+ ps.m.pageConfig.Params = make(hmaps.Params)
}
if ps.m.pageConfigSource.IsFromContentAdapter && ps.m.pageConfig.ContentAdapterData == nil {
- ps.m.pageConfig.ContentAdapterData = make(maps.Params)
+ ps.m.pageConfig.ContentAdapterData = make(hmaps.Params)
}
// Cascade defined on itself has higher priority than inherited ones.
loki := strings.ToLower(k)
if loki == "params" {
- vv, err := maps.ToStringMapE(v)
+ vv, err := hmaps.ToStringMapE(v)
if err != nil {
return err
}
}
pcfg.Params[loki] = pcfg.Aliases
case "sitemap":
- pcfg.Sitemap, err = config.DecodeSitemap(ps.s.conf.Sitemap, maps.ToStringMap(v))
+ pcfg.Sitemap, err = config.DecodeSitemap(ps.s.conf.Sitemap, hmaps.ToStringMap(v))
if err != nil {
return fmt.Errorf("failed to decode sitemap config in front matter: %s", err)
}
switch vv := v.(type) {
case []map[any]any:
for _, vvv := range vv {
- resources = append(resources, maps.ToStringMap(vvv))
+ resources = append(resources, hmaps.ToStringMap(vvv))
}
case []map[string]any:
resources = append(resources, vv...)
for _, vvv := range vv {
switch vvvv := vvv.(type) {
case map[any]any:
- resources = append(resources, maps.ToStringMap(vvvv))
+ resources = append(resources, hmaps.ToStringMap(vvvv))
case map[string]any:
resources = append(resources, vvvv)
}
"sync"
"sync/atomic"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/text"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/tpl/tplimpl"
cp := &pageContentOutput{
po: po,
renderHooks: &renderHooks{},
- otherOutputs: maps.NewCache[uint64, *pageContentOutput](),
+ otherOutputs: hmaps.NewCache[uint64, *pageContentOutput](),
}
return cp, nil
}
// Other pages involved in rendering of this page,
// typically included with .RenderShortcodes.
- otherOutputs *maps.Cache[uint64, *pageContentOutput]
+ otherOutputs *hmaps.Cache[uint64, *pageContentOutput]
contentRenderedVersion uint32 // Incremented on reset.
contentRendered atomic.Bool // Set on content render.
"path/filepath"
"github.com/gohugoio/hugo/common/hashing"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstore"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
}
func (p *pagesFromDataTemplateContext) toPathSitesMap(v any) (string, map[string]any, map[string]any, error) {
- m, err := maps.ToStringMapE(v)
+ m, err := hmaps.ToStringMapE(v)
if err != nil {
return "", nil, nil, err
}
return "", nil, nil, fmt.Errorf("invalid path %q", path)
}
- sites := maps.ToStringMap(m["sites"])
+ sites := hmaps.ToStringMap(m["sites"])
return path, sites, m, nil
}
PagesFromTemplateOptions: opts,
PagesFromTemplateDeps: opts.DepsFromSite(opts.Site),
buildState: &BuildState{
- sourceInfosCurrent: maps.NewCache[string, *sourceInfo](),
+ sourceInfosCurrent: hmaps.NewCache[string, *sourceInfo](),
},
store: hstore.NewScratch(),
}
NumPagesAdded uint64
NumResourcesAdded uint64
- sourceInfosCurrent *maps.Cache[string, *sourceInfo]
- sourceInfosPrevious *maps.Cache[string, *sourceInfo]
+ sourceInfosCurrent *hmaps.Cache[string, *sourceInfo]
+ sourceInfosPrevious *hmaps.Cache[string, *sourceInfo]
}
func (b *BuildState) hash(v any) uint64 {
func (b *BuildState) PrepareNextBuild() {
b.sourceInfosPrevious = b.sourceInfosCurrent
- b.sourceInfosCurrent = maps.NewCache[string, *sourceInfo]()
+ b.sourceInfosCurrent = hmaps.NewCache[string, *sourceInfo]()
b.StaleVersion = 0
b.DeletedPaths = nil
b.ChangedIdentities = nil
p.PagesFromTemplateOptions.Site = s
p.PagesFromTemplateDeps = p.PagesFromTemplateOptions.DepsFromSite(s)
p.buildState = &BuildState{
- sourceInfosCurrent: maps.NewCache[string, *sourceInfo](),
+ sourceInfosCurrent: hmaps.NewCache[string, *sourceInfo](),
}
return &p
}
"strings"
"github.com/gobwas/glob"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/predicate"
"github.com/gohugoio/hugo/config"
hglob "github.com/gohugoio/hugo/hugofs/hglob"
func DecodeSegments(in map[string]any, segmentsToRender []string, logger loggers.Logger) (*config.ConfigNamespace[map[string]SegmentConfig, *Segments], error) {
buildConfig := func(in any) (*Segments, any, error) {
- m, err := maps.ToStringMapE(in)
+ m, err := hmaps.ToStringMapE(in)
if err != nil {
return nil, nil, err
}
if m == nil {
m = map[string]any{}
}
- m = maps.CleanConfigStringMap(m)
+ m = hmaps.CleanConfigStringMap(m)
var segmentCfg map[string]SegmentConfig
if err := mapstructure.WeakDecode(m, &segmentCfg); err != nil {
"errors"
"fmt"
"io"
+ "maps"
"mime"
"net/url"
"os"
"github.com/bep/logg"
"github.com/gohugoio/go-radix"
"github.com/gohugoio/hugo/cache/dynacache"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstore"
"github.com/gohugoio/hugo/common/hsync"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/para"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/modules"
"github.com/gohugoio/hugo/resources"
- xmaps "maps"
-
"github.com/gohugoio/hugo/tpl/tplimpl"
"github.com/gohugoio/hugo/tpl/tplimplinit"
dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild},
),
cacheContentSource: dynacache.GetOrCreatePartition[uint64, *resources.StaleValue[[]byte]](d.MemCache, "/cont/src", dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
- translationKeyPages: maps.NewSliceCache[page.Page](),
+ translationKeyPages: hmaps.NewSliceCache[page.Page](),
currentSite: first[0],
skipRebuildForFilenames: make(map[string]bool),
init: &hugoSitesInit{},
}
// Returns the Params configured for this site.
-func (s *Site) Params() maps.Params {
+func (s *Site) Params() hmaps.Params {
return s.conf.Params
}
if w == nil || w.ids == nil {
return nil
}
- return slices.Collect(xmaps.Keys(w.ids))
+ return slices.Collect(maps.Keys(w.ids))
}
func (w *WhatChanged) Drain() []identity.Identity {
"cmp"
"fmt"
"iter"
- xmaps "maps"
+ "maps"
"slices"
"sort"
"sync"
"github.com/gohugoio/hashstructure"
"github.com/gohugoio/hugo/common/hashing"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/predicate"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/hugofs/hglob"
func (s *vectorStoreMap) clone() *vectorStoreMap {
c := *s
c.h = &hashOnce{}
- c.sets = xmaps.Clone(s.sets)
+ c.sets = maps.Clone(s.sets)
return &c
}
ConfiguredRoles ConfiguredDimension
CommonSitesMatrix CommonSitestMatrix
- singleVectorStoreCache *maps.Cache[Vector, *IntSets]
+ singleVectorStoreCache *hmaps.Cache[Vector, *IntSets]
}
func (c *ConfiguredDimensions) IsSingleVector() bool {
}
func (c *ConfiguredDimensions) Init() error {
- c.singleVectorStoreCache = maps.NewCache[Vector, *IntSets]()
+ c.singleVectorStoreCache = hmaps.NewCache[Vector, *IntSets]()
b := NewIntSetsBuilder(c).WithDefaultsIfNotSet().Build()
defaultVec := b.VectorSample()
c.singleVectorStoreCache.Set(defaultVec, b)
// IntSets holds the ordered sets of integers for the dimensions,
// which is used for fast membership testing of files, resources and pages.
type IntSets struct {
- languages *maps.OrderedIntSet `mapstructure:"-" json:"-"`
- versions *maps.OrderedIntSet `mapstructure:"-" json:"-"`
- roles *maps.OrderedIntSet `mapstructure:"-" json:"-"`
+ languages *hmaps.OrderedIntSet `mapstructure:"-" json:"-"`
+ versions *hmaps.OrderedIntSet `mapstructure:"-" json:"-"`
+ roles *hmaps.OrderedIntSet `mapstructure:"-" json:"-"`
h *hashOnce
}
// WithLanguageIndices replaces the current language set with a single language index.
func (s *IntSets) WithLanguageIndices(i int) VectorStore {
c := s.shallowClone()
- c.languages = maps.NewOrderedIntSet(i)
+ c.languages = hmaps.NewOrderedIntSet(i)
return c.init()
}
// setDefaultsIfNotSet applies default values to the IntSets if they are not already set.
func (s *IntSets) setDefaultsIfNotSet(cfg *ConfiguredDimensions) {
if s.languages == nil {
- s.languages = maps.NewOrderedIntSet()
+ s.languages = hmaps.NewOrderedIntSet()
s.languages.Set(cfg.ConfiguredLanguages.IndexDefault())
}
if s.versions == nil {
- s.versions = maps.NewOrderedIntSet()
+ s.versions = hmaps.NewOrderedIntSet()
s.versions.Set(cfg.ConfiguredVersions.IndexDefault())
}
if s.roles == nil {
- s.roles = maps.NewOrderedIntSet()
+ s.roles = hmaps.NewOrderedIntSet()
s.roles.Set(cfg.ConfiguredRoles.IndexDefault())
}
}
func (s *IntSets) setDefaultsAndAllLAnguagesIfNotSet(cfg *ConfiguredDimensions) {
if s.languages == nil {
- s.languages = maps.NewOrderedIntSet()
+ s.languages = hmaps.NewOrderedIntSet()
for i := range cfg.ConfiguredLanguages.ForEachIndex() {
s.languages.Set(i)
}
func (s *IntSets) setAllIfNotSet(cfg *ConfiguredDimensions) {
if s.languages == nil {
- s.languages = maps.NewOrderedIntSet()
+ s.languages = hmaps.NewOrderedIntSet()
for i := range cfg.ConfiguredLanguages.ForEachIndex() {
s.languages.Set(i)
}
}
if s.versions == nil {
- s.versions = maps.NewOrderedIntSet()
+ s.versions = hmaps.NewOrderedIntSet()
for i := range cfg.ConfiguredVersions.ForEachIndex() {
s.versions.Set(i)
}
}
if s.roles == nil {
- s.roles = maps.NewOrderedIntSet()
+ s.roles = hmaps.NewOrderedIntSet()
for i := range cfg.ConfiguredRoles.ForEachIndex() {
s.roles.Set(i)
}
func (s *IntSets) setValuesInNilSets(vec Vector, setLang, setVer, setRole bool) {
if setLang {
if s.languages == nil {
- s.languages = maps.NewOrderedIntSet()
+ s.languages = hmaps.NewOrderedIntSet()
}
s.languages.Set(vec.Language())
}
if setVer {
if s.versions == nil {
- s.versions = maps.NewOrderedIntSet()
+ s.versions = hmaps.NewOrderedIntSet()
}
s.versions.Set(vec.Version())
}
if setRole {
if s.roles == nil {
- s.roles = maps.NewOrderedIntSet()
+ s.roles = hmaps.NewOrderedIntSet()
}
s.roles.Set(vec.Role())
}
}
func (b *IntSetsBuilder) WithConfig(cfg IntSetsConfig) *IntSetsBuilder {
- applyFilter := func(what string, values []string, matcher ConfiguredDimension) (*maps.OrderedIntSet, error) {
- var result *maps.OrderedIntSet
+ applyFilter := func(what string, values []string, matcher ConfiguredDimension) (*hmaps.OrderedIntSet, error) {
+ var result *hmaps.OrderedIntSet
if len(values) == 0 {
if cfg.ApplyDefaults > 0 {
- result = maps.NewOrderedIntSet()
+ result = hmaps.NewOrderedIntSet()
}
switch cfg.ApplyDefaults {
case IntSetsConfigApplyDefaultsIfNotSet:
}
for i := range iter {
if result == nil {
- result = maps.NewOrderedIntSet()
+ result = hmaps.NewOrderedIntSet()
}
result.Set(i)
}
return s
}
if s.s.languages == nil {
- s.s.languages = maps.NewOrderedIntSet()
+ s.s.languages = hmaps.NewOrderedIntSet()
}
for _, i := range idxs {
s.s.languages.Set(i)
return s
}
-func (b *IntSetsBuilder) WithSets(languages, versions, roles *maps.OrderedIntSet) *IntSetsBuilder {
+func (b *IntSetsBuilder) WithSets(languages, versions, roles *hmaps.OrderedIntSet) *IntSetsBuilder {
b.s.languages = languages
b.s.versions = versions
b.s.roles = roles
return s.Matrix.Equal(other.Matrix) && s.Complements.Equal(other.Complements)
}
-func (s *Sites) SetFromParamsIfNotSet(params maps.Params) {
+func (s *Sites) SetFromParamsIfNotSet(params hmaps.Params) {
const (
matrixKey = "matrix"
complementsKey = "complements"
)
if m, ok := params[matrixKey]; ok {
- s.Matrix.SetFromParamsIfNotSet(m.(maps.Params))
+ s.Matrix.SetFromParamsIfNotSet(m.(hmaps.Params))
}
if f, ok := params[complementsKey]; ok {
- s.Complements.SetFromParamsIfNotSet(f.(maps.Params))
+ s.Complements.SetFromParamsIfNotSet(f.(hmaps.Params))
}
}
slices.Equal(d.Roles, other.Roles)
}
-func (d *StringSlices) SetFromParamsIfNotSet(params maps.Params) {
+func (d *StringSlices) SetFromParamsIfNotSet(params hmaps.Params) {
const (
languagesKey = "languages"
versionsKey = "versions"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/hashing"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
)
testDims := newTestDims()
sets := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2),
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3),
).Build()
sets2 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2),
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(1, 2, 4),
+ hmaps.NewOrderedIntSet(1, 2),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 4),
).Build()
sets3 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(2, 1),
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(2, 1, 4),
+ hmaps.NewOrderedIntSet(2, 1),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(2, 1, 4),
).Build()
sets4 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(3, 4, 5),
- maps.NewOrderedIntSet(3, 4, 5),
- maps.NewOrderedIntSet(3, 4, 5),
+ hmaps.NewOrderedIntSet(3, 4, 5),
+ hmaps.NewOrderedIntSet(3, 4, 5),
+ hmaps.NewOrderedIntSet(3, 4, 5),
).Build()
c.Assert(hashing.HashStringHex(sets), qt.Equals, "790f6004619934ff")
c.Assert(sets.EqualsVector(sets), qt.Equals, true)
c.Assert(sets.EqualsVector(
sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2),
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3),
).Build(),
), qt.Equals, true)
c.Assert(sets.EqualsVector(
sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(1, 2, 3, 4),
- maps.NewOrderedIntSet(1, 2, 3, 4),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3, 4),
+ hmaps.NewOrderedIntSet(1, 2, 3, 4),
).Build(),
), qt.Equals, false)
c.Assert(sets.EqualsVector(
sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2),
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(2, 3, 4),
+ hmaps.NewOrderedIntSet(1, 2),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(2, 3, 4),
).Build(),
), qt.Equals, false)
testDims := newTestDims()
self := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(test.left.v0...),
- maps.NewOrderedIntSet(test.left.v1...),
- maps.NewOrderedIntSet(test.left.v2...),
+ hmaps.NewOrderedIntSet(test.left.v0...),
+ hmaps.NewOrderedIntSet(test.left.v1...),
+ hmaps.NewOrderedIntSet(test.left.v2...),
).Build()
var input []sitesmatrix.VectorProvider
for _, v := range test.input {
input = append(input, sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(v.v0...),
- maps.NewOrderedIntSet(v.v1...),
- maps.NewOrderedIntSet(v.v2...),
+ hmaps.NewOrderedIntSet(v.v0...),
+ hmaps.NewOrderedIntSet(v.v1...),
+ hmaps.NewOrderedIntSet(v.v2...),
).Build())
}
testDims := newTestDims()
sets1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1),
- maps.NewOrderedIntSet(1),
- maps.NewOrderedIntSet(1),
+ hmaps.NewOrderedIntSet(1),
+ hmaps.NewOrderedIntSet(1),
+ hmaps.NewOrderedIntSet(1),
).Build()
sets2 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2),
- maps.NewOrderedIntSet(1),
- maps.NewOrderedIntSet(1, 3),
+ hmaps.NewOrderedIntSet(1, 2),
+ hmaps.NewOrderedIntSet(1),
+ hmaps.NewOrderedIntSet(1, 3),
).Build()
c1 := sets2.Complement(sets1)
testDims := newTestDims()
sets1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3),
).Build()
sets1Copy := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3),
).Build()
sets2 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2, 3, 4),
- maps.NewOrderedIntSet(1, 2, 3, 4),
- maps.NewOrderedIntSet(1, 2, 3, 4, 6),
+ hmaps.NewOrderedIntSet(1, 2, 3, 4),
+ hmaps.NewOrderedIntSet(1, 2, 3, 4),
+ hmaps.NewOrderedIntSet(1, 2, 3, 4, 6),
).Build()
setsLanguage1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1),
- maps.NewOrderedIntSet(1),
- maps.NewOrderedIntSet(1),
+ hmaps.NewOrderedIntSet(1),
+ hmaps.NewOrderedIntSet(1),
+ hmaps.NewOrderedIntSet(1),
).Build()
b.ResetTimer()
testDims := newTestDims()
sets1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2),
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3),
).Build()
sets1Copy := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2),
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3),
).Build()
sets2 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(1, 2, 3, 4),
- maps.NewOrderedIntSet(1, 2, 3, 4),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3, 4),
+ hmaps.NewOrderedIntSet(1, 2, 3, 4),
).Build()
v1 := sitesmatrix.Vector{1, 2, 3}
b.Run("Build", func(b *testing.B) {
for b.Loop() {
_ = sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2),
- maps.NewOrderedIntSet(1, 2, 3),
- maps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2),
+ hmaps.NewOrderedIntSet(1, 2, 3),
+ hmaps.NewOrderedIntSet(1, 2, 3),
).Build()
}
})
c.Run("Complement", func(c *qt.C) {
v1 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 3),
- maps.NewOrderedIntSet(1),
- maps.NewOrderedIntSet(1, 3),
+ hmaps.NewOrderedIntSet(1, 3),
+ hmaps.NewOrderedIntSet(1),
+ hmaps.NewOrderedIntSet(1, 3),
).Build()
m := v1.ToVectorStoreMap()
v2 := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(1, 2),
- maps.NewOrderedIntSet(1),
- maps.NewOrderedIntSet(1, 3),
+ hmaps.NewOrderedIntSet(1, 2),
+ hmaps.NewOrderedIntSet(1),
+ hmaps.NewOrderedIntSet(1, 3),
).Build()
complement := m.Complement(v2)
func BenchmarkHasAnyVectorSingle(b *testing.B) {
testDims := newTestDims()
set := sitesmatrix.NewIntSetsBuilder(testDims).WithSets(
- maps.NewOrderedIntSet(0),
- maps.NewOrderedIntSet(0),
- maps.NewOrderedIntSet(0),
+ hmaps.NewOrderedIntSet(0),
+ hmaps.NewOrderedIntSet(0),
+ hmaps.NewOrderedIntSet(0),
).Build()
v := sitesmatrix.Vector{0, 0, 0}
import (
"context"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/resources/resource"
)
// BatcherClient is used to do JS batch operations.
type BatcherClient interface {
New(id string) (Batcher, error)
- Store() *maps.Cache[string, Batcher]
+ Store() *hmaps.Cache[string, Batcher]
}
// BatchPackage holds a group of JavaScript resources.
"github.com/evanw/esbuild/pkg/api"
"github.com/gohugoio/hugo/cache/dynacache"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hsync"
"github.com/gohugoio/hugo/common/hugio"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/helpers"
d: deps,
buildClient: NewBuildClient(deps.BaseFs.Assets, deps.ResourceSpec),
createClient: create.New(deps.ResourceSpec),
- batcherStore: maps.NewCache[string, js.Batcher](),
- bundlesStore: maps.NewCache[string, js.BatchPackage](),
+ batcherStore: hmaps.NewCache[string, js.Batcher](),
+ bundlesStore: hmaps.NewCache[string, js.BatchPackage](),
}
deps.BuildEndListeners.Add(func(...any) bool {
createClient *create.Client
buildClient *BuildClient
- batcherStore *maps.Cache[string, js.Batcher]
- bundlesStore *maps.Cache[string, js.BatchPackage]
+ batcherStore *hmaps.Cache[string, js.Batcher]
+ bundlesStore *hmaps.Cache[string, js.BatchPackage]
}
// New creates a new Batcher with the given ID.
return b, nil
}
-func (c *BatcherClient) Store() *maps.Cache[string, js.Batcher] {
+func (c *BatcherClient) Store() *hmaps.Cache[string, js.Batcher] {
return c.batcherStore
}
}
state := struct {
- importResource *maps.Cache[string, resource.Resource]
- resultResource *maps.Cache[string, resource.Resource]
- importerImportContext *maps.Cache[string, importContext]
- pathGroup *maps.Cache[string, string]
+ importResource *hmaps.Cache[string, resource.Resource]
+ resultResource *hmaps.Cache[string, resource.Resource]
+ importerImportContext *hmaps.Cache[string, importContext]
+ pathGroup *hmaps.Cache[string, string]
}{
- importResource: maps.NewCache[string, resource.Resource](),
- resultResource: maps.NewCache[string, resource.Resource](),
- importerImportContext: maps.NewCache[string, importContext](),
- pathGroup: maps.NewCache[string, string](),
+ importResource: hmaps.NewCache[string, resource.Resource](),
+ resultResource: hmaps.NewCache[string, resource.Resource](),
+ importerImportContext: hmaps.NewCache[string, importContext](),
+ pathGroup: hmaps.NewCache[string, string](),
}
multihostBasePaths := b.client.d.ResourceSpec.MultihostTargetBasePaths
"path/filepath"
"strings"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugio"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/identity"
var defines map[string]string
if opts.Defines != nil {
- defines = maps.ToStringMapString(opts.Defines)
+ defines = hmaps.ToStringMapString(opts.Defines)
}
var drop api.Drop
"fmt"
"os"
"path/filepath"
+ "slices"
"strings"
"github.com/evanw/esbuild/pkg/api"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/resources/resource"
"github.com/spf13/afero"
- "slices"
)
const (
}
func newFSResolver(fs afero.Fs) *fsResolver {
- return &fsResolver{fs: fs, resolved: maps.NewCache[string, *hugofs.FileMeta]()}
+ return &fsResolver{fs: fs, resolved: hmaps.NewCache[string, *hugofs.FileMeta]()}
}
type fsResolver struct {
fs afero.Fs
- resolved *maps.Cache[string, *hugofs.FileMeta]
+ resolved *hmaps.Cache[string, *hugofs.FileMeta]
}
func (r *fsResolver) resolveComponent(impPath string) *hugofs.FileMeta {
"github.com/bep/textandbinarywriter"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/hugio"
- "github.com/gohugoio/hugo/common/maps"
"golang.org/x/sync/errgroup"
"github.com/tetratelabs/wazero"
if err := q.init(); err != nil {
return nil, err
}
- responseKinds := maps.NewMap[string, bool]()
+ responseKinds := hmaps.NewMap[string, bool]()
for _, rk := range q.Header.ResponseKinds {
responseKinds.Set(rk, true)
}
type call[Q, R any] struct {
request Message[Q]
response Message[R]
- responseKinds *maps.Map[string, bool]
+ responseKinds *hmaps.Map[string, bool]
err error
donec chan *call[Q, R]
}
"golang.org/x/text/collate"
"golang.org/x/text/language"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/htime"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
"github.com/gohugoio/locales"
translators "github.com/gohugoio/localescompressed"
isDefault bool
// This is just an alias of Site.Params.
- params maps.Params
+ params hmaps.Params
}
// Name is an alias for Lang.
// Params returns the language params.
// Note that this is the same as the Site.Params, but we keep it here for legacy reasons.
// Deprecated: Use the site.Params instead.
-func (l *Language) Params() maps.Params {
+func (l *Language) Params() hmaps.Params {
// TODO(bep) Remove this for now as it created a little too much noise. Need to think about this.
// See https://github.com/gohugoio/hugo/issues/11025
// DeprecationFunc(".Language.Params", paramsDeprecationWarning, false)
// Internal access to unexported Language fields.
// This construct is to prevent them from leaking to the templates.
-func SetParams(l *Language, params maps.Params) {
+func SetParams(l *Language, params hmaps.Params) {
l.params = params
}
"github.com/gohugoio/hugo/markup/markup_config"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/markup/converter"
}
data, err := toml.Marshal(mconf)
c.Assert(err, qt.IsNil)
- m := maps.Params{
+ m := hmaps.Params{
"markup": config.FromTOMLConfigString(string(data)).Get(""),
}
conf := testconfig.GetTestConfig(nil, config.NewFrom(m))
package markup_config
import (
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/markup/asciidocext/asciidocext_config"
"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
if m == nil {
return
}
- m = maps.CleanConfigStringMap(m)
+ m = hmaps.CleanConfigStringMap(m)
normalizeConfig(m)
}
func normalizeConfig(m map[string]any) {
- v, err := maps.GetNestedParam("goldmark.parser", ".", m)
+ v, err := hmaps.GetNestedParam("goldmark.parser", ".", m)
if err == nil {
- vm := maps.ToStringMap(v)
+ vm := hmaps.ToStringMap(v)
// Changed from a bool in 0.81.0
if vv, found := vm["attribute"]; found {
if vvb, ok := vv.(bool); ok {
}
// Handle changes to the Goldmark configuration.
- v, err = maps.GetNestedParam("goldmark.extensions", ".", m)
+ v, err = hmaps.GetNestedParam("goldmark.extensions", ".", m)
if err == nil {
- vm := maps.ToStringMap(v)
+ vm := hmaps.ToStringMap(v)
// We changed the typographer extension config from a bool to a struct in 0.112.0.
migrateGoldmarkConfig(vm, "typographer", goldmark_config.Typographer{Disable: true})
"sort"
"strings"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/config"
buildConfig := func(v any) (ContentTypes, any, error) {
var s map[string]ContentTypeConfig
c := DefaultContentTypes
- m, err := maps.ToStringMapE(v)
+ m, err := hmaps.ToStringMapE(v)
if err != nil {
return c, nil, err
}
s = defaultContentTypesConfig
} else {
s = make(map[string]ContentTypeConfig)
- m = maps.CleanConfigStringMap(m)
+ m = hmaps.CleanConfigStringMap(m)
for k, v := range m {
var ctc ContentTypeConfig
if err := mapstructure.WeakDecode(v, &ctc); err != nil {
// DecodeTypes decodes the given map of media types.
func DecodeTypes(in map[string]any) (*config.ConfigNamespace[map[string]MediaTypeConfig, Types], error) {
buildConfig := func(v any) (Types, any, error) {
- m, err := maps.ToStringMapE(v)
+ m, err := hmaps.ToStringMapE(v)
if err != nil {
return nil, nil, err
}
if m == nil {
m = map[string]any{}
}
- m = maps.CleanConfigStringMap(m)
+ m = hmaps.CleanConfigStringMap(m)
// Merge with defaults.
- maps.MergeShallow(m, defaultMediaTypesConfig)
+ hmaps.MergeShallow(m, defaultMediaTypesConfig)
var types Types
if err := mapstructure.WeakDecode(v, &mediaType); err != nil {
return nil, nil, err
}
- mm := maps.ToStringMap(v)
- suffixes, _, found := maps.LookupEqualFold(mm, "suffixes")
+ mm := hmaps.ToStringMap(v)
+ suffixes, _, found := hmaps.LookupEqualFold(mm, "suffixes")
if found {
mediaType.SuffixesCSV = strings.TrimSpace(strings.ToLower(strings.Join(cast.ToStringSlice(suffixes), ",")))
}
import (
"fmt"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/maps"
"github.com/spf13/cast"
"github.com/mitchellh/mapstructure"
return
}
- m := maps.ToStringMap(v)
+ m := hmaps.ToStringMap(v)
// Handle deprecations.
if td, found := m["tdewolff"]; found {
- tdm := maps.ToStringMap(td)
+ tdm := hmaps.ToStringMap(td)
// Decimals was renamed to Precision in tdewolff/minify v2.7.0.
// https://github.com/tdewolff/minify/commit/2fed4401348ce36bd6c20e77335a463e69d94386
for _, key := range []string{"css", "svg"} {
if v, found := tdm[key]; found {
- vm := maps.ToStringMap(v)
+ vm := hmaps.ToStringMap(v)
ko := "decimals"
kn := "precision"
if vv, found := vm[ko]; found {
// KeepConditionalComments was renamed to KeepSpecialComments in tdewolff/minify v2.20.13.
// https://github.com/tdewolff/minify/commit/342cbc1974162db0ad3327f7a42a623b2cd3ebbc
if v, found := tdm["html"]; found {
- vm := maps.ToStringMap(v)
+ vm := hmaps.ToStringMap(v)
ko := "keepconditionalcomments"
kn := "keepspecialcomments"
if vv, found := vm[ko]; found {
// KeepCSS2 was deprecated in favor of Version in tdewolff/minify v2.24.1.
// https://github.com/tdewolff/minify/commit/57e3ebe0e6914b82c9ab0849a14f86bc29cd2ebf
if v, found := tdm["css"]; found {
- vm := maps.ToStringMap(v)
+ vm := hmaps.ToStringMap(v)
ko := "keepcss2"
kn := "version"
if vv, found := vm[ko]; found {
"github.com/bep/debounce"
"github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/version"
"github.com/spf13/cast"
- "github.com/gohugoio/hugo/common/maps"
-
"github.com/gohugoio/hugo/parser/metadecoders"
"github.com/gohugoio/hugo/hugofs/files"
if err != nil {
c.logger.Warnf("Failed to read module config for %q in %q: %s", tc.Path(), themeTOML, err)
} else {
- maps.PrepareParams(themeCfg)
+ hmaps.PrepareParams(themeCfg)
}
}
"io/fs"
"strings"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/hugofs/files"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/afero"
- "github.com/gohugoio/hugo/common/maps"
-
"github.com/gohugoio/hugo/helpers"
)
var commentsm map[string]any
comments, found := b.originalPackageJSON["comments"]
if found {
- commentsm = maps.ToStringMap(comments)
+ commentsm = hmaps.ToStringMap(comments)
} else {
commentsm = make(map[string]any)
}
// These packages will be added by order of import (project, module1, module2...),
// so that should at least give the project control over the situation.
if devDeps, found := m[devDependenciesKey]; found {
- mm := maps.ToStringMapString(devDeps)
+ mm := hmaps.ToStringMapString(devDeps)
for k, v := range mm {
if _, added := b.devDependencies[k]; !added {
b.devDependencies[k] = v
}
if deps, found := m[dependenciesKey]; found {
- mm := maps.ToStringMapString(deps)
+ mm := hmaps.ToStringMapString(deps)
for k, v := range mm {
if _, added := b.dependencies[k]; !added {
b.dependencies[k] = v
"slices"
"sort"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/compare"
"github.com/gohugoio/hugo/config"
IsPage() bool
IsSection() bool
IsAncestor(other any) bool
- Params() maps.Params
+ Params() hmaps.Params
}
// Menu is a collection of menu entries.
Weight int
Title string
// User defined params.
- Params maps.Params
+ Params hmaps.Params
}
// For internal use.
return ret, map[string]any{}, nil
}
- menus, err := maps.ToStringMapE(in)
+ menus, err := hmaps.ToStringMapE(in)
if err != nil {
return ret, nil, err
}
- menus = maps.CleanConfigStringMap(menus)
+ menus = hmaps.CleanConfigStringMap(menus)
for name, menu := range menus {
m, err := cast.ToSliceE(menu)
if err := mapstructure.WeakDecode(entry, &menuConfig); err != nil {
return ret, nil, err
}
- maps.PrepareParams(menuConfig.Params)
+ hmaps.PrepareParams(menuConfig.Params)
menuEntry := MenuEntry{
Menu: name,
MenuConfig: menuConfig,
import (
"fmt"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/types"
"github.com/mitchellh/mapstructure"
}
// Could be a structured menu entry
- menus, err := maps.ToStringMapE(ms)
+ menus, err := hmaps.ToStringMapE(ms)
if err != nil {
return pm, wrapErr(err)
}
for name, menu := range menus {
menuEntry := MenuEntry{Menu: name}
if menu != nil {
- ime, err := maps.ToStringMapE(menu)
+ ime, err := hmaps.ToStringMapE(menu)
if err != nil {
return pm, wrapErr(err)
}
"sort"
"strings"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/media"
"github.com/mitchellh/mapstructure"
f := make(Formats, len(DefaultFormats))
copy(f, DefaultFormats)
if in != nil {
- m, err := maps.ToStringMapE(in)
+ m, err := hmaps.ToStringMapE(in)
if err != nil {
return nil, nil, fmt.Errorf("failed convert config to map: %s", err)
}
- m = maps.CleanConfigStringMap(m)
+ m = hmaps.CleanConfigStringMap(m)
for k, v := range m {
found := false
"unsafe"
"github.com/gohugoio/hugo/common/herrors"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/niklasfasching/go-org/org"
xml "github.com/clbanning/mxj/v2"
switch typ.(type) {
case string:
return data, nil
- case map[string]any, maps.Params:
+ case map[string]any, hmaps.Params:
format := d.FormatFromContentString(data)
return d.UnmarshalToMap([]byte(data), format)
case []any:
"context"
"errors"
"fmt"
+ "maps"
"math"
"sort"
"strings"
"sync"
"time"
- xmaps "maps"
-
"github.com/gohugoio/hugo/common/collections"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/compare"
"github.com/gohugoio/hugo/markup/tableofcontents"
"github.com/spf13/cast"
}
// DecodeConfig decodes a slice of map into Config.
-func DecodeConfig(m maps.Params) (Config, error) {
+func DecodeConfig(m hmaps.Params) (Config, error) {
if m == nil {
return Config{}, errors.New("no related config provided")
}
c.Indices[i].Type = TypeBasic
}
if !validTypes[c.Indices[i].Type] {
- return c, fmt.Errorf("invalid index type %q. Must be one of %v", c.Indices[i].Type, xmaps.Keys(validTypes))
+ return c, fmt.Errorf("invalid index type %q. Must be one of %v", c.Indices[i].Type, maps.Keys(validTypes))
}
if icfg.CardinalityThreshold < 0 || icfg.CardinalityThreshold > 100 {
return Config{}, errors.New("cardinalityThreshold threshold must be between 0 and 100")
"strings"
"github.com/gohugoio/hugo/common/hashing"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/media"
"github.com/mitchellh/mapstructure"
}
buildConfig := func(in any) (ImagingConfigInternal, any, error) {
- m, err := maps.ToStringMapE(in)
+ m, err := hmaps.ToStringMapE(in)
if err != nil {
return ImagingConfigInternal{}, nil, err
}
// Merge in the defaults.
- maps.MergeShallow(m, defaultImaging)
+ hmaps.MergeShallow(m, defaultImaging)
var i ImagingConfigInternal
if err := mapstructure.Decode(m, &i.Imaging); err != nil {
"image/color"
"strings"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugio"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/resources/resource"
"github.com/makeworld-the-better-one/dither/v2"
"github.com/mitchellh/mapstructure"
linespacing: 2,
}
- var opt maps.Params
+ var opt hmaps.Params
if len(options) > 0 {
- opt = maps.MustToParamsAndPrepare(options[0])
+ opt = hmaps.MustToParamsAndPrepare(options[0])
for option, v := range opt {
switch option {
case "color":
"strings"
"github.com/gohugoio/hugo/common/hashing"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs/hglob"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
}
}
-func AddLangToCascadeTargetMap(lang string, m maps.Params) {
- maps.SetNestedParamIfNotSet("target.sites.matrix.languages", ".", lang, m)
+func AddLangToCascadeTargetMap(lang string, m hmaps.Params) {
+ hmaps.SetNestedParamIfNotSet("target.sites.matrix.languages", ".", lang, m)
}
func DecodeCascadeConfig(in any) (*PageMatcherParamsConfigs, error) {
return CascadeConfig{}, []map[string]any{}, nil
}
- ms, err := maps.ToSliceStringMap(in)
+ ms, err := hmaps.ToSliceStringMap(in)
if err != nil {
return CascadeConfig{}, nil, err
}
var cfgs []PageMatcherParamsConfig
for _, m := range ms {
- m = maps.CleanConfigStringMap(m)
+ m = hmaps.CleanConfigStringMap(m)
var (
c PageMatcherParamsConfig
err error
func (d cascadeConfigDecoder) mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, error) {
var pcfg PageMatcherParamsConfig
if pcfg.Fields == nil {
- pcfg.Fields = make(maps.Params)
+ pcfg.Fields = make(hmaps.Params)
}
if pcfg.Params == nil {
- pcfg.Params = make(maps.Params)
+ pcfg.Params = make(hmaps.Params)
}
for k, v := range m {
}
pcfg.Target = target
case "params":
- params := maps.ToStringMap(v)
+ params := hmaps.ToStringMap(v)
for k, v := range params {
if _, found := pcfg.Params[k]; !found {
pcfg.Params[k] = v
type PageMatcherParamsConfig struct {
// Apply Params to all Pages matching Target.
- Params maps.Params
+ Params hmaps.Params
// Fields holds all fields but Params.
- Fields maps.Params
+ Fields hmaps.Params
// Target is the PageMatcher that this config applies to.
Target PageMatcher
}
func (p *PageMatcherParamsConfig) init() error {
- maps.PrepareParams(p.Params)
- maps.PrepareParams(p.Fields)
+ hmaps.PrepareParams(p.Params)
+ hmaps.PrepareParams(p.Fields)
return nil
}
func (p *PageMatcherParamsConfig) hasSitesMatrix() bool {
if m, ok := p.Fields["sites"]; ok {
- mm := maps.ToStringMap(m)
+ mm := hmaps.ToStringMap(m)
if sm, found := mm["matrix"]; found {
- mmm := maps.ToStringMap(sm)
+ mmm := hmaps.ToStringMap(sm)
return len(mmm) > 0
}
}
"path/filepath"
"testing"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
qt "github.com/frankban/quicktest"
return v
}
c.Assert(fn(map[string]any{"_target": map[string]any{"kind": "page"}, "foo": "bar"}), qt.DeepEquals, PageMatcherParamsConfig{
- Params: maps.Params{},
- Fields: maps.Params{
+ Params: hmaps.Params{},
+ Fields: hmaps.Params{
"foo": "bar",
},
Target: PageMatcher{Path: "", Kind: "page", Lang: "", Environment: ""},
})
c.Assert(fn(map[string]any{"target": map[string]any{"kind": "page"}, "params": map[string]any{"foo": "bar"}}), qt.DeepEquals, PageMatcherParamsConfig{
- Params: maps.Params{
+ Params: hmaps.Params{
"foo": "bar",
},
- Fields: maps.Params{},
+ Fields: hmaps.Params{},
Target: PageMatcher{Path: "", Kind: "page", Lang: "", Environment: ""},
})
})
c.Assert(got.c[0].Config.Cascades, qt.HasLen, 2)
first := got.c[0].Config.Cascades[0]
c.Assert(first, qt.DeepEquals, PageMatcherParamsConfig{
- Params: maps.Params{
+ Params: hmaps.Params{
"a": "av",
},
- Fields: maps.Params{},
+ Fields: hmaps.Params{},
Target: PageMatcher{
Kind: "page",
Sites: sitesmatrix.Sites{},
c.Assert(got.c[0].SourceStructure, qt.DeepEquals, []PageMatcherParamsConfig{
{
- Params: maps.Params{"a": string("av")},
- Fields: maps.Params{},
+ Params: hmaps.Params{"a": string("av")},
+ Fields: hmaps.Params{},
Target: PageMatcher{Kind: "page", Environment: "production"},
},
- {Params: maps.Params{"b": string("bv")}, Fields: maps.Params{}, Target: PageMatcher{Kind: "page"}},
+ {Params: hmaps.Params{"b": string("bv")}, Fields: hmaps.Params{}, Target: PageMatcher{Kind: "page"}},
})
got, err = DecodeCascadeConfig(nil)
"github.com/gohugoio/hugo/navigation"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstore"
"github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/source"
return nil, nil
}
-func (p *nopPage) Params() maps.Params {
+func (p *nopPage) Params() hmaps.Params {
return nil
}
"strings"
"time"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hreflect"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/htime"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/hugofs/files"
func (pcfg *PageConfigEarly) setFromFrontMatter(frontmatter map[string]any) error {
// Needed for case insensitive fetching of params values.
- maps.PrepareParams(frontmatter)
+ hmaps.PrepareParams(frontmatter)
pcfg.Frontmatter = frontmatter
if v, found := frontmatter[pageMetaKeyMarkup]; found {
func (p *PageConfigEarly) setCascadeEarlyValueIfNotSet(key string, value any) (done bool) {
switch key {
case pageMetaKeySites:
- p.Sites.SetFromParamsIfNotSet(value.(maps.Params))
+ p.Sites.SetFromParamsIfNotSet(value.(hmaps.Params))
}
return !p.Sites.Matrix.IsZero()
Sites sitesmatrix.Sites
Content Source // Content holds the content for this page.
- Frontmatter maps.Params `mapstructure:"-" json:"-"` // The original front matter or content adapter map.
+ Frontmatter hmaps.Params `mapstructure:"-" json:"-"` // The original front matter or content adapter map.
// Compiled values.
SitesMatrixAndComplements `mapstructure:"-" json:"-"`
type PageConfigLate struct {
Dates Dates `json:"-"` // Dates holds the four core dates for this page.
DatesStrings
- Params maps.Params // User defined params.
+ Params hmaps.Params // User defined params.
Title string // The title of the page.
LinkTitle string // The link title of the page.
}
if p.Params == nil {
- p.Params = make(maps.Params)
+ p.Params = make(hmaps.Params)
} else {
- maps.PrepareParams(p.Params)
+ hmaps.PrepareParams(p.Params)
}
if len(p.Outputs) > 0 {
Path string
Name string
Title string
- Params maps.Params
+ Params hmaps.Params
Content Source
Sites sitesmatrix.Sites
func (rc *ResourceConfig) Compile(basePath string, fim hugofs.FileMetaInfo, conf config.AllProvider, mediaTypes media.Types) error {
if rc.Params != nil {
- maps.PrepareParams(rc.Params)
+ hmaps.PrepareParams(rc.Params)
}
// Note that NormalizePathStringBasic will make sure that we don't preserve the unnormalized path.
"strings"
"time"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/resources/kinds"
)
urlize func(uri string) string
- patternCache *maps.Cache[string, func(Page) (string, error)]
+ patternCache *hmaps.Cache[string, func(Page) (string, error)]
}
// Time for checking date formats. Every field is different than the
func NewPermalinkExpander(urlize func(uri string) string, patterns map[string]map[string]string) (PermalinkExpander, error) {
p := PermalinkExpander{
urlize: urlize,
- patternCache: maps.NewCache[string, func(Page) (string, error)](),
+ patternCache: hmaps.NewCache[string, func(Page) (string, error)](),
}
p.knownPermalinkAttributes = map[string]pageToPermaAttribute{
permalinksConfig[kinds.KindTaxonomy] = make(map[string]string)
permalinksConfig[kinds.KindTerm] = make(map[string]string)
- config := maps.CleanConfigStringMap(m)
+ config := hmaps.CleanConfigStringMap(m)
for k, v := range config {
switch v := v.(type) {
case string:
permalinksConfig[kinds.KindPage][k] = v
permalinksConfig[kinds.KindTerm][k] = v
- case maps.Params:
+ case hmaps.Params:
// [permalinks.key]
// xyz = ???
import (
"time"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstore"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config/privacy"
"github.com/gohugoio/hugo/config/services"
"github.com/gohugoio/hugo/hugolib/roles"
MainSections() []string
// Returns the Params configured for this site.
- Params() maps.Params
+ Params() hmaps.Params
// Param is a convenience method to do lookups in Params.
Param(key any) (any, error)
return s.s.MainSections()
}
-func (s *siteWrapper) Params() maps.Params {
+func (s *siteWrapper) Params() hmaps.Params {
return s.s.Params()
}
return ""
}
-func (t testSite) Params() maps.Params {
+func (t testSite) Params() hmaps.Params {
return nil
}
"github.com/gohugoio/hugo/navigation"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstore"
"github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs"
return resource.Param(p, nil, key)
}
-func (p *testPage) Params() maps.Params {
+func (p *testPage) Params() hmaps.Params {
return p.params
}
"github.com/spf13/cast"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hreflect"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources/resource"
)
return r.field("Title")
}
-func (r *PostPublishResource) Params() maps.Params {
+func (r *PostPublishResource) Params() hmaps.Params {
panic(r.fieldNotSupported("Params"))
}
"github.com/gohugoio/hugo/common/hashing"
"github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hsync"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/common/hugio"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/resources/resource"
"github.com/gohugoio/hugo/helpers"
Data map[string]any
// The Params to associate with this resource.
- Params maps.Params
+ Params hmaps.Params
// Delay publishing until either Permalink or RelPermalink is called. Maybe never.
LazyPublish bool
}
if fd.Params == nil {
- fd.Params = make(maps.Params)
+ fd.Params = make(hmaps.Params)
}
if fd.Path == nil {
return l.sd.NameNormalized
}
-func (l *genericResource) Params() maps.Params {
+func (l *genericResource) Params() hmaps.Params {
return l.params
}
package resource
import (
- "github.com/gohugoio/hugo/common/maps"
-
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/spf13/cast"
)
-func Param(r ResourceParamsProvider, fallback maps.Params, key any) (any, error) {
+func Param(r ResourceParamsProvider, fallback hmaps.Params, key any) (any, error) {
keyStr, err := cast.ToStringE(key)
if err != nil {
return nil, err
}
if fallback == nil {
- return maps.GetNestedParam(keyStr, ".", r.Params())
+ return hmaps.GetNestedParam(keyStr, ".", r.Params())
}
- return maps.GetNestedParam(keyStr, ".", r.Params(), fallback)
+ return hmaps.GetNestedParam(keyStr, ".", r.Params(), fallback)
}
"strings"
"time"
- "github.com/gohugoio/hugo/common/maps"
-
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/helpers"
"github.com/pelletier/go-toml/v2"
}
func getParam(r Resource, key string, stringToLower bool) any {
- v, err := maps.GetNestedParam(key, ".", r.Params())
+ v, err := hmaps.GetNestedParam(key, ".", r.Params())
if v == nil || err != nil {
return nil
"slices"
"strings"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hreflect"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/hugofs/hglob"
"github.com/spf13/cast"
}
return &cachedResourceGetter{
- cache: maps.NewCache[string, Resource](),
+ cache: hmaps.NewCache[string, Resource](),
delegate: getters,
}
}
)
type cachedResourceGetter struct {
- cache *maps.Cache[string, Resource]
+ cache *hmaps.Cache[string, Resource]
delegate ResourceGetter
}
import (
"context"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/media"
type ResourceParamsProvider interface {
// Params set in front matter for this resource.
- Params() maps.Params
+ Params() hmaps.Params
}
type ResourceDataProvider interface {
"context"
"fmt"
"io"
+ "maps"
"math/rand"
"mime"
"net/http"
"strings"
"time"
- gmaps "maps"
-
"github.com/gohugoio/httpcache"
"github.com/gohugoio/hugo/common/hashing"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/hugio"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/tasks"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
}
method := "GET"
- if s, _, ok := maps.LookupEqualFold(optionsm, "method"); ok {
+ if s, _, ok := hmaps.LookupEqualFold(optionsm, "method"); ok {
method = strings.ToUpper(s.(string))
}
isHeadMethod := method == "HEAD"
- optionsm = gmaps.Clone(optionsm)
+ optionsm = maps.Clone(optionsm)
userKey, optionsKey := remoteResourceKeys(uri, optionsm)
// A common pattern is to use the key in the options map as
func remoteResourceKeys(uri string, optionsm map[string]any) (string, string) {
var userKey string
- if key, k, found := maps.LookupEqualFold(optionsm, "key"); found {
+ if key, k, found := hmaps.LookupEqualFold(optionsm, "key"); found {
userKey = hashing.HashString(key)
delete(optionsm, k)
}
maps0 "maps"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/paths"
)
changed bool
title string
name string
- params maps.Params
+ params hmaps.Params
}
func (r *metaResource) Name() string {
return r.title
}
-func (r *metaResource) Params() maps.Params {
+func (r *metaResource) Params() hmaps.Params {
return r.params
}
params, found := meta["params"]
if found {
- m := maps.ToStringMap(params)
+ m := hmaps.ToStringMap(params)
// Needed for case insensitive fetching of params values
- maps.PrepareParams(m)
+ hmaps.PrepareParams(m)
ma.updateParams(m)
}
}
"github.com/gohugoio/hugo/common/constants"
"github.com/gohugoio/hugo/common/hashing"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/common/herrors"
"github.com/gohugoio/hugo/common/hugio"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/resources/internal"
"github.com/gohugoio/hugo/resources/resource"
// to be simple types, as it needs to be serialized to JSON and back.
Data map[string]any
- // This is used to publish additional artifacts, e.g. source maps.
+ // This is used to publish additional artifacts, e.g. source hhmaps.
// We may improve this.
OpenResourcePublisher func(relTargetPath string) (io.WriteCloser, error)
}
return r.target.(resource.NameNormalizedProvider).NameNormalized()
}
-func (r *resourceAdapter) Params() maps.Params {
+func (r *resourceAdapter) Params() hmaps.Params {
r.init(false, false)
return r.metaProvider.Params()
}
"time"
"github.com/gohugoio/hugo/common/collections"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hreflect"
"github.com/gohugoio/hugo/common/hstore"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/langs"
}
loc := langs.GetLocation(language)
- dCache := maps.NewCacheWithOptions[dKey, []int](maps.CacheOptions{Size: 100})
+ dCache := hmaps.NewCacheWithOptions[dKey, []int](hmaps.CacheOptions{Size: 100})
return &Namespace{
loc: loc,
type Namespace struct {
loc *time.Location
sortComp *compare.Namespace
- dCache *maps.Cache[dKey, []int]
+ dCache *hmaps.Cache[dKey, []int]
deps *deps.Deps
}
"testing"
"time"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/config/testconfig"
qt "github.com/frankban/quicktest"
}
type TstParams struct {
- params maps.Params
+ params hmaps.Params
}
-func (x TstParams) Params() maps.Params {
+func (x TstParams) Params() hmaps.Params {
return x.params
}
"github.com/spf13/cast"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hreflect"
- "github.com/gohugoio/hugo/common/maps"
)
// Index returns the result of indexing its first argument by the following
indices = args
}
- lowerm, ok := item.(maps.Params)
+ lowerm, ok := item.(hmaps.Params)
if ok {
return lowerm.GetNested(cast.ToStringSlice(indices)...), nil
}
"fmt"
"testing"
- "github.com/gohugoio/hugo/common/maps"
-
qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/common/hmaps"
)
func TestIndex(t *testing.T) {
{map[string]map[string]string{"a": {"b": "c"}}, []any{"a", "b"}, "c", false},
{[]map[string]map[string]string{{"a": {"b": "c"}}}, []any{0, "a", "b"}, "c", false},
{map[string]map[string]any{"a": {"b": []string{"c", "d"}}}, []any{"a", "b", 1}, "d", false},
- {maps.Params{"a": "av"}, []any{"A"}, "av", false},
- {maps.Params{"a": map[string]any{"b": "bv"}}, []any{"A", "B"}, "bv", false},
+ {hmaps.Params{"a": "av"}, []any{"A"}, "av", false},
+ {hmaps.Params{"a": map[string]any{"b": "bv"}}, []any{"A", "B"}, "bv", false},
// These used to be errors.
// See issue 10489.
"reflect"
"strings"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hreflect"
- "github.com/gohugoio/hugo/common/maps"
)
// Merge creates a copy of the final parameter in params and merges the preceding
out := reflect.MakeMap(dst.Type())
// If the destination is Params, we must lower case all keys.
- _, lowerCase := dst.Interface().(maps.Params)
+ _, lowerCase := dst.Interface().(hmaps.Params)
k := reflect.New(dst.Type().Key()).Elem()
v := reflect.New(dst.Type().Elem()).Elem()
"reflect"
"testing"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/parser"
"github.com/gohugoio/hugo/parser/metadecoders"
"params dst",
[]any{
map[string]any{"a": 42, "c": 3},
- maps.Params{"a": 1, "b": 2},
+ hmaps.Params{"a": 1, "b": 2},
},
- maps.Params{"a": int(1), "b": int(2), "c": int(3)},
+ hmaps.Params{"a": int(1), "b": int(2), "c": int(3)},
false,
},
{
"params dst, upper case src",
[]any{
map[string]any{"a": 42, "C": 3},
- maps.Params{"a": 1, "b": 2},
+ hmaps.Params{"a": 1, "b": 2},
},
- maps.Params{"a": int(1), "b": int(2), "c": int(3)},
+ hmaps.Params{"a": int(1), "b": int(2), "c": int(3)},
false,
},
{
"params src",
[]any{
- maps.Params{"a": 42, "c": 3},
+ hmaps.Params{"a": 42, "c": 3},
map[string]any{"a": 1, "c": 2},
},
map[string]any{"a": int(1), "c": int(2)},
{
"params src, upper case dst",
[]any{
- maps.Params{"a": 42, "c": 3},
+ hmaps.Params{"a": 42, "c": 3},
map[string]any{"a": 1, "C": 2},
},
map[string]any{"a": int(1), "C": int(2)},
"nested, params dst",
[]any{
map[string]any{"a": 42, "c": 3, "b": map[string]any{"d": 55, "e": 66, "f": 3}},
- maps.Params{"a": 1, "b": maps.Params{"d": 1, "e": 2}},
+ hmaps.Params{"a": 1, "b": hmaps.Params{"d": 1, "e": 2}},
},
- maps.Params{"a": 1, "b": maps.Params{"d": 1, "e": 2, "f": 3}, "c": 3},
+ hmaps.Params{"a": 1, "b": hmaps.Params{"d": 1, "e": 2, "f": 3}, "c": 3},
false,
},
{
{"different map types", []any{map[int]any{32: "a"}, simpleMap}, nil, true},
{"all nil", []any{nil, nil}, nil, true},
} {
-
t.Run(test.name, func(t *testing.T) {
t.Parallel()
errMsg := qt.Commentf("[%d] %v", i, test)
"errors"
"net/url"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/spf13/cast"
)
switch v := params[0].(type) {
case map[string]any: // created with collections.Dictionary
return mapToQueryString(v)
- case maps.Params: // site configuration or page parameters
+ case hmaps.Params: // site configuration or page parameters
return mapToQueryString(v)
case []string:
return stringSliceToQueryString(v)
// mapToQueryString returns a URL query string derived from the given string
// map, encoded and sorted by key. The function returns an error if it cannot
// convert an element value to a string.
-func mapToQueryString[T map[string]any | maps.Params](m T) (string, error) {
+func mapToQueryString[T map[string]any | hmaps.Params](m T) (string, error) {
if len(m) == 0 {
return "", nil
}
"testing"
qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
)
func TestQuerify(t *testing.T) {
expect any
}{
// map
- {"01", []any{maps.Params{"a": "foo", "b": "bar"}}, `a=foo&b=bar`},
- {"02", []any{maps.Params{"a": 6, "b": 7}}, `a=6&b=7`},
- {"03", []any{maps.Params{"a": "foo", "b": 7}}, `a=foo&b=7`},
+ {"01", []any{hmaps.Params{"a": "foo", "b": "bar"}}, `a=foo&b=bar`},
+ {"02", []any{hmaps.Params{"a": 6, "b": 7}}, `a=6&b=7`},
+ {"03", []any{hmaps.Params{"a": "foo", "b": 7}}, `a=foo&b=7`},
{"04", []any{map[string]any{"a": "foo", "b": "bar"}}, `a=foo&b=bar`},
{"05", []any{map[string]any{"a": 6, "b": 7}}, `a=6&b=7`},
{"06", []any{map[string]any{"a": "foo", "b": 7}}, `a=foo&b=7`},
// no arguments
{"16", []any{}, ``},
// errors: zero key length
- {"17", []any{maps.Params{"": "foo"}}, false},
+ {"17", []any{hmaps.Params{"": "foo"}}, false},
{"18", []any{map[string]any{"": "foo"}}, false},
{"19", []any{[]string{"", "foo"}}, false},
{"20", []any{[]any{"", 6}}, false},
"sort"
"strings"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hreflect"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/tpl/compare"
"github.com/spf13/cast"
if !v.IsValid() {
continue
}
- // Special handling of lower cased maps.
- if params, ok := v.Interface().(maps.Params); ok {
+ // Special handling of lower cased hmaps.
+ if params, ok := v.Interface().(hmaps.Params); ok {
v = reflect.ValueOf(params.GetNested(path[i+1:]...))
break
}
if !v.IsValid() {
continue
}
- // Special handling of lower cased maps.
- if params, ok := v.Interface().(maps.Params); ok {
+ // Special handling of lower cased hmaps.
+ if params, ok := v.Interface().(hmaps.Params); ok {
v = reflect.ValueOf(params.GetNested(path[j+1:]...))
break
}
"reflect"
"testing"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
)
type stringsSlice []string
},
// Lower case Params, slice
{
- []TstParams{{params: maps.Params{"color": "indigo"}}, {params: maps.Params{"color": "blue"}}, {params: maps.Params{"color": "green"}}},
+ []TstParams{{params: hmaps.Params{"color": "indigo"}}, {params: hmaps.Params{"color": "blue"}}, {params: hmaps.Params{"color": "green"}}},
".Params.COLOR",
"asc",
- []TstParams{{params: maps.Params{"color": "blue"}}, {params: maps.Params{"color": "green"}}, {params: maps.Params{"color": "indigo"}}},
+ []TstParams{{params: hmaps.Params{"color": "blue"}}, {params: hmaps.Params{"color": "green"}}, {params: hmaps.Params{"color": "indigo"}}},
},
// Lower case Params, map
{
- map[string]TstParams{"1": {params: maps.Params{"color": "indigo"}}, "2": {params: maps.Params{"color": "blue"}}, "3": {params: maps.Params{"color": "green"}}},
+ map[string]TstParams{"1": {params: hmaps.Params{"color": "indigo"}}, "2": {params: hmaps.Params{"color": "blue"}}, "3": {params: hmaps.Params{"color": "green"}}},
".Params.CoLoR",
"asc",
- []TstParams{{params: maps.Params{"color": "blue"}}, {params: maps.Params{"color": "green"}}, {params: maps.Params{"color": "indigo"}}},
+ []TstParams{{params: hmaps.Params{"color": "blue"}}, {params: hmaps.Params{"color": "green"}}, {params: hmaps.Params{"color": "indigo"}}},
},
// test map sorting by struct's method
{
"reflect"
"strings"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hreflect"
"github.com/gohugoio/hugo/common/hstrings"
- "github.com/gohugoio/hugo/common/maps"
)
// Where returns a filtered subset of collection c.
rvv := seqv.Index(i)
if kv.Kind() == reflect.String {
- if params, ok := rvv.Interface().(maps.Params); ok {
+ if params, ok := rvv.Interface().(hmaps.Params); ok {
vvv = reflect.ValueOf(params.GetNested(path...))
} else {
vvv = rvv
}
if i < len(path)-1 && vvv.IsValid() {
- if params, ok := vvv.Interface().(maps.Params); ok {
+ if params, ok := vvv.Interface().(hmaps.Params); ok {
// The current path element is the map itself, .Params.
vvv = reflect.ValueOf(params.GetNested(path[i+1:]...))
break
"testing"
"time"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
)
func TestWhere(t *testing.T) {
{1: "a", 2: "m"},
},
},
- // Case insensitive maps.Params
+ // Case insensitive hmaps.Params
// Slice of structs
{
- seq: []TstParams{{params: maps.Params{"i": 0, "color": "indigo"}}, {params: maps.Params{"i": 1, "color": "blue"}}, {params: maps.Params{"i": 2, "color": "green"}}, {params: maps.Params{"i": 3, "color": "blue"}}},
+ seq: []TstParams{{params: hmaps.Params{"i": 0, "color": "indigo"}}, {params: hmaps.Params{"i": 1, "color": "blue"}}, {params: hmaps.Params{"i": 2, "color": "green"}}, {params: hmaps.Params{"i": 3, "color": "blue"}}},
key: ".Params.COLOR", match: "blue",
- expect: []TstParams{{params: maps.Params{"i": 1, "color": "blue"}}, {params: maps.Params{"i": 3, "color": "blue"}}},
+ expect: []TstParams{{params: hmaps.Params{"i": 1, "color": "blue"}}, {params: hmaps.Params{"i": 3, "color": "blue"}}},
},
{
- seq: []TstParams{{params: maps.Params{"nested": map[string]any{"color": "indigo"}}}, {params: maps.Params{"nested": map[string]any{"color": "blue"}}}},
+ seq: []TstParams{{params: hmaps.Params{"nested": map[string]any{"color": "indigo"}}}, {params: hmaps.Params{"nested": map[string]any{"color": "blue"}}}},
key: ".Params.NEsTED.COLOR", match: "blue",
- expect: []TstParams{{params: maps.Params{"nested": map[string]any{"color": "blue"}}}},
+ expect: []TstParams{{params: hmaps.Params{"nested": map[string]any{"color": "blue"}}}},
},
{
- seq: []TstParams{{params: maps.Params{"i": 0, "color": "indigo"}}, {params: maps.Params{"i": 1, "color": "blue"}}, {params: maps.Params{"i": 2, "color": "green"}}, {params: maps.Params{"i": 3, "color": "blue"}}},
+ seq: []TstParams{{params: hmaps.Params{"i": 0, "color": "indigo"}}, {params: hmaps.Params{"i": 1, "color": "blue"}}, {params: hmaps.Params{"i": 2, "color": "green"}}, {params: hmaps.Params{"i": 3, "color": "blue"}}},
key: ".Params", match: "blue",
expect: []TstParams{},
},
// Slice of maps
{
- seq: []maps.Params{
+ seq: []hmaps.Params{
{"a": "a1", "b": "b1"}, {"a": "a2", "b": "b2"},
},
key: "B", match: "b2",
- expect: []maps.Params{
+ expect: []hmaps.Params{
{"a": "a2", "b": "b2"},
},
},
{
- seq: []maps.Params{
+ seq: []hmaps.Params{
{
"a": map[string]any{
"b": "b1",
},
},
key: "A.B", match: "b2",
- expect: []maps.Params{
+ expect: []hmaps.Params{
{
"a": map[string]any{
"b": "b2",
},
{
seq: map[string]any{
- "foo": []any{maps.Params{"a": 1, "b": 2}},
- "bar": []any{maps.Params{"a": 3, "b": 4}},
- "zap": []any{maps.Params{"a": 5, "b": 6}},
+ "foo": []any{hmaps.Params{"a": 1, "b": 2}},
+ "bar": []any{hmaps.Params{"a": 3, "b": 4}},
+ "zap": []any{hmaps.Params{"a": 5, "b": 6}},
},
key: "B", op: ">", match: 3,
expect: map[string]any{
- "bar": []any{maps.Params{"a": 3, "b": 4}},
- "zap": []any{maps.Params{"a": 5, "b": 6}},
+ "bar": []any{hmaps.Params{"a": 3, "b": 4}},
+ "zap": []any{hmaps.Params{"a": 5, "b": 6}},
},
},
} {
"fmt"
"sync"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types/css"
"github.com/gohugoio/hugo/deps"
}
if m != nil {
- if t, _, found := maps.LookupEqualFold(m, "transpiler"); found {
+ if t, _, found := hmaps.LookupEqualFold(m, "transpiler"); found {
switch t {
case sass.TranspilerDart:
transpiler = cast.ToString(t)
"errors"
"fmt"
"net/http"
+ "slices"
"strings"
"github.com/gohugoio/hugo/cache/filecache"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/config/security"
"github.com/gohugoio/hugo/common/types"
"github.com/spf13/cast"
"github.com/gohugoio/hugo/deps"
- "slices"
)
// New returns a new instance of the data-namespaced template functions.
}
// The last argument may be a map.
- headers, err := maps.ToStringMapE(urlParts[len(urlParts)-1])
+ headers, err := hmaps.ToStringMapE(urlParts[len(urlParts)-1])
if err == nil {
urlParts = urlParts[:len(urlParts)-1]
} else {
"testing"
"github.com/bep/logg"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
qt "github.com/frankban/quicktest"
)
},
{
`Params`,
- maps.Params{
+ hmaps.Params{
"Accept-Charset": "utf-8",
},
func(c *qt.C, headers string) {
"html/template"
bp "github.com/gohugoio/hugo/bufferpool"
+ "github.com/gohugoio/hugo/common/hmaps"
- "github.com/gohugoio/hugo/common/maps"
"github.com/mitchellh/mapstructure"
"github.com/spf13/cast"
)
obj = args[0]
case 2:
var m map[string]any
- m, err = maps.ToStringMapE(args[0])
+ m, err = hmaps.ToStringMapE(args[0])
if err != nil {
break
}
"errors"
"fmt"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/resources"
)
return nil, nil, fmt.Errorf("type %T not supported in Resource transformations", args[0])
}
- m, err := maps.ToStringMapE(args[0])
+ m, err := hmaps.ToStringMapE(args[0])
if err != nil {
return nil, nil, fmt.Errorf("invalid options type: %w", err)
}
kopenapi3 "github.com/getkin/kin-openapi/openapi3"
"github.com/gohugoio/hugo/cache/dynacache"
"github.com/gohugoio/hugo/common/hashing"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/parser/metadecoders"
var opts unmarshalOptions
if len(args) > 1 {
- optsm, err := maps.ToStringMapE(args[1])
+ optsm, err := hmaps.ToStringMapE(args[1])
if err != nil {
return nil, err
}
"errors"
"fmt"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hugo"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/tpl/css"
"github.com/gohugoio/hugo/tpl/js"
var options map[string]any
if len(args) > 1 {
- options, err = maps.ToStringMapE(args[1])
+ options, err = hmaps.ToStringMapE(args[1])
if err != nil {
return nil, err
}
"reflect"
"strings"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hreflect"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/tpl"
}
func (t *templateExecHelper) GetMapValue(ctx context.Context, tmpl texttemplate.Preparer, receiver, key reflect.Value) (reflect.Value, bool) {
- if params, ok := receiver.Interface().(maps.Params); ok {
+ if params, ok := receiver.Interface().(hmaps.Params); ok {
// Case insensitive.
keystr := strings.ToLower(key.String())
v, found := params[keystr]
return v, v.IsValid()
}
-var typeParams = reflect.TypeOf(maps.Params{})
+var typeParams = reflect.TypeOf(hmaps.Params{})
func (t *templateExecHelper) GetMethod(ctx context.Context, tmpl texttemplate.Preparer, receiver reflect.Value, name string) (method reflect.Value, firstArg reflect.Value) {
if strings.EqualFold(name, "mainsections") && receiver.Type() == typeParams && receiver.Pointer() == t.siteParams.Pointer() {
"github.com/gohugoio/hugo/common/collections"
"github.com/gohugoio/hugo/common/herrors"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstrings"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/maps"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
storeSite: configureSiteStorage(siteOpts, opts.Watching),
treeMain: doctree.NewSimpleTree[map[nodeKey]*TemplInfo](),
treeShortcodes: doctree.NewSimpleTree[map[string]map[TemplateDescriptor]*TemplInfo](),
- templatesByPath: maps.NewCache[string, *TemplInfo](),
- shortcodesByName: maps.NewCache[string, *TemplInfo](),
- cacheLookupPartials: maps.NewCache[string, *TemplInfo](),
- templatesSnapshotSet: maps.NewCache[*parse.Tree, struct{}](),
+ templatesByPath: hmaps.NewCache[string, *TemplInfo](),
+ shortcodesByName: hmaps.NewCache[string, *TemplInfo](),
+ cacheLookupPartials: hmaps.NewCache[string, *TemplInfo](),
+ templatesSnapshotSet: hmaps.NewCache[*parse.Tree, struct{}](),
// Note that the funcs passed below is just for name validation.
tns: newTemplateNamespace(siteOpts.TemplateFuncs),
treeMain *doctree.SimpleTree[map[nodeKey]*TemplInfo]
treeShortcodes *doctree.SimpleTree[map[string]map[TemplateDescriptor]*TemplInfo]
- templatesByPath *maps.Cache[string, *TemplInfo]
- shortcodesByName *maps.Cache[string, *TemplInfo]
- templatesSnapshotSet *maps.Cache[*parse.Tree, struct{}]
+ templatesByPath *hmaps.Cache[string, *TemplInfo]
+ shortcodesByName *hmaps.Cache[string, *TemplInfo]
+ templatesSnapshotSet *hmaps.Cache[*parse.Tree, struct{}]
dh descriptorHandler
siteOptsOrig SiteOptions
// caches. These need to be refreshed when the templates are refreshed.
- cacheLookupPartials *maps.Cache[string, *TemplInfo]
+ cacheLookupPartials *hmaps.Cache[string, *TemplInfo]
}
// NewFromOpts creates a new store with the same configuration as the original.
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
"github.com/gohugoio/hugo/common/hashing"
- "github.com/gohugoio/hugo/common/maps"
+ "github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/tpl"
"github.com/mitchellh/mapstructure"
)
if s, ok := cmd.Args[0].(*parse.StringNode); ok {
errMsg := "failed to decode $_hugo_config in template: %w"
- m, err := maps.ToStringMapE(s.Text)
+ m, err := hmaps.ToStringMapE(s.Text)
if err != nil {
c.err = fmt.Errorf(errMsg, err)
return