aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2024-07-03 11:20:46 +0200
committerBjørn Erik Pedersen <[email protected]>2024-07-06 14:08:15 +0200
commit644d55475d631f60177eb792e9a010a39160df85 (patch)
tree327f2322d52e628a07f0d0690b3ce12f3f68709f
parentedeed52fc5d7c9c25fee53e0b66c155e248cf09d (diff)
downloadhugo-644d55475d631f60177eb792e9a010a39160df85.tar.gz
hugo-644d55475d631f60177eb792e9a010a39160df85.zip
Add hash.XxHash
Also move the non crypto hash funcs into this new package. This is much faster than e.g. MD5, especially for larger inputs: ``` BenchmarkXxHash/xxHash_43-10 9917955 112.2 ns/op 56 B/op 4 allocs/op BenchmarkXxHash/mdb5_43-10 6017239 204.1 ns/op 96 B/op 3 allocs/op BenchmarkXxHash/fnv32a_43-10 14407333 82.30 ns/op 16 B/op 1 allocs/op BenchmarkXxHash/xxHash_4300-10 2916892 409.7 ns/op 56 B/op 4 allocs/op BenchmarkXxHash/mdb5_4300-10 159748 7491 ns/op 4912 B/op 3 allocs/op BenchmarkXxHash/fnv32a_4300-10 218210 5510 ns/op 16 B/op 1 allocs/op ``` Fixes #12635
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-rw-r--r--tpl/crypto/crypto.go2
-rw-r--r--tpl/crypto/init.go7
-rw-r--r--tpl/hash/hash.go93
-rw-r--r--tpl/hash/hash_test.go84
-rw-r--r--tpl/tplimpl/template_funcs.go1
7 files changed, 183 insertions, 7 deletions
diff --git a/go.mod b/go.mod
index 2cff6b5e0..5a10b2480 100644
--- a/go.mod
+++ b/go.mod
@@ -21,6 +21,7 @@ require (
github.com/bep/overlayfs v0.9.2
github.com/bep/simplecobra v0.4.0
github.com/bep/tmc v0.5.1
+ github.com/cespare/xxhash/v2 v2.3.0
github.com/clbanning/mxj/v2 v2.7.0
github.com/cli/safeexec v1.0.1
github.com/disintegration/gift v1.2.1
diff --git a/go.sum b/go.sum
index 0498c7f8d..ea4788a97 100644
--- a/go.sum
+++ b/go.sum
@@ -147,6 +147,8 @@ github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
github.com/bep/workers v1.0.0 h1:U+H8YmEaBCEaFZBst7GcRVEoqeRC9dzH2dWOwGmOchg=
github.com/bep/workers v1.0.0/go.mod h1:7kIESOB86HfR2379pwoMWNy8B50D7r99fRLUyPSNyCs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
diff --git a/tpl/crypto/crypto.go b/tpl/crypto/crypto.go
index 412a19212..677f59139 100644
--- a/tpl/crypto/crypto.go
+++ b/tpl/crypto/crypto.go
@@ -25,6 +25,7 @@ import (
"hash"
"hash/fnv"
+ "github.com/gohugoio/hugo/common/hugo"
"github.com/spf13/cast"
)
@@ -72,6 +73,7 @@ func (ns *Namespace) SHA256(v any) (string, error) {
// FNV32a hashes v using fnv32a algorithm.
// <docsmeta>{"newIn": "0.98.0" }</docsmeta>
func (ns *Namespace) FNV32a(v any) (int, error) {
+ hugo.Deprecate("crypto.FNV32a", "Use hash.FNV32a.", "v0.129.0")
conv, err := cast.ToStringE(v)
if err != nil {
return 0, err
diff --git a/tpl/crypto/init.go b/tpl/crypto/init.go
index 418fbd9fb..0527fba06 100644
--- a/tpl/crypto/init.go
+++ b/tpl/crypto/init.go
@@ -53,13 +53,6 @@ func init() {
},
)
- ns.AddMethodMapping(ctx.FNV32a,
- nil,
- [][2]string{
- {`{{ crypto.FNV32a "Hugo Rocks!!" }}`, `1515779328`},
- },
- )
-
ns.AddMethodMapping(ctx.HMAC,
[]string{"hmac"},
[][2]string{
diff --git a/tpl/hash/hash.go b/tpl/hash/hash.go
new file mode 100644
index 000000000..d4a80b342
--- /dev/null
+++ b/tpl/hash/hash.go
@@ -0,0 +1,93 @@
+// 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 hash provides non-cryptographic hash functions for template use.
+package hash
+
+import (
+ "context"
+ "encoding/hex"
+ "hash/fnv"
+
+ "github.com/cespare/xxhash/v2"
+ "github.com/gohugoio/hugo/deps"
+ "github.com/gohugoio/hugo/tpl/internal"
+ "github.com/spf13/cast"
+)
+
+// New returns a new instance of the hash-namespaced template functions.
+func New() *Namespace {
+ return &Namespace{}
+}
+
+// Namespace provides template functions for the "hash" namespace.
+type Namespace struct{}
+
+// FNV32a hashes v using fnv32a algorithm.
+func (ns *Namespace) FNV32a(v any) (int, error) {
+ conv, err := cast.ToStringE(v)
+ if err != nil {
+ return 0, err
+ }
+ algorithm := fnv.New32a()
+ algorithm.Write([]byte(conv))
+ return int(algorithm.Sum32()), nil
+}
+
+// XxHash returns the xxHash of the input string.
+func (ns *Namespace) XxHash(v any) (string, error) {
+ conv, err := cast.ToStringE(v)
+ if err != nil {
+ return "", err
+ }
+
+ hasher := xxhash.New()
+
+ _, err = hasher.WriteString(conv)
+ if err != nil {
+ return "", err
+ }
+ hash := hasher.Sum(nil)
+ return hex.EncodeToString(hash), nil
+}
+
+const name = "hash"
+
+func init() {
+ f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
+ ctx := New()
+
+ ns := &internal.TemplateFuncsNamespace{
+ Name: name,
+ Context: func(cctx context.Context, args ...any) (any, error) { return ctx, nil },
+ }
+
+ ns.AddMethodMapping(ctx.XxHash,
+ []string{"xxhash"},
+ [][2]string{
+ {`{{ hash.XxHash "The quick brown fox jumps over the lazy dog" }}`, `0b242d361fda71bc`},
+ },
+ )
+
+ ns.AddMethodMapping(ctx.FNV32a,
+ nil,
+ [][2]string{
+ {`{{ hash.FNV32a "Hugo Rocks!!" }}`, `1515779328`},
+ },
+ )
+
+ return ns
+ }
+
+ internal.AddTemplateFuncsNamespace(f)
+}
diff --git a/tpl/hash/hash_test.go b/tpl/hash/hash_test.go
new file mode 100644
index 000000000..ff5d59a9a
--- /dev/null
+++ b/tpl/hash/hash_test.go
@@ -0,0 +1,84 @@
+// 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 hash provides non-cryptographic hash functions for template use.
+package hash
+
+import (
+ "crypto/md5"
+ "encoding/hex"
+ "fmt"
+ "strings"
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+ "github.com/spf13/cast"
+)
+
+func TestXxHash(t *testing.T) {
+ t.Parallel()
+ c := qt.New(t)
+
+ ns := New()
+
+ h, err := ns.XxHash("The quick brown fox jumps over the lazy dog")
+ c.Assert(err, qt.IsNil)
+ // Facit: https://asecuritysite.com/encryption/xxhash?val=The%20quick%20brown%20fox%20jumps%20over%20the%20lazy%20dog
+ c.Assert(h, qt.Equals, "0b242d361fda71bc")
+}
+
+func BenchmarkXxHash(b *testing.B) {
+ const inputSmall = "The quick brown fox jumps over the lazy dog"
+ inputLarge := strings.Repeat(inputSmall, 100)
+
+ runBench := func(name, input string, b *testing.B, fn func(v any)) {
+ b.Run(fmt.Sprintf("%s_%d", name, len(input)), func(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ fn(input)
+ }
+ })
+ }
+
+ ns := New()
+ fnXxHash := func(v any) {
+ _, err := ns.XxHash(v)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ fnFNv32a := func(v any) {
+ _, err := ns.FNV32a(v)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ // Copied from the crypto tpl/crypto package,
+ // just to have something to compare the above with.
+ fnMD5 := func(v any) {
+ conv, err := cast.ToStringE(v)
+ if err != nil {
+ panic(err)
+ }
+
+ hash := md5.Sum([]byte(conv))
+ _ = hex.EncodeToString(hash[:])
+ }
+
+ for _, input := range []string{inputSmall, inputLarge} {
+ runBench("xxHash", input, b, fnXxHash)
+ runBench("mdb5", input, b, fnMD5)
+ runBench("fnv32a", input, b, fnFNv32a)
+ }
+}
diff --git a/tpl/tplimpl/template_funcs.go b/tpl/tplimpl/template_funcs.go
index 815270631..3daba74a0 100644
--- a/tpl/tplimpl/template_funcs.go
+++ b/tpl/tplimpl/template_funcs.go
@@ -43,6 +43,7 @@ import (
_ "github.com/gohugoio/hugo/tpl/diagrams"
_ "github.com/gohugoio/hugo/tpl/encoding"
_ "github.com/gohugoio/hugo/tpl/fmt"
+ _ "github.com/gohugoio/hugo/tpl/hash"
_ "github.com/gohugoio/hugo/tpl/hugo"
_ "github.com/gohugoio/hugo/tpl/images"
_ "github.com/gohugoio/hugo/tpl/inflect"