diff --git a/SCC-OUTPUT-REPORT.html b/SCC-OUTPUT-REPORT.html index 83ebcb674..20a63ee24 100644 --- a/SCC-OUTPUT-REPORT.html +++ b/SCC-OUTPUT-REPORT.html @@ -12,14 +12,14 @@ Go - 27 - 9589 - 1464 - 447 - 7678 - 1413 - 256080 - 4138 + 25 + 9335 + 1417 + 431 + 7487 + 1375 + 251113 + 4029 processor/workers_test.go @@ -93,13 +93,13 @@ cmd/badges/main.go - 375 - 60 + 384 + 62 17 - 298 - 59 - 9660 - 240 + 305 + 58 + 9762 + 246 processor/workers_tokei_test.go @@ -170,16 +170,6 @@ 50 3766 99 - - cmd/badges/simplecache.go - - 161 - 28 - 13 - 120 - 20 - 3070 - 94 processor/processor_test.go @@ -190,16 +180,6 @@ 21 2573 66 - - cmd/badges/simplecache_test.go - - 102 - 21 - 3 - 78 - 17 - 1999 - 53 processor/structs_test.go @@ -293,16 +273,16 @@ Total - 27 - 9589 - 1464 - 447 - 7678 - 1413 - 256080 - 4138 + 25 + 9335 + 1417 + 431 + 7487 + 1375 + 251113 + 4029 - Estimated Cost to Develop (organic) $229,670
Estimated Schedule Effort (organic) 7.86 months
Estimated People Required (organic) 2.59
+ Estimated Cost to Develop (organic) $223,675
Estimated Schedule Effort (organic) 7.79 months
Estimated People Required (organic) 2.55
diff --git a/cmd/badges/main.go b/cmd/badges/main.go index 31634921c..200f5dda4 100644 --- a/cmd/badges/main.go +++ b/cmd/badges/main.go @@ -16,18 +16,30 @@ import ( "time" "github.com/boyter/scc/v3/processor" + "github.com/boyter/simplecache" jsoniter "github.com/json-iterator/go" "github.com/rs/zerolog/log" ) var ( - uniqueCode = "unique_code" - cache = NewSimpleCache(1000, 86400) + uniqueCode = "unique_code" + cache = simplecache.New[[]processor.LanguageSummary](simplecache.Option{ + MaxItems: intPtr(1000), + MaxAge: timePtr(time.Hour * 72), + }) countingSemaphore = make(chan bool, 1) tmpDir = os.TempDir() json = jsoniter.ConfigCompatibleWithStandardLibrary ) +func intPtr(i int) *int { + return &i +} + +func timePtr(t time.Duration) *time.Duration { + return &t +} + func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { loc, err := processUrlPath(r.URL.Path) @@ -37,7 +49,7 @@ func main() { return } - data, err := process(1, loc) + res, err := process(1, loc) if err != nil { log.Error().Str(uniqueCode, "03ec75c3").Err(err).Str("loc", loc.String()).Send() w.WriteHeader(http.StatusBadRequest) @@ -45,15 +57,6 @@ func main() { return } - var res []processor.LanguageSummary - err = json.Unmarshal(data, &res) - if err != nil { - log.Error().Str(uniqueCode, "9192cad8").Err(err).Send() - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte("something bad happened sorry")) - return - } - category := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("category"))) wage := tryParseInt(strings.TrimSpace(strings.ToLower(r.URL.Query().Get("avg-wage"))), 56286) title, value := calculate(category, wage, res) @@ -250,7 +253,7 @@ func formatCount(count float64) string { return fmt.Sprintf("%v", math.Round(count)) } -func process(id int, s location) ([]byte, error) { +func process(id int, s location) ([]processor.LanguageSummary, error) { countingSemaphore <- true defer func() { <-countingSemaphore // remove one to free up concurrency @@ -277,7 +280,7 @@ func process(id int, s location) ([]byte, error) { cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0") _, err := cmd.Output() - if ctx.Err() == context.DeadlineExceeded { + if errors.Is(ctx.Err(), context.DeadlineExceeded) { return nil, err } @@ -307,11 +310,17 @@ func process(id int, s location) ([]byte, error) { return nil, err } - file, err := os.ReadFile(filePath) + data, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + + var res []processor.LanguageSummary + err = json.Unmarshal(data, &res) if err != nil { return nil, err } - cache.Add(s.String(), file) + _ = cache.Set(s.String(), res) // Cleanup if err := os.RemoveAll(filePath); err != nil { @@ -322,7 +331,7 @@ func process(id int, s location) ([]byte, error) { return nil, err } - return file, nil + return res, nil } func processPath(s string) string { diff --git a/cmd/badges/simplecache.go b/cmd/badges/simplecache.go deleted file mode 100644 index abb72600a..000000000 --- a/cmd/badges/simplecache.go +++ /dev/null @@ -1,161 +0,0 @@ -package main - -import ( - "math" - "sync" - "time" -) - -type cacheEntry struct { - entry []byte - hits int - age int64 -} - -type SimpleCache struct { - maxItems int - items map[string]cacheEntry - lock sync.Mutex - getUnix func() int64 - ageOutTimeSeconds int64 -} - -func NewSimpleCache(maxItems int, ageOutTimeSeconds int64) *SimpleCache { - simpleCache := SimpleCache{ - maxItems: maxItems, - items: map[string]cacheEntry{}, - lock: sync.Mutex{}, - getUnix: func() int64 { - return time.Now().Unix() - }, - ageOutTimeSeconds: ageOutTimeSeconds, - } - simpleCache.runAgeItems() - return &simpleCache -} - -func (c *SimpleCache) runAgeItems() { - go func() { - time.Sleep(10 * time.Second) - c.adjustLfu() - c.ageOut() - }() -} - -func (c *SimpleCache) adjustLfu() { - c.lock.Lock() - defer c.lock.Unlock() - - // maps are randomly ordered, so only decrementing 50 at a time should be acceptable - count := 50 - - for k, v := range c.items { - if v.hits > 0 { - v.hits-- - c.items[k] = v - } - count-- - if count <= 0 { - break - } - } -} - -func (c *SimpleCache) Add(cacheKey string, entry []byte) { - c.evictItems() - - c.lock.Lock() - defer c.lock.Unlock() - - c.items[cacheKey] = cacheEntry{ - entry: entry, - hits: 1, - age: c.getUnix(), - } -} - -func (c *SimpleCache) Get(cacheKey string) ([]byte, bool) { - c.lock.Lock() - defer c.lock.Unlock() - - item, ok := c.items[cacheKey] - - if ok { - if item.hits < 100 { - item.hits++ - } - c.items[cacheKey] = item - return item.entry, true - } - - return nil, false -} - -// evictItems is called before any insert operation because we need to ensure we have less than -// the total number of items -func (c *SimpleCache) evictItems() { - c.lock.Lock() - defer c.lock.Unlock() - - // insert process only needs to expire if we have too much - // as such if we haven't hit the limit return - if len(c.items) < c.maxItems { - return - } - - count := 10 - - lfuKey := "" - lfuLowestCount := math.MaxInt - - for k, v := range c.items { - v.hits-- - c.items[k] = v - if v.hits < lfuLowestCount { - lfuKey = k - lfuLowestCount = v.hits - } - - // we only want to process X random elements so we don't spin forever - // however we also exit if the count is <= 0 - count-- - if count <= 0 || lfuLowestCount <= 0 { - break - } - } - - delete(c.items, lfuKey) -} - -// ageOut is called on a schedule to evict the oldest entry so long as -// its older than the configured cache time -func (c *SimpleCache) ageOut() { - // we also want to age out things eventually to avoid https://github.com/boyter/scc/discussions/435 - // as such loop though and the first one that's older than a day with 0 hits is removed - - c.lock.Lock() - defer c.lock.Unlock() - - count := 10 - lfuKey := "" - lfuOldest := int64(math.MaxInt) - - // maps are un-ordered so this is acceptable - for k, v := range c.items { - c.items[k] = v - if v.age < lfuOldest { - lfuKey = k - lfuOldest = v.age - } - - count-- - if count <= 0 { - break - } - } - - // evict the oldest but only if its older than it should be - if lfuOldest <= c.getUnix()-c.ageOutTimeSeconds { - delete(c.items, lfuKey) - } -} diff --git a/cmd/badges/simplecache_test.go b/cmd/badges/simplecache_test.go deleted file mode 100644 index ad530ab99..000000000 --- a/cmd/badges/simplecache_test.go +++ /dev/null @@ -1,102 +0,0 @@ -package main - -import ( - "strconv" - "sync" - "testing" - "time" -) - -func TestSimpleCache_Add(t *testing.T) { - simpleCache := NewSimpleCache(5, 60) - - for i := 0; i < 5; i++ { - simpleCache.Add(strconv.Itoa(i), []byte{}) - } - - for i := 0; i < 4; i++ { - for j := 0; j < 5; j++ { - simpleCache.Get(strconv.Itoa(i)) - } - } - - simpleCache.Add("10", []byte{}) -} - -func TestSimpleCache_Multiple(t *testing.T) { - simpleCache := NewSimpleCache(10, 60) - - for i := 0; i < 500; i++ { - simpleCache.Add(strconv.Itoa(i), []byte{}) - } - - simpleCache.Add("test-key", []byte{}) - - if len(simpleCache.items) != 10 { - t.Errorf("expected 10 items got %v", len(simpleCache.items)) - } -} - -func TestSimpleCache_MultipleLarge(t *testing.T) { - simpleCache := NewSimpleCache(1000, 60) - - for i := 0; i < 500000; i++ { - simpleCache.Add(strconv.Itoa(i), []byte{}) - simpleCache.Add("10", []byte{}) - simpleCache.Get(strconv.Itoa(i)) - simpleCache.Get("10") - simpleCache.Get("10") - } - - if len(simpleCache.items) != 999 { - t.Errorf("expected 999 items got %v", len(simpleCache.items)) - } -} - -func TestSimpleCache_AgeOut(t *testing.T) { - simpleCache := &SimpleCache{ - maxItems: 100, - items: map[string]cacheEntry{}, - lock: sync.Mutex{}, - getUnix: func() int64 { - return 0 - }, - ageOutTimeSeconds: 10, - } - - for i := 0; i < 10; i++ { - simpleCache.Add(strconv.Itoa(i), []byte{}) - } - - // advance time - simpleCache.getUnix = func() int64 { - return 10000 - } - // simulate eviction over time - for i := 0; i < 10; i++ { - simpleCache.ageOut() - } - - if len(simpleCache.items) != 0 { - t.Errorf("expected 0 items got %v", len(simpleCache.items)) - } -} - -func TestSimpleCache_AgeOutTime(t *testing.T) { - simpleCache := NewSimpleCache(100, 1) - - for i := 0; i < 10; i++ { - simpleCache.Add(strconv.Itoa(i), []byte{}) - } - - time.Sleep(1 * time.Second) - - // simulate eviction over time - for i := 0; i < 10; i++ { - simpleCache.ageOut() - } - - if len(simpleCache.items) != 0 { - t.Errorf("expected 0 items got %v", len(simpleCache.items)) - } -} diff --git a/go.mod b/go.mod index a89870a7e..7a55637dd 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ module github.com/boyter/scc/v3 -go 1.22 +go 1.23.3 require ( github.com/boyter/gocodewalker v1.4.0 + github.com/boyter/simplecache v0.0.0-20250113230110-8a4c9201822a github.com/json-iterator/go v1.1.12 github.com/mattn/go-runewidth v0.0.16 github.com/rs/zerolog v1.30.0 diff --git a/go.sum b/go.sum index c087ae46f..cd26007e8 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/boyter/gocodewalker v1.4.0 h1:fVmFeQxKpj5tlpjPcyTtJ96btgaHYd9yn6m+T/66et4= github.com/boyter/gocodewalker v1.4.0/go.mod h1:hXG8xzR1uURS+99P5/3xh3uWHjaV2XfoMMmvPyhrCDg= +github.com/boyter/simplecache v0.0.0-20250113230110-8a4c9201822a h1:eL28tNqB4nBuMVA+WijpviMStOY7NAFWDowPB6I6Ruo= +github.com/boyter/simplecache v0.0.0-20250113230110-8a4c9201822a/go.mod h1:8yw7v2b4T5LJbZEBhPOqUsqe8h04anlyPhmWnoUtRIs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= @@ -42,8 +44,9 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= @@ -60,4 +63,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/boyter/simplecache/.gitignore b/vendor/github.com/boyter/simplecache/.gitignore new file mode 100644 index 000000000..723ef36f4 --- /dev/null +++ b/vendor/github.com/boyter/simplecache/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/vendor/github.com/boyter/simplecache/LICENSE b/vendor/github.com/boyter/simplecache/LICENSE new file mode 100644 index 000000000..c645e9214 --- /dev/null +++ b/vendor/github.com/boyter/simplecache/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2021 Ben Boyter + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/boyter/simplecache/README.md b/vendor/github.com/boyter/simplecache/README.md new file mode 100644 index 000000000..d01924ee0 --- /dev/null +++ b/vendor/github.com/boyter/simplecache/README.md @@ -0,0 +1,63 @@ +# SimpleCache + +[![Scc Count Badge](https://sloc.xyz/github/boyter/simplecache/)](https://github.com/boyter/simplecache/) + +A simple thread safe cache implementation using Go generics. + +## Why? + +While many excellent cache solutions exist, what I often want for smaller projects is a map, with some expiration +abilities over it. That is intended to fill that role. This is because different types can have +different caching needs, such as a small group of items that should never expire, items that should exist in cache +forever only being removed when the cache is full. Or some combination. + +Most operations should be `o(1)` as well as all being thread safe. + +### What isn't it + +1. A generic cache for anything E.G. redis/memcached +2. Aiming for extreme performance under load +3. Implementing any sort of persistence + +# Usage + +Import `github.com/boyter/simplecache` + +```go +sc := simplecache.New[string]() + +_ = sc.Set("key-1", "some value") + +v, ok := sc.Get("key-1") +if ok { + fmt.Println(v) // prints "some value" +} +v, ok = sc.Get("key-99") +if ok { + fmt.Println(v) // not run "key-99" was never added +} +``` + +Note that a default cache has an limit of 100,000 items, once the next item is added beyond this limit 5 random +entries will be checked, and one of them removed based on the default LFU algorithm. + +You can configure this through the use of options, as indicated below + +```go +oMi := 1000 +oEp := simplecache.LRU +oEs := 5 +oMA := time.Second * 60 + +sc := simplecache.New[string](simplecache.Option{ + MaxItems: &oMi, // max number of items the cache will hold, evicting on Set, nil for no limit + EvictionPolicy: &oEp, // Which eviction policy should be applied LRU or LFU + EvictionSamples: &oEs, // How many random samples to take from the items to find the best to expire + MaxAge: &oMA, // Max age an item can live on Get when past this will be deleted, nil for no expiry +}) +``` + +# Benchmarks? + +I don't have any. It's a Go map with some locking. It should be fine. Being 5% faster or slower than any other +cache isn't the point here. diff --git a/vendor/github.com/boyter/simplecache/simplecache.go b/vendor/github.com/boyter/simplecache/simplecache.go new file mode 100644 index 000000000..6880d5dd3 --- /dev/null +++ b/vendor/github.com/boyter/simplecache/simplecache.go @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: MIT + +package simplecache + +import ( + "errors" + "math" + "sync" + "time" +) + +type CacheEvictionPolicy int + +const ( + LRU CacheEvictionPolicy = iota + LFU +) + +var ErrInvalidEvictionPolicy = errors.New("invalid eviction policy") + +// cacheEntry is a generic structure to hold cached items +type cacheEntry[T any] struct { + entry T + hits int64 + age time.Time + lastUse time.Time +} + +// Cache is a generic cache implementation using a map +// not designed for raw performance but to be simple to configure +type Cache[T any] struct { + items map[string]cacheEntry[T] + mutex sync.Mutex + maxItems int // 0 indicates no limits IE never expires unless age limits kick in + evictionPolicy CacheEvictionPolicy + evictionSamples int // how many random samples do we look for when expiring + maxAge *time.Duration // at what point should it be evicted no matter what +} + +// CacheInterface is an interface for the Cache type mostly for mocking purposes +type CacheInterface[T any] interface { + // Set adds or updates an item in the cache + Set(key string, value T) error + + // Get retrieves an item from the cache by key, returning the value and a boolean indicating if the value was found + Get(key string) (T, bool) + + // Delete removes an item from the cache by key + Delete(key string) + + // Clear removes all items from the cache + Clear() + + // Sum returns the count of items in the cache + Sum() int +} + +type Option struct { + MaxItems *int + EvictionPolicy *CacheEvictionPolicy + EvictionSamples *int + MaxAge *time.Duration +} + +// New creates and returns a new Cache +func New[T any](opts ...Option) *Cache[T] { + sc := &Cache[T]{ + items: make(map[string]cacheEntry[T]), + mutex: sync.Mutex{}, + maxItems: 100_000, // 0 indicates no limits + evictionPolicy: LFU, + evictionSamples: 5, + maxAge: nil, // by default no expiration + } + + for _, opt := range opts { + if opt.MaxItems != nil { + sc.maxItems = *opt.MaxItems + } + if opt.EvictionPolicy != nil { + sc.evictionPolicy = *opt.EvictionPolicy + } + if opt.EvictionSamples != nil { + sc.evictionSamples = *opt.EvictionSamples + } + if opt.MaxAge != nil { + sc.maxAge = opt.MaxAge + } + } + + return sc +} + +// Set adds or updates an item in the cache with a given key +func (c *Cache[T]) Set(key string, value T) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + if c.maxItems > 0 && len(c.items) >= c.maxItems { + // now we need to expire things based on our settings + switch c.evictionPolicy { + case LRU: + c.evictLRU() + case LFU: + c.evictLFU() + default: + return ErrInvalidEvictionPolicy + } + } + + c.items[key] = cacheEntry[T]{ + entry: value, + hits: 0, + age: time.Now(), + lastUse: time.Now(), + } + + return nil +} + +// evictLFU is a simple LRU eviction where we check which item was last used +// from the random sample we iterate over and then remove it +func (c *Cache[T]) evictLRU() { + count := 0 + pKey := "" + oldest := time.Now() + + for k, v := range c.items { // iterating over a map is random in Go + count++ + if v.age.Before(oldest) { + oldest = v.age + pKey = k + } + + if count >= c.evictionSamples { + break + } + } + + delete(c.items, pKey) +} + +// evictLFU is a simple LFU eviction where we check which item has the least number of cache hits +// from the random sample we iterate over and remove it +func (c *Cache[T]) evictLFU() { + count := 0 + pKey := "" + pHit := int64(math.MaxInt64) + + for k, v := range c.items { // iterating over a map is random in Go + count++ + if v.hits < pHit { + pKey = k + } + + if count > c.evictionSamples { + break + } + } + delete(c.items, pKey) +} + +// Get retrieves an item from the cache by key, also incrementing the hit count +func (c *Cache[T]) Get(key string) (T, bool) { + c.mutex.Lock() + defer c.mutex.Unlock() + + entry, found := c.items[key] + if found { + if c.maxAge != nil { // should it be expired? + if entry.lastUse.Before(time.Now().Add(-*c.maxAge)) { + delete(c.items, key) + var zero T + return zero, false + } + } + + entry.hits++ + entry.lastUse = time.Now() + c.items[key] = entry // Update the hit count in the cache + return entry.entry, true + } + + var zero T + return zero, false +} + +// Delete removes an item from the cache by key +func (c *Cache[T]) Delete(key string) { + c.mutex.Lock() + defer c.mutex.Unlock() + + delete(c.items, key) +} + +// Clear removes all entries from the cache +func (c *Cache[T]) Clear() { + c.mutex.Lock() + defer c.mutex.Unlock() + + c.items = make(map[string]cacheEntry[T]) +} + +// Sum returns the count of items in the cache +func (c *Cache[T]) Sum() int { + c.mutex.Lock() + defer c.mutex.Unlock() + + return len(c.items) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3ca25977f..5aca947dd 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2,6 +2,9 @@ ## explicit; go 1.20 github.com/boyter/gocodewalker github.com/boyter/gocodewalker/go-gitignore +# github.com/boyter/simplecache v0.0.0-20250113230110-8a4c9201822a +## explicit; go 1.23.3 +github.com/boyter/simplecache # github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 ## explicit github.com/danwakefield/fnmatch