diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/hugo/version_current.go | 4 | ||||
-rw-r--r-- | common/maps/cache.go | 14 | ||||
-rw-r--r-- | common/maps/maps.go | 8 | ||||
-rw-r--r-- | common/maps/maps_test.go | 6 | ||||
-rw-r--r-- | common/paths/pathparser.go | 51 | ||||
-rw-r--r-- | common/paths/pathparser_test.go | 19 | ||||
-rw-r--r-- | common/predicate/predicate.go | 6 | ||||
-rw-r--r-- | common/tasks/tasks.go | 153 | ||||
-rw-r--r-- | common/types/closer.go | 47 |
9 files changed, 275 insertions, 33 deletions
diff --git a/common/hugo/version_current.go b/common/hugo/version_current.go index 5f4e104ff..439f9fe10 100644 --- a/common/hugo/version_current.go +++ b/common/hugo/version_current.go @@ -17,7 +17,7 @@ package hugo // This should be the only one. var CurrentVersion = Version{ Major: 0, - Minor: 126, + Minor: 127, PatchLevel: 0, - Suffix: "-DEV", + Suffix: "", } diff --git a/common/maps/cache.go b/common/maps/cache.go index 7e23a2662..3723d318e 100644 --- a/common/maps/cache.go +++ b/common/maps/cache.go @@ -27,7 +27,12 @@ func NewCache[K comparable, T any]() *Cache[K, T] { } // 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.m[key] c.RUnlock() @@ -60,6 +65,15 @@ func (c *Cache[K, T]) Set(key K, value T) { c.Unlock() } +// ForEeach calls the given function for each key/value pair in the cache. +func (c *Cache[K, T]) ForEeach(f func(K, T)) { + c.RLock() + defer c.RUnlock() + for k, v := range c.m { + f(k, v) + } +} + // SliceCache is a simple thread safe cache backed by a map. type SliceCache[T any] struct { m map[string][]T diff --git a/common/maps/maps.go b/common/maps/maps.go index 2686baad6..f9171ebf2 100644 --- a/common/maps/maps.go +++ b/common/maps/maps.go @@ -112,17 +112,17 @@ func ToSliceStringMap(in any) ([]map[string]any, error) { } // LookupEqualFold finds key in m with case insensitive equality checks. -func LookupEqualFold[T any | string](m map[string]T, key string) (T, bool) { +func LookupEqualFold[T any | string](m map[string]T, key string) (T, string, bool) { if v, found := m[key]; found { - return v, true + return v, key, true } for k, v := range m { if strings.EqualFold(k, key) { - return v, true + return v, k, true } } var s T - return s, false + return s, "", false } // MergeShallow merges src into dst, but only if the key does not already exist in dst. diff --git a/common/maps/maps_test.go b/common/maps/maps_test.go index 098098388..b4f9c5a3d 100644 --- a/common/maps/maps_test.go +++ b/common/maps/maps_test.go @@ -180,16 +180,18 @@ func TestLookupEqualFold(t *testing.T) { "B": "bv", } - v, found := LookupEqualFold(m1, "b") + 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, found = LookupEqualFold(m2, "b") + 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/paths/pathparser.go b/common/paths/pathparser.go index 951501406..5fa798fb0 100644 --- a/common/paths/pathparser.go +++ b/common/paths/pathparser.go @@ -25,8 +25,6 @@ import ( "github.com/gohugoio/hugo/identity" ) -var defaultPathParser PathParser - // PathParser parses a path into a Path. type PathParser struct { // Maps the language code to its index in the languages/sites slice. @@ -34,11 +32,9 @@ type PathParser struct { // Reports whether the given language is disabled. IsLangDisabled func(string) bool -} -// Parse parses component c with path s into Path using the default path parser. -func Parse(c, s string) *Path { - return defaultPathParser.Parse(c, s) + // Reports whether the given ext is a content file. + IsContentExt func(string) bool } // NormalizePathString returns a normalized path string using the very basic Hugo rules. @@ -108,7 +104,6 @@ func (pp *PathParser) parse(component, s string) (*Path, error) { var err error // Preserve the original case for titles etc. p.unnormalized, err = pp.doParse(component, s, pp.newPath(component)) - if err != nil { return nil, err } @@ -195,23 +190,26 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) { } } - isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes - isContent := isContentComponent && files.IsContentExt(p.Ext()) - - if isContent { + if len(p.identifiers) > 0 { + isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes + isContent := isContentComponent && pp.IsContentExt(p.Ext()) id := p.identifiers[len(p.identifiers)-1] b := p.s[p.posContainerHigh : id.Low-1] - switch b { - case "index": - p.bundleType = PathTypeLeaf - case "_index": - p.bundleType = PathTypeBranch - default: - p.bundleType = PathTypeContentSingle - } + if isContent { + switch b { + case "index": + p.bundleType = PathTypeLeaf + case "_index": + p.bundleType = PathTypeBranch + default: + p.bundleType = PathTypeContentSingle + } - if slashCount == 2 && p.IsLeafBundle() { - p.posSectionHigh = 0 + if slashCount == 2 && p.IsLeafBundle() { + p.posSectionHigh = 0 + } + } else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) { + p.bundleType = PathTypeContentData } } @@ -246,6 +244,9 @@ const ( // Branch bundles, e.g. /blog/_index.md PathTypeBranch + + // Content data file, _content.gotmpl. + PathTypeContentData ) type Path struct { @@ -521,10 +522,6 @@ func (p *Path) Identifiers() []string { return ids } -func (p *Path) IsHTML() bool { - return files.IsHTML(p.Ext()) -} - func (p *Path) BundleType() PathType { return p.bundleType } @@ -541,6 +538,10 @@ func (p *Path) IsLeafBundle() bool { return p.bundleType == PathTypeLeaf } +func (p *Path) IsContentData() bool { + return p.bundleType == PathTypeContentData +} + func (p Path) ForBundleType(t PathType) *Path { p.bundleType = t return &p diff --git a/common/paths/pathparser_test.go b/common/paths/pathparser_test.go index 8c89ddd41..e8fee96e1 100644 --- a/common/paths/pathparser_test.go +++ b/common/paths/pathparser_test.go @@ -27,6 +27,9 @@ var testParser = &PathParser{ "no": 0, "en": 1, }, + IsContentExt: func(ext string) bool { + return ext == "md" + }, } func TestParse(t *testing.T) { @@ -333,6 +336,22 @@ func TestParse(t *testing.T) { c.Assert(p.Path(), qt.Equals, "/a/b/c.txt") }, }, + { + "Content data file gotmpl", + "/a/b/_content.gotmpl", + func(c *qt.C, p *Path) { + c.Assert(p.Path(), qt.Equals, "/a/b/_content.gotmpl") + c.Assert(p.Ext(), qt.Equals, "gotmpl") + c.Assert(p.IsContentData(), qt.IsTrue) + }, + }, + { + "Content data file yaml", + "/a/b/_content.yaml", + func(c *qt.C, p *Path) { + c.Assert(p.IsContentData(), qt.IsFalse) + }, + }, } for _, test := range tests { c.Run(test.name, func(c *qt.C) { diff --git a/common/predicate/predicate.go b/common/predicate/predicate.go index f9cb1bb2b..f71536474 100644 --- a/common/predicate/predicate.go +++ b/common/predicate/predicate.go @@ -24,6 +24,9 @@ func (p P[T]) And(ps ...P[T]) P[T] { return false } } + if p == nil { + return true + } return p(v) } } @@ -36,6 +39,9 @@ func (p P[T]) Or(ps ...P[T]) P[T] { return true } } + if p == nil { + return false + } return p(v) } } diff --git a/common/tasks/tasks.go b/common/tasks/tasks.go new file mode 100644 index 000000000..1f7e061f9 --- /dev/null +++ b/common/tasks/tasks.go @@ -0,0 +1,153 @@ +// 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 tasks + +import ( + "sync" + "time" +) + +// RunEvery runs a function at intervals defined by the function itself. +// Functions can be added and removed while running. +type RunEvery struct { + // Any error returned from the function will be passed to this function. + HandleError func(string, error) + + // If set, the function will be run immediately. + RunImmediately bool + + // The named functions to run. + funcs map[string]*Func + + mu sync.Mutex + started bool + closed bool + quit chan struct{} +} + +type Func struct { + // The shortest interval between each run. + IntervalLow time.Duration + + // The longest interval between each run. + IntervalHigh time.Duration + + // The function to run. + F func(interval time.Duration) (time.Duration, error) + + interval time.Duration + last time.Time +} + +func (r *RunEvery) Start() error { + if r.started { + return nil + } + + r.started = true + r.quit = make(chan struct{}) + + go func() { + if r.RunImmediately { + r.run() + } + ticker := time.NewTicker(500 * time.Millisecond) + defer ticker.Stop() + for { + select { + case <-r.quit: + return + case <-ticker.C: + r.run() + } + } + }() + + return nil +} + +// Close stops the RunEvery from running. +func (r *RunEvery) Close() error { + if r.closed { + return nil + } + r.closed = true + if r.quit != nil { + close(r.quit) + } + return nil +} + +// Add adds a function to the RunEvery. +func (r *RunEvery) Add(name string, f Func) { + r.mu.Lock() + defer r.mu.Unlock() + if r.funcs == nil { + r.funcs = make(map[string]*Func) + } + if f.IntervalLow == 0 { + f.IntervalLow = 500 * time.Millisecond + } + if f.IntervalHigh <= f.IntervalLow { + f.IntervalHigh = 20 * time.Second + } + + start := f.IntervalHigh / 3 + if start < f.IntervalLow { + start = f.IntervalLow + } + f.interval = start + f.last = time.Now() + + r.funcs[name] = &f +} + +// Remove removes a function from the RunEvery. +func (r *RunEvery) Remove(name string) { + r.mu.Lock() + defer r.mu.Unlock() + delete(r.funcs, name) +} + +// Has returns whether the RunEvery has a function with the given name. +func (r *RunEvery) Has(name string) bool { + r.mu.Lock() + defer r.mu.Unlock() + _, found := r.funcs[name] + return found +} + +func (r *RunEvery) run() { + r.mu.Lock() + defer r.mu.Unlock() + for name, f := range r.funcs { + if time.Now().Before(f.last.Add(f.interval)) { + continue + } + f.last = time.Now() + interval, err := f.F(f.interval) + if err != nil && r.HandleError != nil { + r.HandleError(name, err) + } + + if interval < f.IntervalLow { + interval = f.IntervalLow + } + + if interval > f.IntervalHigh { + interval = f.IntervalHigh + } + f.interval = interval + } +} diff --git a/common/types/closer.go b/common/types/closer.go new file mode 100644 index 000000000..2844b1986 --- /dev/null +++ b/common/types/closer.go @@ -0,0 +1,47 @@ +// 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 types + +import "sync" + +type Closer interface { + Close() error +} + +type CloseAdder interface { + Add(Closer) +} + +type Closers struct { + mu sync.Mutex + cs []Closer +} + +func (cs *Closers) Add(c Closer) { + cs.mu.Lock() + defer cs.mu.Unlock() + cs.cs = append(cs.cs, c) +} + +func (cs *Closers) Close() error { + cs.mu.Lock() + defer cs.mu.Unlock() + for _, c := range cs.cs { + c.Close() + } + + cs.cs = cs.cs[:0] + + return nil +} |