From: Bjørn Erik Pedersen Date: Fri, 16 Jan 2026 10:47:46 +0000 (+0100) Subject: Rename common/maps to common/hmaps (#14384) X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=608ed09a0a86e0477cffe63322d90e1dd31d167b;p=brevno-suite%2Fhugo Rename common/maps to common/hmaps (#14384) 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. --- diff --git a/cache/filecache/filecache_config.go b/cache/filecache/filecache_config.go index d52981174..b4e4efdc2 100644 --- a/cache/filecache/filecache_config.go +++ b/cache/filecache/filecache_config.go @@ -22,7 +22,7 @@ import ( "strings" "time" - "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/common/hmaps" "github.com/gohugoio/hugo/config" "github.com/mitchellh/mapstructure" @@ -154,7 +154,7 @@ func DecodeConfig(fs afero.Fs, bcfg config.BaseConfig, m map[string]any) (Config _, isOsFs := fs.(*afero.OsFs) for k, v := range m { - if _, ok := v.(maps.Params); !ok { + if _, ok := v.(hmaps.Params); !ok { continue } cc := defaultCacheConfig diff --git a/commands/config.go b/commands/config.go index a44378b75..ce00c2239 100644 --- a/commands/config.go +++ b/commands/config.go @@ -23,7 +23,7 @@ import ( "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" @@ -95,7 +95,7 @@ func (c *configCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, arg 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) diff --git a/commands/hugobuilder.go b/commands/hugobuilder.go index bc8eeb588..038b9ad94 100644 --- a/commands/hugobuilder.go +++ b/commands/hugobuilder.go @@ -31,11 +31,11 @@ import ( "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" @@ -1077,7 +1077,7 @@ func (c *hugoBuilder) loadConfig(cd *simplecobra.Commandeer, running bool) error } cfg.Set("environment", c.r.environment) - cfg.Set("internal", maps.Params{ + cfg.Set("internal", hmaps.Params{ "running": running, "watch": watch, "verbose": c.r.isVerbose(), diff --git a/commands/import.go b/commands/import.go index 9da2b39e5..163f3b4d4 100644 --- a/commands/import.go +++ b/commands/import.go @@ -29,9 +29,9 @@ import ( "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" @@ -190,7 +190,7 @@ func (c *importCommand) createSiteFromJekyll(jekyllRoot, targetDir string, jekyl } 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 @@ -246,7 +246,7 @@ func (c *importCommand) convertJekyllContent(m any, content string) (string, err } 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 } diff --git a/common/hexec/exec.go b/common/hexec/exec.go index c3a6ebf57..79d48a483 100644 --- a/common/hexec/exec.go +++ b/common/hexec/exec.go @@ -27,8 +27,8 @@ import ( "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" ) @@ -102,7 +102,7 @@ func New(cfg security.Config, workingDir string, log loggers.Logger) *Exec { 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)](), } } @@ -121,7 +121,7 @@ type Exec struct { // 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 } diff --git a/common/hmaps/cache.go b/common/hmaps/cache.go new file mode 100644 index 000000000..b67f1bcec --- /dev/null +++ b/common/hmaps/cache.go @@ -0,0 +1,218 @@ +// 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() +} diff --git a/common/hmaps/cache_test.go b/common/hmaps/cache_test.go new file mode 100644 index 000000000..c6555d259 --- /dev/null +++ b/common/hmaps/cache_test.go @@ -0,0 +1,57 @@ +// 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) +} diff --git a/common/hmaps/map.go b/common/hmaps/map.go new file mode 100644 index 000000000..8bb1cfa2b --- /dev/null +++ b/common/hmaps/map.go @@ -0,0 +1,129 @@ +// 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 + } + } + } +} diff --git a/common/hmaps/map_test.go b/common/hmaps/map_test.go new file mode 100644 index 000000000..7791bc6da --- /dev/null +++ b/common/hmaps/map_test.go @@ -0,0 +1,72 @@ +// 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) +} diff --git a/common/hmaps/maps.go b/common/hmaps/maps.go new file mode 100644 index 000000000..36d01035b --- /dev/null +++ b/common/hmaps/maps.go @@ -0,0 +1,238 @@ +// 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) + } + } + } + } +} diff --git a/common/hmaps/maps_test.go b/common/hmaps/maps_test.go new file mode 100644 index 000000000..a017e6f70 --- /dev/null +++ b/common/hmaps/maps_test.go @@ -0,0 +1,201 @@ +// 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") +} diff --git a/common/hmaps/ordered.go b/common/hmaps/ordered.go new file mode 100644 index 000000000..b1a567b8f --- /dev/null +++ b/common/hmaps/ordered.go @@ -0,0 +1,144 @@ +// 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) +} diff --git a/common/hmaps/ordered_test.go b/common/hmaps/ordered_test.go new file mode 100644 index 000000000..c04fe0aa4 --- /dev/null +++ b/common/hmaps/ordered_test.go @@ -0,0 +1,99 @@ +// 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)) +} diff --git a/common/hmaps/orderedintset.go b/common/hmaps/orderedintset.go new file mode 100644 index 000000000..659d30ff8 --- /dev/null +++ b/common/hmaps/orderedintset.go @@ -0,0 +1,154 @@ +// 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() +} diff --git a/common/hmaps/orderedintset_test.go b/common/hmaps/orderedintset_test.go new file mode 100644 index 000000000..8bce69b15 --- /dev/null +++ b/common/hmaps/orderedintset_test.go @@ -0,0 +1,117 @@ +// 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 + }) + } + }) +} diff --git a/common/hmaps/params.go b/common/hmaps/params.go new file mode 100644 index 000000000..a67a45ca8 --- /dev/null +++ b/common/hmaps/params.go @@ -0,0 +1,473 @@ +// 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 +} diff --git a/common/hmaps/params_test.go b/common/hmaps/params_test.go new file mode 100644 index 000000000..9202a8420 --- /dev/null +++ b/common/hmaps/params_test.go @@ -0,0 +1,201 @@ +// 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", + }, + }, + }) +} diff --git a/common/hreflect/helpers.go b/common/hreflect/helpers.go index 4b70167d5..0a9200007 100644 --- a/common/hreflect/helpers.go +++ b/common/hreflect/helpers.go @@ -20,8 +20,8 @@ import ( "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" ) @@ -355,7 +355,7 @@ func IsNil(v reflect.Value) bool { 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 diff --git a/common/maps/cache.go b/common/maps/cache.go deleted file mode 100644 index cf1307f3c..000000000 --- a/common/maps/cache.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2024 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package 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() -} diff --git a/common/maps/cache_test.go b/common/maps/cache_test.go deleted file mode 100644 index 239c56fec..000000000 --- a/common/maps/cache_test.go +++ /dev/null @@ -1,57 +0,0 @@ -// 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) -} diff --git a/common/maps/map.go b/common/maps/map.go deleted file mode 100644 index 2050dac09..000000000 --- a/common/maps/map.go +++ /dev/null @@ -1,129 +0,0 @@ -// 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 - } - } - } -} diff --git a/common/maps/map_test.go b/common/maps/map_test.go deleted file mode 100644 index de5e980d7..000000000 --- a/common/maps/map_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// 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) -} diff --git a/common/maps/maps.go b/common/maps/maps.go deleted file mode 100644 index 5b3a08b9c..000000000 --- a/common/maps/maps.go +++ /dev/null @@ -1,238 +0,0 @@ -// 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) - } - } - } - } -} diff --git a/common/maps/maps_test.go b/common/maps/maps_test.go deleted file mode 100644 index 40c8ac824..000000000 --- a/common/maps/maps_test.go +++ /dev/null @@ -1,201 +0,0 @@ -// 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") -} diff --git a/common/maps/ordered.go b/common/maps/ordered.go deleted file mode 100644 index 0da9d239d..000000000 --- a/common/maps/ordered.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2024 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package 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) -} diff --git a/common/maps/ordered_test.go b/common/maps/ordered_test.go deleted file mode 100644 index 65a827810..000000000 --- a/common/maps/ordered_test.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2024 The Hugo Authors. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package 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)) -} diff --git a/common/maps/orderedintset.go b/common/maps/orderedintset.go deleted file mode 100644 index ed9babb2d..000000000 --- a/common/maps/orderedintset.go +++ /dev/null @@ -1,154 +0,0 @@ -// 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() -} diff --git a/common/maps/orderedintset_test.go b/common/maps/orderedintset_test.go deleted file mode 100644 index 2aab63028..000000000 --- a/common/maps/orderedintset_test.go +++ /dev/null @@ -1,117 +0,0 @@ -// 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 - }) - } - }) -} diff --git a/common/maps/params.go b/common/maps/params.go deleted file mode 100644 index e69d13a5b..000000000 --- a/common/maps/params.go +++ /dev/null @@ -1,473 +0,0 @@ -// 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 -} diff --git a/common/maps/params_test.go b/common/maps/params_test.go deleted file mode 100644 index 4e27eae39..000000000 --- a/common/maps/params_test.go +++ /dev/null @@ -1,201 +0,0 @@ -// 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", - }, - }, - }) -} diff --git a/common/paths/pathparser.go b/common/paths/pathparser.go index 8029b6cda..fbba141b8 100644 --- a/common/paths/pathparser.go +++ b/common/paths/pathparser.go @@ -21,7 +21,7 @@ import ( "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" @@ -54,12 +54,12 @@ type PathParser struct { // 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]() }) } diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go index 118f57d69..ddfa70572 100644 --- a/config/allconfig/allconfig.go +++ b/config/allconfig/allconfig.go @@ -29,10 +29,10 @@ import ( "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" @@ -212,7 +212,7 @@ type Config struct { // User provided parameters. // {"refs": ["config:languages:params"] } - Params maps.Params `mapstructure:"-"` + Params hmaps.Params `mapstructure:"-"` // UglyURLs configuration. Either a boolean or a sections map. UglyURLs any `mapstructure:"-"` @@ -1023,7 +1023,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon 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 @@ -1050,11 +1050,11 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon 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 { @@ -1066,16 +1066,16 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon 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) } @@ -1086,14 +1086,14 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon // 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: @@ -1103,7 +1103,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon } } else { switch vv.(type) { - case maps.Params: + case hmaps.Params: differentRootKeys = append(differentRootKeys, kk) default: // Apply new values to the root. @@ -1139,7 +1139,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon } langConfigMap[k] = clone - case maps.ParamsMergeStrategy: + case hmaps.ParamsMergeStrategy: default: panic(fmt.Sprintf("unknown type in languages config: %T", v)) diff --git a/config/allconfig/alldecoders.go b/config/allconfig/alldecoders.go index 81e31aa6e..b7c55258d 100644 --- a/config/allconfig/alldecoders.go +++ b/config/allconfig/alldecoders.go @@ -22,9 +22,9 @@ import ( "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" @@ -196,7 +196,7 @@ var allDecoderSetups = map[string]decodeWeight{ 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) @@ -225,13 +225,13 @@ var allDecoderSetups = map[string]decodeWeight{ "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 } @@ -266,7 +266,7 @@ var allDecoderSetups = map[string]decodeWeight{ 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 }, @@ -278,7 +278,7 @@ var allDecoderSetups = map[string]decodeWeight{ 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 @@ -289,7 +289,7 @@ var allDecoderSetups = map[string]decodeWeight{ "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) } @@ -335,7 +335,7 @@ var allDecoderSetups = map[string]decodeWeight{ 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 }, @@ -447,7 +447,7 @@ var allDecoderSetups = map[string]decodeWeight{ "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, @@ -455,7 +455,7 @@ var allDecoderSetups = map[string]decodeWeight{ "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, @@ -469,8 +469,8 @@ var allDecoderSetups = map[string]decodeWeight{ 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) } diff --git a/config/allconfig/docshelper.go b/config/allconfig/docshelper.go index 1a5fb6153..deaf4b9e0 100644 --- a/config/allconfig/docshelper.go +++ b/config/allconfig/docshelper.go @@ -14,7 +14,7 @@ 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" ) @@ -27,12 +27,12 @@ func init() { 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) diff --git a/config/allconfig/load.go b/config/allconfig/load.go index ec6b71e05..1aa6a1281 100644 --- a/config/allconfig/load.go +++ b/config/allconfig/load.go @@ -25,9 +25,9 @@ import ( "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" @@ -166,7 +166,7 @@ func (l configLoader) applyConfigAliases() error { } 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", @@ -186,7 +186,7 @@ func (l configLoader) normalizeCfg(cfg config.Provider) error { } 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}) } } @@ -240,7 +240,7 @@ func (l configLoader) applyOsEnvOverrides(environ []string) error { } 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 } @@ -547,7 +547,7 @@ func (l configLoader) loadConfig(configName string) (string, error) { } 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 }) diff --git a/config/configLoader.go b/config/configLoader.go index 7b8633492..49fc27fc6 100644 --- a/config/configLoader.go +++ b/config/configLoader.go @@ -20,11 +20,11 @@ import ( "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" ) @@ -222,11 +222,11 @@ func LoadConfigFromDir(sourceFs afero.Fs, configDir, environment string) (Provid 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", ) diff --git a/config/configProvider.go b/config/configProvider.go index 4e6878c1f..69efc595c 100644 --- a/config/configProvider.go +++ b/config/configProvider.go @@ -16,7 +16,7 @@ package config 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" @@ -96,7 +96,7 @@ type Provider interface { 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 @@ -104,9 +104,9 @@ type Provider interface { 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 } diff --git a/config/defaultConfigProvider.go b/config/defaultConfigProvider.go index 8e0a294eb..7e1e0adb0 100644 --- a/config/defaultConfigProvider.go +++ b/config/defaultConfigProvider.go @@ -20,21 +20,20 @@ import ( "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, } @@ -44,7 +43,7 @@ func NewFrom(params maps.Params) Provider { // All methods are thread safe. type defaultConfigProvider struct { mu sync.RWMutex - root maps.Params + root hmaps.Params keyCache sync.Map } @@ -90,22 +89,22 @@ func (c *defaultConfigProvider) GetString(k string) string { 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 { @@ -120,9 +119,9 @@ func (c *defaultConfigProvider) Set(k string, v any) { 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 } @@ -132,7 +131,7 @@ func (c *defaultConfigProvider) Set(k string, v any) { switch vv := v.(type) { case map[string]any, map[any]any, map[string]string: - p := maps.MustToParamsAndPrepare(vv) + p := hmaps.MustToParamsAndPrepare(vv) v = p } @@ -142,9 +141,9 @@ func (c *defaultConfigProvider) Set(k string, v any) { } 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 } } @@ -154,8 +153,8 @@ func (c *defaultConfigProvider) Set(k string, v any) { } // 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 @@ -170,28 +169,28 @@ func (c *defaultConfigProvider) Merge(k string, v any) { 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. @@ -201,7 +200,7 @@ func (c *defaultConfigProvider) Merge(k string, v any) { } } // Merge the rest. - maps.MergeParams(c.root, p) + hmaps.MergeParams(c.root, p) for _, k := range keysToDelete { delete(c.root, k) } @@ -214,7 +213,7 @@ func (c *defaultConfigProvider) Merge(k string, v any) { switch vv := v.(type) { case map[string]any, map[any]any, map[string]string: - p := maps.MustToParamsAndPrepare(vv) + p := hmaps.MustToParamsAndPrepare(vv) v = p } @@ -224,9 +223,9 @@ func (c *defaultConfigProvider) Merge(k string, v any) { } 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 { @@ -245,10 +244,10 @@ func (c *defaultConfigProvider) Keys() []string { 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")) } @@ -258,24 +257,24 @@ func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams) 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] ) @@ -299,10 +298,10 @@ func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams) // 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 @@ -314,11 +313,11 @@ func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams) } } if isMenuKey { - strategy = maps.ParamsMergeStrategyShallow + strategy = hmaps.ParamsMergeStrategyShallow } default: if strategy == "" { - strategy = maps.ParamsMergeStrategyNone + strategy = hmaps.ParamsMergeStrategyNone } } @@ -326,7 +325,7 @@ func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams) } func (c *defaultConfigProvider) SetDefaultMergeStrategy() { - c.WalkParams(func(params ...maps.KeyParams) bool { + c.WalkParams(func(params ...hmaps.KeyParams) bool { if len(params) == 0 { return false } @@ -344,7 +343,7 @@ func (c *defaultConfigProvider) SetDefaultMergeStrategy() { }) } -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 { @@ -358,14 +357,14 @@ func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (str 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 diff --git a/config/defaultConfigProvider_test.go b/config/defaultConfigProvider_test.go index a21d573ed..7ab4185f8 100644 --- a/config/defaultConfigProvider_test.go +++ b/config/defaultConfigProvider_test.go @@ -22,10 +22,9 @@ import ( "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" ) @@ -48,7 +47,7 @@ func TestDefaultConfigProvider(t *testing.T) { 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, }) }) @@ -60,7 +59,7 @@ func TestDefaultConfigProvider(t *testing.T) { "bar": "baz", }) - c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{ + c.Assert(cfg.Get("foo"), qt.DeepEquals, hmaps.Params{ "bar": "baz", }) @@ -76,14 +75,14 @@ func TestDefaultConfigProvider(t *testing.T) { }) 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", }) @@ -91,7 +90,7 @@ func TestDefaultConfigProvider(t *testing.T) { "b": "bv", }) - c.Assert(cfg.Get("b"), qt.DeepEquals, maps.Params{ + c.Assert(cfg.Get("b"), qt.DeepEquals, hmaps.Params{ "a": "av", "b": "bv", }) @@ -105,7 +104,7 @@ func TestDefaultConfigProvider(t *testing.T) { "b": "bv2", }) - c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{ + c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{ "a": "av2", "b": "bv2", }) @@ -118,7 +117,7 @@ func TestDefaultConfigProvider(t *testing.T) { "b": "bv2", }) - c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{ + c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{ "a": "av", "b": "bv2", }) @@ -137,7 +136,7 @@ func TestDefaultConfigProvider(t *testing.T) { }, }) - c.Assert(cfg.Get("foo"), qt.DeepEquals, maps.Params{ + c.Assert(cfg.Get("foo"), qt.DeepEquals, hmaps.Params{ "a": "av", "b": "bv2", }) @@ -155,7 +154,7 @@ func TestDefaultConfigProvider(t *testing.T) { "c": "cv2", }) - c.Assert(cfg.Get("a"), qt.DeepEquals, maps.Params{ + c.Assert(cfg.Get("a"), qt.DeepEquals, hmaps.Params{ "b": "bv", "c": "cv2", }) @@ -169,7 +168,7 @@ func TestDefaultConfigProvider(t *testing.T) { "b": "bv2", }) - c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{ + c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{ "a": "av", }) }) @@ -192,11 +191,11 @@ func TestDefaultConfigProvider(t *testing.T) { "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", }, }) @@ -221,15 +220,15 @@ func TestDefaultConfigProvider(t *testing.T) { "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", }, @@ -267,8 +266,8 @@ func TestDefaultConfigProvider(t *testing.T) { 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", }, @@ -292,7 +291,7 @@ func TestDefaultConfigProvider(t *testing.T) { }, }) - c.Assert(cfg.Get(""), qt.DeepEquals, maps.Params{ + c.Assert(cfg.Get(""), qt.DeepEquals, hmaps.Params{ "b": "bv", }) }) @@ -341,7 +340,7 @@ func TestDefaultConfigProvider(t *testing.T) { return err } - m := maps.Params{ + m := hmaps.Params{ "new": 42, } @@ -371,8 +370,8 @@ func TestDefaultConfigProvider(t *testing.T) { 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, }) @@ -384,8 +383,8 @@ func TestDefaultConfigProvider(t *testing.T) { 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) @@ -401,11 +400,11 @@ func TestDefaultConfigProvider(t *testing.T) { 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 }) @@ -417,7 +416,7 @@ func TestDefaultConfigProvider(t *testing.T) { c.Assert(got, qt.DeepEquals, want) cfg = New() - cfg.WalkParams(func(params ...maps.KeyParams) bool { + cfg.WalkParams(func(params ...hmaps.KeyParams) bool { return true }) @@ -429,7 +428,7 @@ func TestDefaultConfigProvider(t *testing.T) { c.Run("SetDefaults", func(c *qt.C) { cfg := New() - cfg.SetDefaults(maps.Params{ + cfg.SetDefaults(hmaps.Params{ "foo": "bar", "bar": "baz", }) diff --git a/config/namespace_test.go b/config/namespace_test.go index f443523a4..481302d26 100644 --- a/config/namespace_test.go +++ b/config/namespace_test.go @@ -18,7 +18,7 @@ import ( "testing" qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/common/hmaps" "github.com/mitchellh/mapstructure" ) @@ -32,7 +32,7 @@ func TestNamespace(t *testing.T) { 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 } diff --git a/config/testconfig/testconfig.go b/config/testconfig/testconfig.go index 596e0515d..20497b2b5 100644 --- a/config/testconfig/testconfig.go +++ b/config/testconfig/testconfig.go @@ -17,7 +17,7 @@ package testconfig 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" @@ -84,7 +84,7 @@ func GetTestConfigSectionFromStruct(section string, v any) config.AllProvider { if err != nil { panic(err) } - p := maps.Params{ + p := hmaps.Params{ section: config.FromTOMLConfigString(string(data)).Get(""), } cfg := config.NewFrom(p) diff --git a/deps/deps.go b/deps/deps.go index 024b22ec8..33b224013 100644 --- a/deps/deps.go +++ b/deps/deps.go @@ -28,8 +28,8 @@ import ( "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" @@ -173,8 +173,8 @@ func (d *Deps) Init() error { 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](), } } @@ -464,10 +464,10 @@ type Counters struct { 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) @@ -480,8 +480,8 @@ func (b *BuildState) StartStageRender(stage tpl.RenderingContext) { 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](), } } diff --git a/hugofs/hglob/glob.go b/hugofs/hglob/glob.go index 15d3ba2cb..7a3597f17 100644 --- a/hugofs/hglob/glob.go +++ b/hugofs/hglob/glob.go @@ -22,7 +22,7 @@ import ( "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" ) @@ -32,9 +32,9 @@ var ( 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 { @@ -47,7 +47,7 @@ type pathGlobCache 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. diff --git a/hugofs/rootmapping_fs_test.go b/hugofs/rootmapping_fs_test.go index 59470d6d0..77b89b54c 100644 --- a/hugofs/rootmapping_fs_test.go +++ b/hugofs/rootmapping_fs_test.go @@ -21,7 +21,7 @@ import ( 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" @@ -556,5 +556,5 @@ func TestRootMappingFileFilter(t *testing.T) { 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() } diff --git a/hugolib/config_test.go b/hugolib/config_test.go index 91b3cd0a6..18d92fa69 100644 --- a/hugolib/config_test.go +++ b/hugolib/config_test.go @@ -20,11 +20,11 @@ import ( "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" ) @@ -53,8 +53,8 @@ title = "English Comments Title" 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", }, @@ -424,13 +424,13 @@ name = "menu-theme" 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", }, @@ -447,11 +447,11 @@ name = "menu-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", }, }, @@ -468,7 +468,7 @@ name = "menu-theme" got := b.H.Configs.Base.Params - b.Assert(got, qt.DeepEquals, maps.Params{ + b.Assert(got, qt.DeepEquals, hmaps.Params{ "p1": "p1 theme", }) }) @@ -534,7 +534,7 @@ t3: {{ .Site.Params.t3 }} 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", diff --git a/hugolib/content_map_page.go b/hugolib/content_map_page.go index ea75bbd5c..15b2c58a9 100644 --- a/hugolib/content_map_page.go +++ b/hugolib/content_map_page.go @@ -26,6 +26,7 @@ import ( "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" @@ -39,8 +40,6 @@ import ( "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" @@ -100,7 +99,7 @@ type pageMap struct { 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 } @@ -710,7 +709,7 @@ func newPageMap(s *Site, mcache *dynacache.Cache, pageTrees *pageTrees) *pageMap 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(), @@ -756,13 +755,13 @@ func newPageMap(s *Site, mcache *dynacache.Cache, pageTrees *pageTrees) *pageMap 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() { diff --git a/hugolib/content_map_page_assembler.go b/hugolib/content_map_page_assembler.go index 7ba9ef5ab..e8c37084c 100644 --- a/hugolib/content_map_page_assembler.go +++ b/hugolib/content_map_page_assembler.go @@ -22,7 +22,7 @@ import ( "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" @@ -54,9 +54,9 @@ type allPagesAssembler struct { 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. } @@ -75,7 +75,7 @@ func newAllPagesAssembler( pw := rw.Extend() pw.Tree = m.treePages - seenRootSections := maps.NewMap[string, bool]() + seenRootSections := hmaps.NewMap[string, bool]() seenRootSections.Set("", true) // home. return &allPagesAssembler{ @@ -83,8 +83,8 @@ func newAllPagesAssembler( 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, diff --git a/hugolib/doctree/support.go b/hugolib/doctree/support.go index edd907ec2..b64221e08 100644 --- a/hugolib/doctree/support.go +++ b/hugolib/doctree/support.go @@ -21,7 +21,7 @@ import ( 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" ) @@ -70,7 +70,7 @@ func (ctx *WalkContext[T]) Data() *SimpleThreadSafeTree[any] { 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]]() }) } @@ -215,7 +215,7 @@ type WalkContext[T any] struct { 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 diff --git a/hugolib/hugo_sites.go b/hugolib/hugo_sites.go index 32207325c..82601a867 100644 --- a/hugolib/hugo_sites.go +++ b/hugolib/hugo_sites.go @@ -38,10 +38,10 @@ import ( "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" @@ -95,11 +95,11 @@ type HugoSites struct { // 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 diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go index 4f5b299dd..ebc458e07 100644 --- a/hugolib/integrationtest_builder.go +++ b/hugolib/integrationtest_builder.go @@ -26,8 +26,8 @@ import ( "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" @@ -858,12 +858,12 @@ func (s *IntegrationTestBuilder) initBuilder() error { } 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, }) } diff --git a/hugolib/page__content.go b/hugolib/page__content.go index f1ed435e9..0cca67bdb 100644 --- a/hugolib/page__content.go +++ b/hugolib/page__content.go @@ -29,9 +29,9 @@ import ( "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" @@ -114,7 +114,7 @@ func (m *pageMeta) newCachedContent(s *Site) (*cachedContent, error) { 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) @@ -136,7 +136,7 @@ type cachedContent struct { // 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 { diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go index 4da455de1..3776cb3ab 100644 --- a/hugolib/page__meta.go +++ b/hugolib/page__meta.go @@ -15,13 +15,12 @@ package hugolib import ( "fmt" + "maps" "path/filepath" "regexp" "strings" "time" - xmaps "maps" - "github.com/bep/logg" "github.com/gobuffalo/flect" "github.com/gohugoio/hugo/hugofs" @@ -36,10 +35,10 @@ import ( "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" @@ -373,7 +372,7 @@ func (s *Site) newPageMetaFromPageMetasource(ms *pageMetaSource) (*pageMeta, err m := &pageMeta{ pageMetaSource: ms, pageConfig: &pagemeta.PageConfigLate{ - Params: make(maps.Params), + Params: make(hmaps.Params), }, } return m, nil @@ -470,7 +469,7 @@ func (m *pageMeta) IsPage() bool { return m.Kind() == kinds.KindPage } -func (m *pageMeta) Params() maps.Params { +func (m *pageMeta) Params() hmaps.Params { return m.pageConfig.Params } @@ -524,17 +523,17 @@ func (m *pageMeta) Weight() int { 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. @@ -660,7 +659,7 @@ params: loki := strings.ToLower(k) if loki == "params" { - vv, err := maps.ToStringMapE(v) + vv, err := hmaps.ToStringMapE(v) if err != nil { return err } @@ -753,7 +752,7 @@ params: } 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) } @@ -771,7 +770,7 @@ params: 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...) @@ -779,7 +778,7 @@ params: 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) } diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go index b6e9f6602..3d0cb5ff7 100644 --- a/hugolib/page__per_output.go +++ b/hugolib/page__per_output.go @@ -22,7 +22,7 @@ import ( "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" @@ -67,7 +67,7 @@ func newPageContentOutput(po *pageOutput) (*pageContentOutput, error) { cp := &pageContentOutput{ po: po, renderHooks: &renderHooks{}, - otherOutputs: maps.NewCache[uint64, *pageContentOutput](), + otherOutputs: hmaps.NewCache[uint64, *pageContentOutput](), } return cp, nil } @@ -83,7 +83,7 @@ type pageContentOutput struct { // 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. diff --git a/hugolib/pagesfromdata/pagesfromgotmpl.go b/hugolib/pagesfromdata/pagesfromgotmpl.go index db2931503..c2214cdca 100644 --- a/hugolib/pagesfromdata/pagesfromgotmpl.go +++ b/hugolib/pagesfromdata/pagesfromgotmpl.go @@ -20,8 +20,8 @@ import ( "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" @@ -65,7 +65,7 @@ type pagesFromDataTemplateContext struct { } 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 } @@ -75,7 +75,7 @@ func (p *pagesFromDataTemplateContext) toPathSitesMap(v any) (string, map[string return "", nil, nil, fmt.Errorf("invalid path %q", path) } - sites := maps.ToStringMap(m["sites"]) + sites := hmaps.ToStringMap(m["sites"]) return path, sites, m, nil } @@ -161,7 +161,7 @@ func NewPagesFromTemplate(opts PagesFromTemplateOptions) *PagesFromTemplate { PagesFromTemplateOptions: opts, PagesFromTemplateDeps: opts.DepsFromSite(opts.Site), buildState: &BuildState{ - sourceInfosCurrent: maps.NewCache[string, *sourceInfo](), + sourceInfosCurrent: hmaps.NewCache[string, *sourceInfo](), }, store: hstore.NewScratch(), } @@ -231,8 +231,8 @@ type BuildState struct { 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 { @@ -304,7 +304,7 @@ func (b *BuildState) resolveDeletedPaths() { 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 @@ -317,7 +317,7 @@ func (p PagesFromTemplate) CloneForSite(s page.Site) *PagesFromTemplate { 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 } diff --git a/hugolib/segments/segments.go b/hugolib/segments/segments.go index 273811a53..382b8032b 100644 --- a/hugolib/segments/segments.go +++ b/hugolib/segments/segments.go @@ -18,9 +18,9 @@ import ( "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" @@ -276,14 +276,14 @@ func getGlob(s string) (glob.Glob, error) { 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 { diff --git a/hugolib/site.go b/hugolib/site.go index 849d48dcc..2f04bd4a8 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "io" + "maps" "mime" "net/url" "os" @@ -32,13 +33,13 @@ import ( "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" @@ -55,8 +56,6 @@ import ( "github.com/gohugoio/hugo/modules" "github.com/gohugoio/hugo/resources" - xmaps "maps" - "github.com/gohugoio/hugo/tpl/tplimpl" "github.com/gohugoio/hugo/tpl/tplimplinit" @@ -478,7 +477,7 @@ func newHugoSites( 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{}, @@ -669,7 +668,7 @@ func (s *Site) Lastmod() time.Time { } // Returns the Params configured for this site. -func (s *Site) Params() maps.Params { +func (s *Site) Params() hmaps.Params { return s.conf.Params } @@ -1140,7 +1139,7 @@ func (w *WhatChanged) Changes() []identity.Identity { 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 { diff --git a/hugolib/sitesmatrix/vectorstores.go b/hugolib/sitesmatrix/vectorstores.go index 015efb641..53a6f2782 100644 --- a/hugolib/sitesmatrix/vectorstores.go +++ b/hugolib/sitesmatrix/vectorstores.go @@ -17,14 +17,14 @@ import ( "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" @@ -250,7 +250,7 @@ func (s *vectorStoreMap) HasRole(i int) bool { func (s *vectorStoreMap) clone() *vectorStoreMap { c := *s c.h = &hashOnce{} - c.sets = xmaps.Clone(s.sets) + c.sets = maps.Clone(s.sets) return &c } @@ -277,7 +277,7 @@ type ConfiguredDimensions struct { ConfiguredRoles ConfiguredDimension CommonSitesMatrix CommonSitestMatrix - singleVectorStoreCache *maps.Cache[Vector, *IntSets] + singleVectorStoreCache *hmaps.Cache[Vector, *IntSets] } func (c *ConfiguredDimensions) IsSingleVector() bool { @@ -295,7 +295,7 @@ func (c *ConfiguredDimensions) GetOrCreateSingleVectorStore(vec Vector) *IntSets } 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) @@ -339,9 +339,9 @@ func (c *ConfiguredDimensions) ResolveVector(names types.Strings3) Vector { // 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 } @@ -592,7 +592,7 @@ func (s IntSets) shallowClone() *IntSets { // 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() } @@ -615,22 +615,22 @@ func (s *IntSets) MustHash() uint64 { // 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) } @@ -640,19 +640,19 @@ func (s *IntSets) setDefaultsAndAllLAnguagesIfNotSet(cfg *ConfiguredDimensions) 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) } @@ -696,20 +696,20 @@ func (s *IntSets) setDimensionsFromOtherIfNotSet(other VectorIterator) { 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()) } @@ -737,12 +737,12 @@ func (b *IntSetsBuilder) Build() *IntSets { } 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: @@ -772,7 +772,7 @@ func (b *IntSetsBuilder) WithConfig(cfg IntSetsConfig) *IntSetsBuilder { } for i := range iter { if result == nil { - result = maps.NewOrderedIntSet() + result = hmaps.NewOrderedIntSet() } result.Set(i) } @@ -807,7 +807,7 @@ func (s *IntSetsBuilder) WithLanguageIndices(idxs ...int) *IntSetsBuilder { 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) @@ -835,7 +835,7 @@ func (s *IntSetsBuilder) WithDimensionsFromOtherIfNotSet(other VectorIterator) * 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 @@ -867,17 +867,17 @@ func (s *Sites) Equal(other Sites) bool { 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)) } } @@ -899,7 +899,7 @@ func (d StringSlices) Equal(other StringSlices) bool { 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" diff --git a/hugolib/sitesmatrix/vectorstores_test.go b/hugolib/sitesmatrix/vectorstores_test.go index 3f5bd5905..c6919a7b3 100644 --- a/hugolib/sitesmatrix/vectorstores_test.go +++ b/hugolib/sitesmatrix/vectorstores_test.go @@ -18,7 +18,7 @@ import ( 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" ) @@ -31,27 +31,27 @@ func TestIntSets(t *testing.T) { 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") @@ -68,24 +68,24 @@ func TestIntSets(t *testing.T) { 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) @@ -126,17 +126,17 @@ func TestIntSetsComplement(t *testing.T) { 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()) } @@ -262,15 +262,15 @@ func TestIntSetsComplementOfComplement(t *testing.T) { 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) @@ -296,27 +296,27 @@ func BenchmarkIntSetsComplement(b *testing.B) { 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() @@ -361,21 +361,21 @@ func BenchmarkSets(b *testing.B) { 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} @@ -385,9 +385,9 @@ func BenchmarkSets(b *testing.B) { 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() } }) @@ -484,17 +484,17 @@ func TestVectorStoreMap(t *testing.T) { 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) @@ -509,9 +509,9 @@ func TestVectorStoreMap(t *testing.T) { 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} diff --git a/internal/js/api.go b/internal/js/api.go index 30180dece..fb6029334 100644 --- a/internal/js/api.go +++ b/internal/js/api.go @@ -16,14 +16,14 @@ package js 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. diff --git a/internal/js/esbuild/batch.go b/internal/js/esbuild/batch.go index 7f5d39855..dd5eb194e 100644 --- a/internal/js/esbuild/batch.go +++ b/internal/js/esbuild/batch.go @@ -31,9 +31,9 @@ import ( "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" @@ -74,8 +74,8 @@ func NewBatcherClient(deps *deps.Deps) (js.BatcherClient, error) { 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 { @@ -196,8 +196,8 @@ type BatcherClient struct { 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. @@ -279,7 +279,7 @@ func (c *BatcherClient) New(id string) (js.Batcher, error) { return b, nil } -func (c *BatcherClient) Store() *maps.Cache[string, js.Batcher] { +func (c *BatcherClient) Store() *hmaps.Cache[string, js.Batcher] { return c.batcherStore } @@ -435,15 +435,15 @@ func (b *batcher) doBuild(ctx context.Context) (*Package, error) { } 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 diff --git a/internal/js/esbuild/options.go b/internal/js/esbuild/options.go index 21f9e31cd..310fa305d 100644 --- a/internal/js/esbuild/options.go +++ b/internal/js/esbuild/options.go @@ -19,8 +19,8 @@ import ( "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" @@ -299,7 +299,7 @@ func (opts *Options) compile() (err error) { var defines map[string]string if opts.Defines != nil { - defines = maps.ToStringMapString(opts.Defines) + defines = hmaps.ToStringMapString(opts.Defines) } var drop api.Drop diff --git a/internal/js/esbuild/resolve.go b/internal/js/esbuild/resolve.go index 18ad8a40a..e66c72c7b 100644 --- a/internal/js/esbuild/resolve.go +++ b/internal/js/esbuild/resolve.go @@ -18,16 +18,16 @@ import ( "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 ( @@ -132,12 +132,12 @@ func ResolveResource(impPath string, resourceGetter resource.ResourceGetter) (r } 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 { diff --git a/internal/warpc/warpc.go b/internal/warpc/warpc.go index 5f9797de1..03f4a6ed8 100644 --- a/internal/warpc/warpc.go +++ b/internal/warpc/warpc.go @@ -30,9 +30,9 @@ import ( "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" @@ -253,7 +253,7 @@ func (d *dispatcher[Q, R]) newCall(q Message[Q]) (*call[Q, R], error) { 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) } @@ -424,7 +424,7 @@ func (d *dispatcher[Q, R]) pendingCall(id uint32) *call[Q, R] { 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] } diff --git a/langs/language.go b/langs/language.go index 41f1898b2..24616e79e 100644 --- a/langs/language.go +++ b/langs/language.go @@ -22,8 +22,8 @@ import ( "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" @@ -54,7 +54,7 @@ type Language struct { isDefault bool // This is just an alias of Site.Params. - params maps.Params + params hmaps.Params } // Name is an alias for Lang. @@ -114,7 +114,7 @@ var DeprecationFunc = func(item, alternative string, err bool) {} // 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) @@ -167,7 +167,7 @@ func (l Languages) AsIndexSet() map[string]int { // 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 } diff --git a/markup/goldmark/convert_test.go b/markup/goldmark/convert_test.go index a35441aff..2336b52ae 100644 --- a/markup/goldmark/convert_test.go +++ b/markup/goldmark/convert_test.go @@ -31,9 +31,9 @@ import ( "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" @@ -398,7 +398,7 @@ func TestConvertAttributes(t *testing.T) { } 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)) diff --git a/markup/markup_config/config.go b/markup/markup_config/config.go index dfa66a52f..d79879015 100644 --- a/markup/markup_config/config.go +++ b/markup/markup_config/config.go @@ -14,7 +14,7 @@ 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" @@ -52,7 +52,7 @@ func Decode(cfg config.Provider) (conf Config, err error) { if m == nil { return } - m = maps.CleanConfigStringMap(m) + m = hmaps.CleanConfigStringMap(m) normalizeConfig(m) @@ -73,9 +73,9 @@ func Decode(cfg config.Provider) (conf Config, err error) { } 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 { @@ -87,9 +87,9 @@ func normalizeConfig(m map[string]any) { } // 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}) diff --git a/media/config.go b/media/config.go index 6d3687a4f..40ed8621e 100644 --- a/media/config.go +++ b/media/config.go @@ -21,7 +21,7 @@ import ( "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" @@ -180,7 +180,7 @@ func DecodeContentTypes(in map[string]any, types Types) (*config.ConfigNamespace 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 } @@ -188,7 +188,7 @@ func DecodeContentTypes(in map[string]any, types Types) (*config.ConfigNamespace 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 { @@ -221,16 +221,16 @@ func DecodeContentTypes(in map[string]any, types Types) (*config.ConfigNamespace // 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 @@ -242,8 +242,8 @@ func DecodeTypes(in map[string]any) (*config.ConfigNamespace[map[string]MediaTyp 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), ","))) } diff --git a/minifiers/config.go b/minifiers/config.go index 7a28d707d..8b68a0bf0 100644 --- a/minifiers/config.go +++ b/minifiers/config.go @@ -16,8 +16,8 @@ package minifiers 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" @@ -88,17 +88,17 @@ func DecodeConfig(v any) (conf MinifyConfig, err error) { 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 { @@ -121,7 +121,7 @@ func DecodeConfig(v any) (conf MinifyConfig, err error) { // 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 { @@ -140,7 +140,7 @@ func DecodeConfig(v any) (conf MinifyConfig, err error) { // 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 { diff --git a/modules/collect.go b/modules/collect.go index 9a86c8140..de803dbd4 100644 --- a/modules/collect.go +++ b/modules/collect.go @@ -28,6 +28,7 @@ import ( "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" @@ -35,8 +36,6 @@ import ( "github.com/spf13/cast" - "github.com/gohugoio/hugo/common/maps" - "github.com/gohugoio/hugo/parser/metadecoders" "github.com/gohugoio/hugo/hugofs/files" @@ -494,7 +493,7 @@ LOOP: 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) } } diff --git a/modules/npm/package_builder.go b/modules/npm/package_builder.go index 0deed2f42..2c1b6f2f9 100644 --- a/modules/npm/package_builder.go +++ b/modules/npm/package_builder.go @@ -21,6 +21,7 @@ import ( "io/fs" "strings" + "github.com/gohugoio/hugo/common/hmaps" "github.com/gohugoio/hugo/common/hugio" "github.com/gohugoio/hugo/hugofs/files" @@ -28,8 +29,6 @@ import ( "github.com/gohugoio/hugo/hugofs" "github.com/spf13/afero" - "github.com/gohugoio/hugo/common/maps" - "github.com/gohugoio/hugo/helpers" ) @@ -130,7 +129,7 @@ func Pack(sourceFs, assetsWithDuplicatesPreservedFs afero.Fs) error { 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) } @@ -213,7 +212,7 @@ func (b *packageBuilder) addm(source string, m 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 @@ -223,7 +222,7 @@ func (b *packageBuilder) addm(source string, m map[string]any) { } 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 diff --git a/navigation/menu.go b/navigation/menu.go index 5d4c5b93e..c020475bf 100644 --- a/navigation/menu.go +++ b/navigation/menu.go @@ -19,7 +19,7 @@ import ( "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" @@ -89,7 +89,7 @@ type Page interface { IsPage() bool IsSection() bool IsAncestor(other any) bool - Params() maps.Params + Params() hmaps.Params } // Menu is a collection of menu entries. @@ -158,7 +158,7 @@ type MenuConfig struct { Weight int Title string // User defined params. - Params maps.Params + Params hmaps.Params } // For internal use. @@ -279,11 +279,11 @@ func DecodeConfig(in any) (*config.ConfigNamespace[map[string]MenuConfig, Menus] 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) @@ -295,7 +295,7 @@ func DecodeConfig(in any) (*config.ConfigNamespace[map[string]MenuConfig, Menus] 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, diff --git a/navigation/pagemenus.go b/navigation/pagemenus.go index 0919d93bb..689032421 100644 --- a/navigation/pagemenus.go +++ b/navigation/pagemenus.go @@ -16,7 +16,7 @@ package navigation import ( "fmt" - "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/common/hmaps" "github.com/gohugoio/hugo/common/types" "github.com/mitchellh/mapstructure" @@ -74,7 +74,7 @@ func PageMenusFromPage(ms any, p Page) (PageMenus, error) { } // Could be a structured menu entry - menus, err := maps.ToStringMapE(ms) + menus, err := hmaps.ToStringMapE(ms) if err != nil { return pm, wrapErr(err) } @@ -82,7 +82,7 @@ func PageMenusFromPage(ms any, p Page) (PageMenus, error) { 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) } diff --git a/output/config.go b/output/config.go index 09d29f184..3f6f4e248 100644 --- a/output/config.go +++ b/output/config.go @@ -19,7 +19,7 @@ import ( "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" @@ -42,11 +42,11 @@ func DecodeConfig(mediaTypes media.Types, in any) (*config.ConfigNamespace[map[s 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 diff --git a/parser/metadecoders/decoder.go b/parser/metadecoders/decoder.go index 6443bfdf8..02e7dbdb7 100644 --- a/parser/metadecoders/decoder.go +++ b/parser/metadecoders/decoder.go @@ -25,7 +25,7 @@ import ( "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" @@ -120,7 +120,7 @@ func (d Decoder) UnmarshalStringTo(data string, typ any) (any, error) { 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: diff --git a/related/inverted_index.go b/related/inverted_index.go index ebed77baa..9541ecd8a 100644 --- a/related/inverted_index.go +++ b/related/inverted_index.go @@ -18,16 +18,15 @@ import ( "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" @@ -557,7 +556,7 @@ func norm(num, min, max int) int { } // 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") } @@ -590,7 +589,7 @@ func DecodeConfig(m maps.Params) (Config, error) { 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") diff --git a/resources/images/config.go b/resources/images/config.go index 08c0a5007..f72f0af23 100644 --- a/resources/images/config.go +++ b/resources/images/config.go @@ -21,7 +21,7 @@ import ( "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" @@ -162,12 +162,12 @@ func DecodeConfig(in map[string]any) (*config.ConfigNamespace[ImagingConfig, Ima } 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 { diff --git a/resources/images/filters.go b/resources/images/filters.go index 1e44f1184..5fb5dae1f 100644 --- a/resources/images/filters.go +++ b/resources/images/filters.go @@ -19,8 +19,8 @@ import ( "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" @@ -83,9 +83,9 @@ func (*Filters) Text(text string, options ...any) gift.Filter { 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": diff --git a/resources/page/page_matcher.go b/resources/page/page_matcher.go index 163a91e90..cb7922e7e 100644 --- a/resources/page/page_matcher.go +++ b/resources/page/page_matcher.go @@ -21,10 +21,10 @@ import ( "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" @@ -121,8 +121,8 @@ func checkCascadePattern(logger loggers.Logger, m PageMatcher) { } } -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) { @@ -133,7 +133,7 @@ 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 } @@ -141,7 +141,7 @@ func DecodeCascadeConfig(in any) (*PageMatcherParamsConfigs, error) { var cfgs []PageMatcherParamsConfig for _, m := range ms { - m = maps.CleanConfigStringMap(m) + m = hmaps.CleanConfigStringMap(m) var ( c PageMatcherParamsConfig err error @@ -192,10 +192,10 @@ type cascadeConfigDecoder struct{} 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 { @@ -207,7 +207,7 @@ func (d cascadeConfigDecoder) mapToPageMatcherParamsConfig(m map[string]any) (Pa } 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 @@ -271,25 +271,25 @@ type CascadeConfig struct { 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 } } diff --git a/resources/page/page_matcher_test.go b/resources/page/page_matcher_test.go index 127af247d..3dafef7b9 100644 --- a/resources/page/page_matcher_test.go +++ b/resources/page/page_matcher_test.go @@ -17,9 +17,9 @@ import ( "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" @@ -87,18 +87,18 @@ func TestPageMatcher(t *testing.T) { 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: ""}, }) }) @@ -134,10 +134,10 @@ func TestDecodeCascadeConfig(t *testing.T) { 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{}, @@ -147,11 +147,11 @@ func TestDecodeCascadeConfig(t *testing.T) { 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) diff --git a/resources/page/page_nop.go b/resources/page/page_nop.go index 297ebda22..d4ca5bff6 100644 --- a/resources/page/page_nop.go +++ b/resources/page/page_nop.go @@ -29,9 +29,9 @@ import ( "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" @@ -320,7 +320,7 @@ func (p *nopPage) Param(key any) (any, error) { return nil, nil } -func (p *nopPage) Params() maps.Params { +func (p *nopPage) Params() hmaps.Params { return nil } diff --git a/resources/page/pagemeta/page_frontmatter.go b/resources/page/pagemeta/page_frontmatter.go index d90ae074c..b5f9149b7 100644 --- a/resources/page/pagemeta/page_frontmatter.go +++ b/resources/page/pagemeta/page_frontmatter.go @@ -20,12 +20,12 @@ import ( "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" @@ -104,7 +104,7 @@ func (pcfg *PageConfigEarly) SetMetaPreFromMap(ext string, frontmatter map[strin 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 { @@ -122,7 +122,7 @@ func (pcfg *PageConfigEarly) setFromFrontMatter(frontmatter map[string]any) erro 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() @@ -137,7 +137,7 @@ type PageConfigEarly struct { 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:"-"` @@ -151,7 +151,7 @@ type PageConfigEarly struct { 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. @@ -438,9 +438,9 @@ func (p *PageConfigLate) Compile(e *PageConfigEarly, logger loggers.Logger, outp } 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 { @@ -466,7 +466,7 @@ type ResourceConfig struct { Path string Name string Title string - Params maps.Params + Params hmaps.Params Content Source Sites sitesmatrix.Sites @@ -486,7 +486,7 @@ func (rc *ResourceConfig) Validate() error { 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. diff --git a/resources/page/permalinks.go b/resources/page/permalinks.go index 1d6078b4a..adaa9faee 100644 --- a/resources/page/permalinks.go +++ b/resources/page/permalinks.go @@ -24,8 +24,8 @@ import ( "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" ) @@ -41,7 +41,7 @@ type PermalinkExpander struct { 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 @@ -83,7 +83,7 @@ func (p PermalinkExpander) callback(attr string) (pageToPermaAttribute, bool) { 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{ @@ -494,7 +494,7 @@ func DecodePermalinksConfig(m map[string]any) (map[string]map[string]string, err 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: @@ -505,7 +505,7 @@ func DecodePermalinksConfig(m map[string]any) (map[string]map[string]string, err permalinksConfig[kinds.KindPage][k] = v permalinksConfig[kinds.KindTerm][k] = v - case maps.Params: + case hmaps.Params: // [permalinks.key] // xyz = ??? diff --git a/resources/page/site.go b/resources/page/site.go index 33dd94736..e6016c508 100644 --- a/resources/page/site.go +++ b/resources/page/site.go @@ -16,8 +16,8 @@ package page 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" @@ -105,7 +105,7 @@ type Site interface { 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) @@ -308,7 +308,7 @@ func (s *siteWrapper) MainSections() []string { return s.s.MainSections() } -func (s *siteWrapper) Params() maps.Params { +func (s *siteWrapper) Params() hmaps.Params { return s.s.Params() } @@ -472,7 +472,7 @@ func (t testSite) BaseURL() string { return "" } -func (t testSite) Params() maps.Params { +func (t testSite) Params() hmaps.Params { return nil } diff --git a/resources/page/testhelpers_test.go b/resources/page/testhelpers_test.go index 4c6cfbd30..1d6085e73 100644 --- a/resources/page/testhelpers_test.go +++ b/resources/page/testhelpers_test.go @@ -27,9 +27,9 @@ import ( "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" @@ -398,7 +398,7 @@ func (p *testPage) Param(key any) (any, error) { return resource.Param(p, nil, key) } -func (p *testPage) Params() maps.Params { +func (p *testPage) Params() hmaps.Params { return p.params } diff --git a/resources/postpub/postpub.go b/resources/postpub/postpub.go index 65e32145c..f1aab7001 100644 --- a/resources/postpub/postpub.go +++ b/resources/postpub/postpub.go @@ -22,8 +22,8 @@ import ( "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" ) @@ -169,7 +169,7 @@ func (r *PostPublishResource) Title() string { return r.field("Title") } -func (r *PostPublishResource) Params() maps.Params { +func (r *PostPublishResource) Params() hmaps.Params { panic(r.fieldNotSupported("Params")) } diff --git a/resources/resource.go b/resources/resource.go index 1666e7692..cf6e1e597 100644 --- a/resources/resource.go +++ b/resources/resource.go @@ -29,13 +29,13 @@ import ( "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" @@ -91,7 +91,7 @@ type ResourceSourceDescriptor struct { 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 @@ -122,7 +122,7 @@ func (fd *ResourceSourceDescriptor) init(r *Spec) error { } if fd.Params == nil { - fd.Params = make(maps.Params) + fd.Params = make(hmaps.Params) } if fd.Path == nil { @@ -498,7 +498,7 @@ func (l *genericResource) NameNormalized() string { return l.sd.NameNormalized } -func (l *genericResource) Params() maps.Params { +func (l *genericResource) Params() hmaps.Params { return l.params } diff --git a/resources/resource/params.go b/resources/resource/params.go index d88424e9d..cc85e3c92 100644 --- a/resources/resource/params.go +++ b/resources/resource/params.go @@ -14,20 +14,19 @@ 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) } diff --git a/resources/resource/resource_helpers.go b/resources/resource/resource_helpers.go index 8575ae79e..742379b18 100644 --- a/resources/resource/resource_helpers.go +++ b/resources/resource/resource_helpers.go @@ -17,8 +17,7 @@ import ( "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" @@ -38,7 +37,7 @@ func GetParamToLower(r Resource, key string) any { } 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 diff --git a/resources/resource/resources.go b/resources/resource/resources.go index 013d9b097..28fbab42f 100644 --- a/resources/resource/resources.go +++ b/resources/resource/resources.go @@ -20,8 +20,8 @@ import ( "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" @@ -330,7 +330,7 @@ func NewCachedResourceGetter(os ...any) *cachedResourceGetter { } return &cachedResourceGetter{ - cache: maps.NewCache[string, Resource](), + cache: hmaps.NewCache[string, Resource](), delegate: getters, } } @@ -352,7 +352,7 @@ var ( ) type cachedResourceGetter struct { - cache *maps.Cache[string, Resource] + cache *hmaps.Cache[string, Resource] delegate ResourceGetter } diff --git a/resources/resource/resourcetypes.go b/resources/resource/resourcetypes.go index e55e7169f..66b49f900 100644 --- a/resources/resource/resourcetypes.go +++ b/resources/resource/resourcetypes.go @@ -16,7 +16,7 @@ package resource 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" @@ -139,7 +139,7 @@ type NameNormalizedProvider interface { type ResourceParamsProvider interface { // Params set in front matter for this resource. - Params() maps.Params + Params() hmaps.Params } type ResourceDataProvider interface { diff --git a/resources/resource_factories/create/remote.go b/resources/resource_factories/create/remote.go index c4dffaf95..899ac8330 100644 --- a/resources/resource_factories/create/remote.go +++ b/resources/resource_factories/create/remote.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "io" + "maps" "math/rand" "mime" "net/http" @@ -26,14 +27,12 @@ import ( "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" @@ -172,12 +171,12 @@ func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resou } 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 @@ -323,7 +322,7 @@ func (c *Client) validateFromRemoteArgs(uri string, options fromRemoteOptions) e 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) } diff --git a/resources/resource_metadata.go b/resources/resource_metadata.go index 955809c93..c97ed260d 100644 --- a/resources/resource_metadata.go +++ b/resources/resource_metadata.go @@ -28,7 +28,7 @@ import ( maps0 "maps" - "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/common/hmaps" "github.com/gohugoio/hugo/common/paths" ) @@ -60,7 +60,7 @@ type metaResource struct { changed bool title string name string - params maps.Params + params hmaps.Params } func (r *metaResource) Name() string { @@ -71,7 +71,7 @@ func (r *metaResource) Title() string { return r.title } -func (r *metaResource) Params() maps.Params { +func (r *metaResource) Params() hmaps.Params { return r.params } @@ -209,9 +209,9 @@ func assignMetadata(metadata []map[string]any, ma *metaResource) error { 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) } } diff --git a/resources/transform.go b/resources/transform.go index bb1f57fd3..4d3d7c1b8 100644 --- a/resources/transform.go +++ b/resources/transform.go @@ -25,6 +25,7 @@ import ( "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" @@ -36,7 +37,6 @@ import ( "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" @@ -133,7 +133,7 @@ type ResourceTransformationCtx struct { // 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) } @@ -310,7 +310,7 @@ func (r *resourceAdapter) NameNormalized() string { 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() } diff --git a/tpl/collections/collections.go b/tpl/collections/collections.go index f10466fd9..749ed612f 100644 --- a/tpl/collections/collections.go +++ b/tpl/collections/collections.go @@ -25,9 +25,9 @@ import ( "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" @@ -43,7 +43,7 @@ func New(deps *deps.Deps) *Namespace { } 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, @@ -57,7 +57,7 @@ func New(deps *deps.Deps) *Namespace { type Namespace struct { loc *time.Location sortComp *compare.Namespace - dCache *maps.Cache[dKey, []int] + dCache *hmaps.Cache[dKey, []int] deps *deps.Deps } diff --git a/tpl/collections/collections_test.go b/tpl/collections/collections_test.go index 2d0a6c7df..5562a80d6 100644 --- a/tpl/collections/collections_test.go +++ b/tpl/collections/collections_test.go @@ -23,7 +23,7 @@ import ( "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" @@ -912,10 +912,10 @@ type TstX struct { } type TstParams struct { - params maps.Params + params hmaps.Params } -func (x TstParams) Params() maps.Params { +func (x TstParams) Params() hmaps.Params { return x.params } diff --git a/tpl/collections/index.go b/tpl/collections/index.go index 7de27931e..999abf829 100644 --- a/tpl/collections/index.go +++ b/tpl/collections/index.go @@ -20,8 +20,8 @@ import ( "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 @@ -63,7 +63,7 @@ func (ns *Namespace) doIndex(item any, args ...any) (any, error) { indices = args } - lowerm, ok := item.(maps.Params) + lowerm, ok := item.(hmaps.Params) if ok { return lowerm.GetNested(cast.ToStringSlice(indices)...), nil } diff --git a/tpl/collections/index_test.go b/tpl/collections/index_test.go index 0c5a58756..5b56ba341 100644 --- a/tpl/collections/index_test.go +++ b/tpl/collections/index_test.go @@ -17,9 +17,8 @@ import ( "fmt" "testing" - "github.com/gohugoio/hugo/common/maps" - qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/common/hmaps" ) func TestIndex(t *testing.T) { @@ -47,8 +46,8 @@ 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. diff --git a/tpl/collections/merge.go b/tpl/collections/merge.go index b9696b502..b50697931 100644 --- a/tpl/collections/merge.go +++ b/tpl/collections/merge.go @@ -19,8 +19,8 @@ import ( "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 @@ -92,7 +92,7 @@ func mergeMap(dst, src reflect.Value) reflect.Value { 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() diff --git a/tpl/collections/merge_test.go b/tpl/collections/merge_test.go index 3642e9060..978c2ae89 100644 --- a/tpl/collections/merge_test.go +++ b/tpl/collections/merge_test.go @@ -18,7 +18,7 @@ import ( "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" @@ -78,24 +78,24 @@ func TestMerge(t *testing.T) { "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)}, @@ -104,7 +104,7 @@ func TestMerge(t *testing.T) { { "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)}, @@ -114,9 +114,9 @@ func TestMerge(t *testing.T) { "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, }, { @@ -136,7 +136,6 @@ func TestMerge(t *testing.T) { {"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) diff --git a/tpl/collections/querify.go b/tpl/collections/querify.go index 19e6d8afe..164a6e43c 100644 --- a/tpl/collections/querify.go +++ b/tpl/collections/querify.go @@ -17,7 +17,7 @@ import ( "errors" "net/url" - "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/common/hmaps" "github.com/spf13/cast" ) @@ -37,7 +37,7 @@ func (ns *Namespace) Querify(params ...any) (string, error) { 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) @@ -66,7 +66,7 @@ func (ns *Namespace) Querify(params ...any) (string, error) { // 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 } diff --git a/tpl/collections/querify_test.go b/tpl/collections/querify_test.go index 9d4f72923..3c09fbab7 100644 --- a/tpl/collections/querify_test.go +++ b/tpl/collections/querify_test.go @@ -17,7 +17,7 @@ import ( "testing" qt "github.com/frankban/quicktest" - "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/common/hmaps" ) func TestQuerify(t *testing.T) { @@ -31,9 +31,9 @@ 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`}, @@ -53,7 +53,7 @@ func TestQuerify(t *testing.T) { // 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}, diff --git a/tpl/collections/sort.go b/tpl/collections/sort.go index 2a0a4f3bb..6d6a2ad56 100644 --- a/tpl/collections/sort.go +++ b/tpl/collections/sort.go @@ -20,8 +20,8 @@ import ( "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" @@ -89,8 +89,8 @@ func (ns *Namespace) Sort(ctx context.Context, l any, args ...any) (any, error) 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 } @@ -122,8 +122,8 @@ func (ns *Namespace) Sort(ctx context.Context, l any, args ...any) (any, error) 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 } diff --git a/tpl/collections/sort_test.go b/tpl/collections/sort_test.go index 4fc95ac8a..0a6f04373 100644 --- a/tpl/collections/sort_test.go +++ b/tpl/collections/sort_test.go @@ -19,7 +19,7 @@ import ( "reflect" "testing" - "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/common/hmaps" ) type stringsSlice []string @@ -103,17 +103,17 @@ func TestSort(t *testing.T) { }, // 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 { diff --git a/tpl/collections/where.go b/tpl/collections/where.go index 9a45ba12f..bc7dcd229 100644 --- a/tpl/collections/where.go +++ b/tpl/collections/where.go @@ -20,9 +20,9 @@ import ( "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. @@ -403,7 +403,7 @@ func (ns *Namespace) checkWhereArray(ctxv, seqv, kv, mv reflect.Value, path []st 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 @@ -415,7 +415,7 @@ func (ns *Namespace) checkWhereArray(ctxv, seqv, kv, mv reflect.Value, path []st } 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 diff --git a/tpl/collections/where_test.go b/tpl/collections/where_test.go index 3b55ffdbd..2caf1e830 100644 --- a/tpl/collections/where_test.go +++ b/tpl/collections/where_test.go @@ -23,7 +23,7 @@ import ( "testing" "time" - "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/common/hmaps" ) func TestWhere(t *testing.T) { @@ -176,35 +176,35 @@ 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", @@ -217,7 +217,7 @@ func TestWhere(t *testing.T) { }, }, key: "A.B", match: "b2", - expect: []maps.Params{ + expect: []hmaps.Params{ { "a": map[string]any{ "b": "b2", @@ -622,14 +622,14 @@ func TestWhere(t *testing.T) { }, { 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}}, }, }, } { diff --git a/tpl/css/css.go b/tpl/css/css.go index 6ac37d6d8..9a2761f51 100644 --- a/tpl/css/css.go +++ b/tpl/css/css.go @@ -6,8 +6,8 @@ import ( "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" @@ -105,7 +105,7 @@ func (ns *Namespace) Sass(args ...any) (resource.Resource, error) { } 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) diff --git a/tpl/data/data.go b/tpl/data/data.go index ca1796826..0de72ebbc 100644 --- a/tpl/data/data.go +++ b/tpl/data/data.go @@ -22,11 +22,12 @@ import ( "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" @@ -36,7 +37,6 @@ import ( "github.com/spf13/cast" "github.com/gohugoio/hugo/deps" - "slices" ) // New returns a new instance of the data-namespaced template functions. @@ -185,7 +185,7 @@ func toURLAndHeaders(urlParts []any) (string, map[string]any) { } // 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 { diff --git a/tpl/data/data_test.go b/tpl/data/data_test.go index 7c49d8076..d50c99384 100644 --- a/tpl/data/data_test.go +++ b/tpl/data/data_test.go @@ -23,7 +23,7 @@ import ( "testing" "github.com/bep/logg" - "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/common/hmaps" qt "github.com/frankban/quicktest" ) @@ -241,7 +241,7 @@ func TestHeaders(t *testing.T) { }, { `Params`, - maps.Params{ + hmaps.Params{ "Accept-Charset": "utf-8", }, func(c *qt.C, headers string) { diff --git a/tpl/encoding/encoding.go b/tpl/encoding/encoding.go index f9102967a..6ac0e4f1e 100644 --- a/tpl/encoding/encoding.go +++ b/tpl/encoding/encoding.go @@ -21,8 +21,8 @@ import ( "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" ) @@ -76,7 +76,7 @@ func (ns *Namespace) Jsonify(args ...any) (template.HTML, error) { 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 } diff --git a/tpl/internal/resourcehelpers/helpers.go b/tpl/internal/resourcehelpers/helpers.go index a777c8c3b..88635c675 100644 --- a/tpl/internal/resourcehelpers/helpers.go +++ b/tpl/internal/resourcehelpers/helpers.go @@ -19,7 +19,7 @@ import ( "errors" "fmt" - "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/common/hmaps" "github.com/gohugoio/hugo/resources" ) @@ -60,7 +60,7 @@ func ResolveArgs(args []any) (resources.ResourceTransformer, map[string]any, err 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) } diff --git a/tpl/openapi/openapi3/openapi3.go b/tpl/openapi/openapi3/openapi3.go index 4cadae523..7fef75463 100644 --- a/tpl/openapi/openapi3/openapi3.go +++ b/tpl/openapi/openapi3/openapi3.go @@ -27,7 +27,7 @@ import ( 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" @@ -81,7 +81,7 @@ func (ns *Namespace) Unmarshal(ctx context.Context, args ...any) (*OpenAPIDocume 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 } diff --git a/tpl/resources/resources.go b/tpl/resources/resources.go index f28cc36fe..da537ab99 100644 --- a/tpl/resources/resources.go +++ b/tpl/resources/resources.go @@ -19,8 +19,8 @@ import ( "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" @@ -129,7 +129,7 @@ func (ns *Namespace) GetRemote(args ...any) (resource.Resource, error) { 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 } diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go index 18d4d4a27..43a8bdfbe 100644 --- a/tpl/tplimpl/template_funcs.go +++ b/tpl/tplimpl/template_funcs.go @@ -20,8 +20,8 @@ import ( "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" @@ -68,7 +68,7 @@ func (t *templateExecHelper) Init(ctx context.Context, tmpl texttemplate.Prepare } 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] @@ -83,7 +83,7 @@ func (t *templateExecHelper) GetMapValue(ctx context.Context, tmpl texttemplate. 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() { diff --git a/tpl/tplimpl/templatestore.go b/tpl/tplimpl/templatestore.go index 48f3139b9..944a0953f 100644 --- a/tpl/tplimpl/templatestore.go +++ b/tpl/tplimpl/templatestore.go @@ -37,9 +37,9 @@ import ( "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" @@ -126,10 +126,10 @@ func NewStore(opts StoreOptions, siteOpts SiteOptions) (*TemplateStore, error) { 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), @@ -437,9 +437,9 @@ type TemplateStore struct { 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 @@ -455,7 +455,7 @@ type TemplateStore struct { 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. diff --git a/tpl/tplimpl/templatetransform.go b/tpl/tplimpl/templatetransform.go index 6ace0f109..ffb859b3e 100644 --- a/tpl/tplimpl/templatetransform.go +++ b/tpl/tplimpl/templatetransform.go @@ -13,7 +13,7 @@ import ( 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" ) @@ -478,7 +478,7 @@ func (c *templateTransformContext) collectConfig(n *parse.PipeNode) { 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