diff options
author | Bjørn Erik Pedersen <[email protected]> | 2024-07-30 15:47:34 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <[email protected]> | 2024-07-31 16:44:06 +0200 |
commit | e67886c038dc79755c14ec77bbeff6605953f9ef (patch) | |
tree | 6b36585a55796b5a29d41764175e4d6d82a0d206 /common | |
parent | d5eda13cb2e57998210b66e080dc96e95b38e5f0 (diff) | |
download | hugo-e67886c038dc79755c14ec77bbeff6605953f9ef.tar.gz hugo-e67886c038dc79755c14ec77bbeff6605953f9ef.zip |
Consolidate all hashing to the common/hashing package
And remove now unsued hashing funcs.
Diffstat (limited to 'common')
-rw-r--r-- | common/hashing/hashing.go | 80 | ||||
-rw-r--r-- | common/hashing/hashing_test.go | 50 | ||||
-rw-r--r-- | common/loggers/handlersmisc.go | 4 |
3 files changed, 127 insertions, 7 deletions
diff --git a/common/hashing/hashing.go b/common/hashing/hashing.go index abf8e6b14..42194fd4d 100644 --- a/common/hashing/hashing.go +++ b/common/hashing/hashing.go @@ -15,11 +15,15 @@ package hashing import ( + "crypto/md5" "encoding/hex" "io" + "strconv" "sync" "github.com/cespare/xxhash/v2" + "github.com/gohugoio/hashstructure" + "github.com/gohugoio/hugo/identity" ) // XXHashFromReader calculates the xxHash for the given reader. @@ -50,6 +54,82 @@ func XxHashFromStringHexEncoded(f string) string { return hex.EncodeToString(hash) } +// MD5FromStringHexEncoded returns the MD5 hash of the given string. +func MD5FromStringHexEncoded(f string) string { + h := md5.New() + h.Write([]byte(f)) + return hex.EncodeToString(h.Sum(nil)) +} + +// HashString returns a hash from the given elements. +// It will panic if the hash cannot be calculated. +// Note that this hash should be used primarily for identity, not for change detection as +// it in the more complex values (e.g. Page) will not hash the full content. +func HashString(vs ...any) string { + hash := HashUint64(vs...) + return strconv.FormatUint(hash, 10) +} + +var hashOptsPool = sync.Pool{ + New: func() any { + return &hashstructure.HashOptions{ + Hasher: xxhash.New(), + } + }, +} + +func getHashOpts() *hashstructure.HashOptions { + return hashOptsPool.Get().(*hashstructure.HashOptions) +} + +func putHashOpts(opts *hashstructure.HashOptions) { + opts.Hasher.Reset() + hashOptsPool.Put(opts) +} + +// HashUint64 returns a hash from the given elements. +// It will panic if the hash cannot be calculated. +// Note that this hash should be used primarily for identity, not for change detection as +// it in the more complex values (e.g. Page) will not hash the full content. +func HashUint64(vs ...any) uint64 { + var o any + if len(vs) == 1 { + o = toHashable(vs[0]) + } else { + elements := make([]any, len(vs)) + for i, e := range vs { + elements[i] = toHashable(e) + } + o = elements + } + + hashOpts := getHashOpts() + defer putHashOpts(hashOpts) + + hash, err := hashstructure.Hash(o, hashOpts) + if err != nil { + panic(err) + } + return hash +} + +type keyer interface { + Key() string +} + +// For structs, hashstructure.Hash only works on the exported fields, +// so rewrite the input slice for known identity types. +func toHashable(v any) any { + switch t := v.(type) { + case keyer: + return t.Key() + case identity.IdentityProvider: + return t.GetIdentity() + default: + return v + } +} + type xxhashReadFrom struct { buff []byte *xxhash.Digest diff --git a/common/hashing/hashing_test.go b/common/hashing/hashing_test.go index 2e79b36b9..e059b3da8 100644 --- a/common/hashing/hashing_test.go +++ b/common/hashing/hashing_test.go @@ -14,10 +14,11 @@ package hashing import ( + "fmt" + "math" "strings" "testing" - "github.com/cespare/xxhash/v2" qt "github.com/frankban/quicktest" ) @@ -72,8 +73,47 @@ func BenchmarkXXHashFromStringHexEncoded(b *testing.B) { } } -func xxHashFromString(f string) uint64 { - h := xxhash.New() - h.WriteString(f) - return h.Sum64() +func TestHashString(t *testing.T) { + c := qt.New(t) + + c.Assert(HashString("a", "b"), qt.Equals, "3176555414984061461") + c.Assert(HashString("ab"), qt.Equals, "7347350983217793633") + + var vals []any = []any{"a", "b", tstKeyer{"c"}} + + c.Assert(HashString(vals...), qt.Equals, "4438730547989914315") + c.Assert(vals[2], qt.Equals, tstKeyer{"c"}) +} + +type tstKeyer struct { + key string +} + +func (t tstKeyer) Key() string { + return t.key +} + +func (t tstKeyer) String() string { + return "key: " + t.key +} + +func BenchmarkHashString(b *testing.B) { + word := " hello " + + var tests []string + + for i := 1; i <= 5; i++ { + sentence := strings.Repeat(word, int(math.Pow(4, float64(i)))) + tests = append(tests, sentence) + } + + b.ResetTimer() + + for _, test := range tests { + b.Run(fmt.Sprintf("n%d", len(test)), func(b *testing.B) { + for i := 0; i < b.N; i++ { + HashString(test) + } + }) + } } diff --git a/common/loggers/handlersmisc.go b/common/loggers/handlersmisc.go index 55bf8b940..2ae6300f7 100644 --- a/common/loggers/handlersmisc.go +++ b/common/loggers/handlersmisc.go @@ -21,7 +21,7 @@ import ( "sync" "github.com/bep/logg" - "github.com/gohugoio/hugo/identity" + "github.com/gohugoio/hugo/common/hashing" ) // PanicOnWarningHook panics on warnings. @@ -85,7 +85,7 @@ func (h *logOnceHandler) HandleLog(e *logg.Entry) error { } h.mu.Lock() defer h.mu.Unlock() - hash := identity.HashUint64(e.Level, e.Message, e.Fields) + hash := hashing.HashUint64(e.Level, e.Message, e.Fields) if h.seen[hash] { return errStop } |