aboutsummaryrefslogtreecommitdiffhomepage
path: root/common
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2024-07-30 15:47:34 +0200
committerBjørn Erik Pedersen <[email protected]>2024-07-31 16:44:06 +0200
commite67886c038dc79755c14ec77bbeff6605953f9ef (patch)
tree6b36585a55796b5a29d41764175e4d6d82a0d206 /common
parentd5eda13cb2e57998210b66e080dc96e95b38e5f0 (diff)
downloadhugo-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.go80
-rw-r--r--common/hashing/hashing_test.go50
-rw-r--r--common/loggers/handlersmisc.go4
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
}