Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b08cb55a0 | |||
| 216a69a1ef | |||
| e67886c038 | |||
| d5eda13cb2 | |||
| 8b5d796989 | |||
| c9d7577cb4 | |||
| 2babd6404e | |||
| 78db8aebca | |||
| 3140e0b994 | |||
| 9989404d97 | |||
| b81ba2a0f0 | |||
| afdd87db59 | |||
| e45eae4d67 | |||
| 9b1b11c8a5 | |||
| 9d2b5f98d0 | |||
| 0e00561620 | |||
| 71842140d0 | |||
| cb95a033c1 | |||
| 3240511153 | |||
| 72ff937e11 | |||
| a28bed0817 | |||
| 979423f4d5 | |||
| e85be29867 | |||
| 4d8bfa7f1c | |||
| f0ed91caba | |||
| 7be0377505 |
@@ -102,6 +102,9 @@ Build the extended edition:
|
||||
```text
|
||||
CGO_ENABLED=1 go install -tags extended github.com/gohugoio/hugo@latest
|
||||
```
|
||||
## Star History
|
||||
|
||||
[](https://star-history.com/#gohugoio/hugo&Timeline)
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
// 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 hashing provides common hashing utilities.
|
||||
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.
|
||||
func XXHashFromReader(r io.Reader) (uint64, int64, error) {
|
||||
h := getXxHashReadFrom()
|
||||
defer putXxHashReadFrom(h)
|
||||
|
||||
size, err := io.Copy(h, r)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return h.Sum64(), size, nil
|
||||
}
|
||||
|
||||
// XXHashFromString calculates the xxHash for the given string.
|
||||
func XXHashFromString(s string) (uint64, error) {
|
||||
h := xxhash.New()
|
||||
h.WriteString(s)
|
||||
return h.Sum64(), nil
|
||||
}
|
||||
|
||||
// XxHashFromStringHexEncoded calculates the xxHash for the given string
|
||||
// and returns the hash as a hex encoded string.
|
||||
func XxHashFromStringHexEncoded(f string) string {
|
||||
h := xxhash.New()
|
||||
h.WriteString(f)
|
||||
hash := h.Sum(nil)
|
||||
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
|
||||
}
|
||||
|
||||
func (x *xxhashReadFrom) ReadFrom(r io.Reader) (int64, error) {
|
||||
for {
|
||||
n, err := r.Read(x.buff)
|
||||
if n > 0 {
|
||||
x.Digest.Write(x.buff[:n])
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return int64(n), err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var xXhashReadFromPool = sync.Pool{
|
||||
New: func() any {
|
||||
return &xxhashReadFrom{Digest: xxhash.New(), buff: make([]byte, 48*1024)}
|
||||
},
|
||||
}
|
||||
|
||||
func getXxHashReadFrom() *xxhashReadFrom {
|
||||
return xXhashReadFromPool.Get().(*xxhashReadFrom)
|
||||
}
|
||||
|
||||
func putXxHashReadFrom(h *xxhashReadFrom) {
|
||||
h.Reset()
|
||||
xXhashReadFromPool.Put(h)
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// 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 hashing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestXxHashFromReader(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
s := "Hello World"
|
||||
r := strings.NewReader(s)
|
||||
got, size, err := XXHashFromReader(r)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(size, qt.Equals, int64(len(s)))
|
||||
c.Assert(got, qt.Equals, uint64(7148569436472236994))
|
||||
}
|
||||
|
||||
func TestXxHashFromReaderPara(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 100; j++ {
|
||||
s := strings.Repeat("Hello ", i+j+1*42)
|
||||
r := strings.NewReader(s)
|
||||
got, size, err := XXHashFromReader(r)
|
||||
c.Assert(size, qt.Equals, int64(len(s)))
|
||||
c.Assert(err, qt.IsNil)
|
||||
expect, _ := XXHashFromString(s)
|
||||
c.Assert(got, qt.Equals, expect)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestXxHashFromString(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
s := "Hello World"
|
||||
got, err := XXHashFromString(s)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(got, qt.Equals, uint64(7148569436472236994))
|
||||
}
|
||||
|
||||
func TestXxHashFromStringHexEncoded(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
s := "The quick brown fox jumps over the lazy dog"
|
||||
got := XxHashFromStringHexEncoded(s)
|
||||
// Facit: https://asecuritysite.com/encryption/xxhash?val=The%20quick%20brown%20fox%20jumps%20over%20the%20lazy%20dog
|
||||
c.Assert(got, qt.Equals, "0b242d361fda71bc")
|
||||
}
|
||||
|
||||
func BenchmarkXXHashFromReader(b *testing.B) {
|
||||
r := strings.NewReader("Hello World")
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
XXHashFromReader(r)
|
||||
r.Seek(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkXXHashFromString(b *testing.B) {
|
||||
s := "Hello World"
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
XXHashFromString(s)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkXXHashFromStringHexEncoded(b *testing.B) {
|
||||
s := "The quick brown fox jumps over the lazy dog"
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
XxHashFromStringHexEncoded(s)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ package hugo
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 129,
|
||||
Minor: 131,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@ package config
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
)
|
||||
|
||||
func DecodeNamespace[S, C any](configSource any, buildConfig func(any) (C, any, error)) (*ConfigNamespace[S, C], error) {
|
||||
// Calculate the hash of the input (not including any defaults applied later).
|
||||
// This allows us to introduce new config options without breaking the hash.
|
||||
h := identity.HashString(configSource)
|
||||
h := hashing.HashString(configSource)
|
||||
|
||||
// Build the config
|
||||
c, ext, err := buildConfig(configSource)
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestNamespace(t *testing.T) {
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(ns, qt.Not(qt.IsNil))
|
||||
c.Assert(ns.SourceStructure, qt.DeepEquals, map[string]interface{}{"foo": "bar"})
|
||||
c.Assert(ns.SourceHash, qt.Equals, "14368731254619220105")
|
||||
c.Assert(ns.SourceHash, qt.Equals, "1450430416588600409")
|
||||
c.Assert(ns.Config, qt.DeepEquals, &tstNsExt{Foo: "bar"})
|
||||
c.Assert(ns.Signature(), qt.DeepEquals, []*tstNsExt(nil))
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ var DefaultConfig = Config{
|
||||
Allow: MustNewWhitelist(
|
||||
"^(dart-)?sass(-embedded)?$", // sass, dart-sass, dart-sass-embedded.
|
||||
"^go$", // for Go Modules
|
||||
"^git$", // For Git info
|
||||
"^npx$", // used by all Node tools (Babel, PostCSS).
|
||||
"^postcss$",
|
||||
"^tailwindcss$",
|
||||
|
||||
@@ -135,7 +135,7 @@ func TestToTOML(t *testing.T) {
|
||||
got := DefaultConfig.ToTOML()
|
||||
|
||||
c.Assert(got, qt.Equals,
|
||||
"[security]\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^npx$', '^postcss$', '^tailwindcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
|
||||
"[security]\n enableInlineShortcodes = false\n\n [security.exec]\n allow = ['^(dart-)?sass(-embedded)?$', '^go$', '^git$', '^npx$', '^postcss$', '^tailwindcss$']\n osEnv = ['(?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE)$']\n\n [security.funcs]\n getenv = ['^HUGO_', '^CI$']\n\n [security.http]\n methods = ['(?i)GET|POST']\n urls = ['.*']",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: math.Acos
|
||||
description: Returns the arccosine, in radians, of the given number.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related:
|
||||
- functions/math/Asin
|
||||
- functions/math/Atan
|
||||
- functions/math/Atan2
|
||||
- functions/math/Pi
|
||||
- functions/math/Sin
|
||||
- functions/math/Cos
|
||||
- functions/math/Tan
|
||||
returnType: float64
|
||||
signatures: [math.Acos VALUE]
|
||||
---
|
||||
|
||||
{{< new-in 0.130.0 >}}
|
||||
|
||||
```go-html-template
|
||||
{{ math.Acos 1 }} → 0
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: math.Asin
|
||||
description: Returns the arcsine, in radians, of the given number.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related:
|
||||
- functions/math/Acos
|
||||
- functions/math/Atan
|
||||
- functions/math/Atan2
|
||||
- functions/math/Pi
|
||||
- functions/math/Sin
|
||||
- functions/math/Cos
|
||||
- functions/math/Tan
|
||||
returnType: float64
|
||||
signatures: [math.Asin VALUE]
|
||||
---
|
||||
|
||||
{{< new-in 0.130.0 >}}
|
||||
|
||||
```go-html-template
|
||||
{{ math.Asin 1 }} → 1.5707963267948966
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: math.Atan
|
||||
description: Returns the arctangent, in radians, of the given number.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related:
|
||||
- functions/math/Atan2
|
||||
- functions/math/Asin
|
||||
- functions/math/Acos
|
||||
- functions/math/Pi
|
||||
- functions/math/Sin
|
||||
- functions/math/Cos
|
||||
- functions/math/Tan
|
||||
returnType: float64
|
||||
signatures: [math.Atan VALUE]
|
||||
---
|
||||
|
||||
{{< new-in 0.130.0 >}}
|
||||
|
||||
```go-html-template
|
||||
{{ math.Atan 1 }} → 0.7853981633974483
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: math.Atan2
|
||||
description: Returns the arctangent, in radians, of the given number pair, determining the correct quadrant from their signs.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related:
|
||||
- functions/math/Atan
|
||||
- functions/math/Asin
|
||||
- functions/math/Acos
|
||||
- functions/math/Pi
|
||||
- functions/math/Sin
|
||||
- functions/math/Cos
|
||||
- functions/math/Tan
|
||||
returnType: float64
|
||||
signatures: [math.Atan2 VALUE VALUE]
|
||||
---
|
||||
|
||||
{{< new-in 0.130.0 >}}
|
||||
|
||||
```go-html-template
|
||||
{{ math.Atan2 1 2 }} → 0.4636476090008061
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: math.Cos
|
||||
description: Returns the cosine of the given radian number.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related:
|
||||
- functions/math/Pi
|
||||
- functions/math/Sin
|
||||
- functions/math/Tan
|
||||
- functions/math/Asin
|
||||
- functions/math/Acos
|
||||
- functions/math/Atan
|
||||
- functions/math/Atan2
|
||||
returnType: float64
|
||||
signatures: [math.Cos VALUE]
|
||||
---
|
||||
|
||||
{{< new-in 0.130.0 >}}
|
||||
|
||||
```go-html-template
|
||||
{{ math.Cos 1 }} → 0.5403023058681398
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: math.Pi
|
||||
description: Returns the mathematical constant pi.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related:
|
||||
- functions/math/Sin
|
||||
- functions/math/Cos
|
||||
- functions/math/Tan
|
||||
- functions/math/Asin
|
||||
- functions/math/Acos
|
||||
- functions/math/Atan
|
||||
- functions/math/Atan2
|
||||
returnType: float64
|
||||
signatures: [math.Pi]
|
||||
---
|
||||
|
||||
{{< new-in 0.130.0 >}}
|
||||
|
||||
```go-html-template
|
||||
{{ math.Pi }} → 3.141592653589793
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: math.Sin
|
||||
description: Returns the sine of the given radian number.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related:
|
||||
- functions/math/Pi
|
||||
- functions/math/Cos
|
||||
- functions/math/Tan
|
||||
- functions/math/Asin
|
||||
- functions/math/Acos
|
||||
- functions/math/Atan
|
||||
- functions/math/Atan2
|
||||
returnType: float64
|
||||
signatures: [math.Sin VALUE]
|
||||
---
|
||||
|
||||
{{< new-in 0.130.0 >}}
|
||||
|
||||
```go-html-template
|
||||
{{ math.Sin 1 }} → 0.8414709848078965
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: math.Tan
|
||||
description: Returns the tangent of the given radian number.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related:
|
||||
- functions/math/Pi
|
||||
- functions/math/Sin
|
||||
- functions/math/Cos
|
||||
- functions/math/Asin
|
||||
- functions/math/Acos
|
||||
- functions/math/Atan
|
||||
- functions/math/Atan2
|
||||
returnType: float64
|
||||
signatures: [math.Tan VALUE]
|
||||
---
|
||||
|
||||
{{< new-in 0.130.0 >}}
|
||||
|
||||
```go-html-template
|
||||
{{ math.Tan 1 }} → 1.557407724654902
|
||||
```
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: math.ToDegrees
|
||||
description: ToDegrees converts radians into degrees.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related:
|
||||
- functions/math/ToRadians
|
||||
- functions/math/Pi
|
||||
returnType: float64
|
||||
signatures: [math.ToDegrees VALUE]
|
||||
---
|
||||
|
||||
{{< new-in 0.130.0 >}}
|
||||
|
||||
```go-html-template
|
||||
{{ math.ToDegrees 1.5707963267948966 }} → 90
|
||||
```
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
title: math.ToRadians
|
||||
description: ToRadians converts degrees into radians.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related:
|
||||
- functions/math/ToDegrees
|
||||
- functions/math/Pi
|
||||
returnType: float64
|
||||
signatures: [math.ToRadians VALUE]
|
||||
---
|
||||
|
||||
{{< new-in 0.130.0 >}}
|
||||
|
||||
```go-html-template
|
||||
{{ math.ToRadians 90 }} → 1.5707963267948966
|
||||
```
|
||||
@@ -307,6 +307,9 @@ chroma:
|
||||
- gherkin
|
||||
- Gherkin
|
||||
Name: Gherkin
|
||||
- Aliases:
|
||||
- gleam>
|
||||
Name: Gleam
|
||||
- Aliases:
|
||||
- glsl
|
||||
Name: GLSL
|
||||
@@ -1079,6 +1082,8 @@ config:
|
||||
escapedSpace: false
|
||||
definitionList: true
|
||||
extras:
|
||||
delete:
|
||||
enable: false
|
||||
insert:
|
||||
enable: false
|
||||
mark:
|
||||
@@ -1331,6 +1336,7 @@ config:
|
||||
minifyOutput: false
|
||||
tdewolff:
|
||||
css:
|
||||
inline: false
|
||||
keepCSS2: true
|
||||
precision: 0
|
||||
html:
|
||||
@@ -1353,6 +1359,7 @@ config:
|
||||
keepNumbers: false
|
||||
precision: 0
|
||||
svg:
|
||||
inline: false
|
||||
keepComments: false
|
||||
precision: 0
|
||||
xml:
|
||||
@@ -1364,37 +1371,44 @@ config:
|
||||
min: ""
|
||||
imports: null
|
||||
mounts:
|
||||
- excludeFiles: null
|
||||
- disableWatch: false
|
||||
excludeFiles: null
|
||||
includeFiles: null
|
||||
lang: ""
|
||||
source: content
|
||||
target: content
|
||||
- excludeFiles: null
|
||||
- disableWatch: false
|
||||
excludeFiles: null
|
||||
includeFiles: null
|
||||
lang: ""
|
||||
source: data
|
||||
target: data
|
||||
- excludeFiles: null
|
||||
- disableWatch: false
|
||||
excludeFiles: null
|
||||
includeFiles: null
|
||||
lang: ""
|
||||
source: layouts
|
||||
target: layouts
|
||||
- excludeFiles: null
|
||||
- disableWatch: false
|
||||
excludeFiles: null
|
||||
includeFiles: null
|
||||
lang: ""
|
||||
source: i18n
|
||||
target: i18n
|
||||
- excludeFiles: null
|
||||
- disableWatch: false
|
||||
excludeFiles: null
|
||||
includeFiles: null
|
||||
lang: ""
|
||||
source: archetypes
|
||||
target: archetypes
|
||||
- excludeFiles: null
|
||||
- disableWatch: false
|
||||
excludeFiles: null
|
||||
includeFiles: null
|
||||
lang: ""
|
||||
source: assets
|
||||
target: assets
|
||||
- excludeFiles: null
|
||||
- disableWatch: false
|
||||
excludeFiles: null
|
||||
includeFiles: null
|
||||
lang: ""
|
||||
source: static
|
||||
@@ -1583,8 +1597,12 @@ config:
|
||||
term:
|
||||
- html
|
||||
- rss
|
||||
paginate: 10
|
||||
paginatePath: page
|
||||
paginate: 0
|
||||
paginatePath: ""
|
||||
pagination:
|
||||
disableAliases: false
|
||||
pagerSize: 10
|
||||
path: page
|
||||
panicOnWarning: false
|
||||
params: {}
|
||||
permalinks:
|
||||
@@ -1656,8 +1674,10 @@ config:
|
||||
allow:
|
||||
- ^(dart-)?sass(-embedded)?$
|
||||
- ^go$
|
||||
- ^git$
|
||||
- ^npx$
|
||||
- ^postcss$
|
||||
- ^tailwindcss$
|
||||
osEnv:
|
||||
- (?i)^((HTTPS?|NO)_PROXY|PATH(EXT)?|APPDATA|TE?MP|TERM|GO\w+|(XDG_CONFIG_)?HOME|USERPROFILE|SSH_AUTH_SOCK|DISPLAY|LANG|SYSTEMDRIVE)$
|
||||
funcs:
|
||||
@@ -1761,6 +1781,8 @@ config_helpers:
|
||||
_merge: shallow
|
||||
outputs:
|
||||
_merge: none
|
||||
pagination:
|
||||
_merge: none
|
||||
params:
|
||||
_merge: deep
|
||||
permalinks:
|
||||
@@ -2738,14 +2760,9 @@ tpl:
|
||||
crypto:
|
||||
FNV32a:
|
||||
Aliases: null
|
||||
Args:
|
||||
- v
|
||||
Description: |-
|
||||
FNV32a hashes v using fnv32a algorithm.
|
||||
<docsmeta>{"newIn": "0.98.0" }</docsmeta>
|
||||
Examples:
|
||||
- - '{{ crypto.FNV32a "Hugo Rocks!!" }}'
|
||||
- "1515779328"
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
HMAC:
|
||||
Aliases:
|
||||
- hmac
|
||||
@@ -2788,11 +2805,30 @@ tpl:
|
||||
- - '{{ sha256 "Hello world, gophers!" }}'
|
||||
- 6ec43b78da9669f50e4e422575c54bf87536954ccd58280219c393f2ce352b46
|
||||
css:
|
||||
PostCSS:
|
||||
Aliases:
|
||||
- postCSS
|
||||
Args:
|
||||
- args
|
||||
Description: PostCSS processes the given Resource with PostCSS.
|
||||
Examples: []
|
||||
Quoted:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
Sass:
|
||||
Aliases:
|
||||
- toCSS
|
||||
Args:
|
||||
- args
|
||||
Description: Sass processes the given Resource with SASS.
|
||||
Examples: []
|
||||
TailwindCSS:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
Unquoted:
|
||||
Aliases: null
|
||||
Args: null
|
||||
@@ -3013,6 +3049,24 @@ tpl:
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
hash:
|
||||
FNV32a:
|
||||
Aliases: null
|
||||
Args:
|
||||
- v
|
||||
Description: FNV32a hashes v using fnv32a algorithm.
|
||||
Examples:
|
||||
- - '{{ hash.FNV32a "Hugo Rocks!!" }}'
|
||||
- "1515779328"
|
||||
XxHash:
|
||||
Aliases:
|
||||
- xxhash
|
||||
Args:
|
||||
- v
|
||||
Description: XxHash returns the xxHash of the input string.
|
||||
Examples:
|
||||
- - '{{ hash.XxHash "The quick brown fox jumps over the lazy dog" }}'
|
||||
- 0b242d361fda71bc
|
||||
hugo:
|
||||
Deps:
|
||||
Aliases: null
|
||||
@@ -3228,6 +3282,13 @@ tpl:
|
||||
- - '{{ "cats" | singularize }}'
|
||||
- cat
|
||||
js:
|
||||
Babel:
|
||||
Aliases:
|
||||
- babel
|
||||
Args:
|
||||
- args
|
||||
Description: Babel processes the given Resource with Babel.
|
||||
Examples: []
|
||||
Build:
|
||||
Aliases: null
|
||||
Args: null
|
||||
@@ -3341,6 +3402,14 @@ tpl:
|
||||
Examples:
|
||||
- - '{{ math.Abs -2.1 }}'
|
||||
- "2.1"
|
||||
Acos:
|
||||
Aliases: null
|
||||
Args:
|
||||
- "n"
|
||||
Description: Acos returns the arccosine, in radians, of n.
|
||||
Examples:
|
||||
- - '{{ math.Acos 1 }}'
|
||||
- "0"
|
||||
Add:
|
||||
Aliases:
|
||||
- add
|
||||
@@ -3350,6 +3419,32 @@ tpl:
|
||||
Examples:
|
||||
- - '{{ add 1 2 }}'
|
||||
- "3"
|
||||
Asin:
|
||||
Aliases: null
|
||||
Args:
|
||||
- "n"
|
||||
Description: Asin returns the arcsine, in radians, of n.
|
||||
Examples:
|
||||
- - '{{ math.Asin 1 }}'
|
||||
- "1.5707963267948966"
|
||||
Atan:
|
||||
Aliases: null
|
||||
Args:
|
||||
- "n"
|
||||
Description: Atan returns the arctangent, in radians, of n.
|
||||
Examples:
|
||||
- - '{{ math.Atan 1 }}'
|
||||
- "0.7853981633974483"
|
||||
Atan2:
|
||||
Aliases: null
|
||||
Args:
|
||||
- "n"
|
||||
- m
|
||||
Description: Atan2 returns the arc tangent of n/m, using the signs of the
|
||||
two to determine the quadrant of the return value.
|
||||
Examples:
|
||||
- - '{{ math.Atan2 1 2 }}'
|
||||
- "0.4636476090008061"
|
||||
Ceil:
|
||||
Aliases: null
|
||||
Args:
|
||||
@@ -3359,6 +3454,14 @@ tpl:
|
||||
Examples:
|
||||
- - '{{ math.Ceil 2.1 }}'
|
||||
- "3"
|
||||
Cos:
|
||||
Aliases: null
|
||||
Args:
|
||||
- "n"
|
||||
Description: Cos returns the cosine of the radian argument n.
|
||||
Examples:
|
||||
- - '{{ math.Cos 1 }}'
|
||||
- "0.5403023058681398"
|
||||
Counter:
|
||||
Aliases: null
|
||||
Args: null
|
||||
@@ -3438,6 +3541,13 @@ tpl:
|
||||
Examples:
|
||||
- - '{{ mul 2 3 }}'
|
||||
- "6"
|
||||
Pi:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: Pi returns the mathematical constant pi.
|
||||
Examples:
|
||||
- - '{{ math.Pi }}'
|
||||
- "3.141592653589793"
|
||||
Pow:
|
||||
Aliases:
|
||||
- pow
|
||||
@@ -3470,6 +3580,14 @@ tpl:
|
||||
Examples:
|
||||
- - '{{ math.Round 1.5 }}'
|
||||
- "2"
|
||||
Sin:
|
||||
Aliases: null
|
||||
Args:
|
||||
- "n"
|
||||
Description: Sin returns the sine of the radian argument n.
|
||||
Examples:
|
||||
- - '{{ math.Sin 1 }}'
|
||||
- "0.8414709848078965"
|
||||
Sqrt:
|
||||
Aliases: null
|
||||
Args:
|
||||
@@ -3492,6 +3610,30 @@ tpl:
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
Tan:
|
||||
Aliases: null
|
||||
Args:
|
||||
- "n"
|
||||
Description: Tan returns the tangent of the radian argument n.
|
||||
Examples:
|
||||
- - '{{ math.Tan 1 }}'
|
||||
- "1.557407724654902"
|
||||
ToDegrees:
|
||||
Aliases: null
|
||||
Args:
|
||||
- "n"
|
||||
Description: ToDegrees converts radians into degrees.
|
||||
Examples:
|
||||
- - '{{ math.ToDegrees 1.5707963267948966 }}'
|
||||
- "90"
|
||||
ToRadians:
|
||||
Aliases: null
|
||||
Args:
|
||||
- "n"
|
||||
Description: ToRadians converts degrees into radians.
|
||||
Examples:
|
||||
- - '{{ math.ToRadians 90 }}'
|
||||
- "1.5707963267948966"
|
||||
openapi3:
|
||||
Unmarshal:
|
||||
Aliases: null
|
||||
@@ -3657,12 +3799,10 @@ tpl:
|
||||
- Slice
|
||||
resources:
|
||||
Babel:
|
||||
Aliases:
|
||||
- babel
|
||||
Args:
|
||||
- args
|
||||
Description: Babel processes the given Resource with Babel.
|
||||
Examples: []
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
ByType:
|
||||
Aliases: null
|
||||
Args: null
|
||||
@@ -3738,27 +3878,20 @@ tpl:
|
||||
minifier.
|
||||
Examples: []
|
||||
PostCSS:
|
||||
Aliases:
|
||||
- postCSS
|
||||
Args:
|
||||
- args
|
||||
Description: PostCSS processes the given Resource with PostCSS
|
||||
Examples: []
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
PostProcess:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
ToCSS:
|
||||
Aliases:
|
||||
- toCSS
|
||||
Args:
|
||||
- args
|
||||
Description: |-
|
||||
ToCSS converts the given Resource to CSS. You can optional provide an Options object
|
||||
as second argument. As an option, you can e.g. specify e.g. the target path (string)
|
||||
for the converted CSS resource.
|
||||
Examples: []
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
safe:
|
||||
CSS:
|
||||
Aliases:
|
||||
@@ -3838,6 +3971,11 @@ tpl:
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
CheckReady:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
Config:
|
||||
Aliases: null
|
||||
Args: null
|
||||
@@ -4339,6 +4477,23 @@ tpl:
|
||||
- - '{{ "With [Markdown](/markdown) inside." | markdownify | truncate 14 }}'
|
||||
- With <a href="/markdown">Markdown …</a>
|
||||
templates:
|
||||
Defer:
|
||||
Aliases: null
|
||||
Args:
|
||||
- args
|
||||
Description: Defer defers the execution of a template block.
|
||||
Examples: []
|
||||
DoDefer:
|
||||
Aliases:
|
||||
- doDefer
|
||||
Args:
|
||||
- ctx
|
||||
- id
|
||||
- optsv
|
||||
Description: |-
|
||||
DoDefer defers the execution of a template block.
|
||||
For internal use only.
|
||||
Examples: []
|
||||
Exists:
|
||||
Aliases: null
|
||||
Args:
|
||||
|
||||
@@ -4,17 +4,18 @@ require (
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69
|
||||
github.com/alecthomas/chroma/v2 v2.14.0
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.35.4
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.3
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.38.4
|
||||
github.com/bep/clocks v0.5.0
|
||||
github.com/bep/debounce v1.2.0
|
||||
github.com/bep/gitmap v1.4.0
|
||||
github.com/bep/gitmap v1.6.0
|
||||
github.com/bep/goat v0.5.0
|
||||
github.com/bep/godartsass v1.2.0
|
||||
github.com/bep/godartsass/v2 v2.0.0
|
||||
github.com/bep/golibsass v1.1.1
|
||||
github.com/bep/gowebp v0.3.0
|
||||
github.com/bep/helpers v0.4.0
|
||||
github.com/bep/imagemeta v0.7.5
|
||||
github.com/bep/lazycache v0.4.0
|
||||
github.com/bep/logg v0.4.0
|
||||
github.com/bep/mclib v1.20400.20402
|
||||
@@ -36,6 +37,7 @@ require (
|
||||
github.com/gobuffalo/flect v1.0.2
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e
|
||||
github.com/gohugoio/hashstructure v0.1.0
|
||||
github.com/gohugoio/httpcache v0.7.0
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.2.0
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0
|
||||
@@ -52,7 +54,6 @@ require (
|
||||
github.com/makeworld-the-better-one/dither/v2 v2.4.0
|
||||
github.com/marekm4/color-extractor v1.2.1
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mitchellh/hashstructure v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c
|
||||
github.com/muesli/smartcrop v0.3.0
|
||||
github.com/niklasfasching/go-org v1.7.0
|
||||
@@ -60,7 +61,6 @@ require (
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
||||
github.com/pelletier/go-toml/v2 v2.2.2
|
||||
github.com/rogpeppe/go-internal v1.12.0
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
github.com/sanity-io/litter v1.5.5
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cast v1.6.0
|
||||
@@ -75,11 +75,11 @@ require (
|
||||
gocloud.dev v0.36.0
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326
|
||||
golang.org/x/image v0.18.0
|
||||
golang.org/x/mod v0.17.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/mod v0.19.0
|
||||
golang.org/x/net v0.27.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/text v0.16.0
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d
|
||||
golang.org/x/tools v0.23.0
|
||||
google.golang.org/api v0.152.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
@@ -90,21 +90,21 @@ require (
|
||||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
cloud.google.com/go/iam v1.1.5 // indirect
|
||||
cloud.google.com/go/storage v1.35.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
|
||||
github.com/aws/aws-sdk-go v1.50.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||
@@ -115,16 +115,16 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 // indirect
|
||||
github.com/aws/smithy-go v1.20.2 // indirect
|
||||
github.com/aws/smithy-go v1.20.3 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.8 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/google/wire v0.5.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
@@ -141,13 +141,13 @@ require (
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.25.0 // indirect
|
||||
golang.org/x/oauth2 v0.15.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
|
||||
@@ -46,12 +46,12 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
|
||||
cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w=
|
||||
cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0 h1:fb8kj/Dh4CSwgsOzHeZY4Xh68cFVbzXx+ONXGMY//4w=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3qKzmAucSi51+SP1OhohieR821Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0 h1:BMAjVKJM0U/CYF27gA0ZMmXGkOcvfFtD0oHVZ1TIPRI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 h1:d81/ng9rET2YqdVkVwkb6EXeRrLJIwyGnJcAlAWKwhs=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 h1:gggzg0SUMs6SQbEw+3LoSsYf9YMjkupeAnHMX8O9mmY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0/go.mod h1:+6KLcKIVgxoBDMqMO/Nvy7bZ9a0nbU3I1DtFQK3YvB4=
|
||||
@@ -59,8 +59,8 @@ github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
|
||||
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0 h1:hVeq+yCyUi+MsoO/CU95yqCIcdzra5ovzk8Q2BBpV2M=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.0/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69 h1:+tu3HOoMXB7RXEINRVIpxJCT+KdYiI7LAEAUrOw3dIU=
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZdiDllfyYH5l5OkAaZtk7VkWe89bPJFmnDBNHxg=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
@@ -74,8 +74,8 @@ github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c h1:651/eoCRnQ7YtS
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aws/aws-sdk-go v1.50.7 h1:odKb+uneeGgF2jgAerKjFzpljiyZxleV4SHB7oBK+YA=
|
||||
github.com/aws/aws-sdk-go v1.50.7/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY=
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.1 h1:z6DqMxclFGL3Zfo+4Q0rLnAZ6yVkzCRxhRMsiRQnD1o=
|
||||
@@ -86,16 +86,16 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6Jk
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 h1:FnLf60PtjXp8ZOzQfhJVsqF0OtYKQZWQfqOLshh8YXg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7/go.mod h1:tDVvl8hyU6E9B8TrnNrZQEVkQlB8hjJwcgpPhgtlnNg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.35.4 h1:a4gfRHHCzvV0jEjOUdZOK0oJ4H21x5WT+E4ucWk4jeM=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.35.4/go.mod h1:Pphkts8iBnexoEpcMti5fUvN3/yoGRLtl2heOeppF70=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.38.4 h1:I/sQ9uGOs72/483obb2SPoa9ZEsYGbel6jcTTwD/0zU=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.38.4/go.mod h1:P6ByphKl2oNQZlv4WsCaLSmRncKEcOnbitYLtJPfqZI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo=
|
||||
@@ -112,14 +112,14 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsY
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 h1:5UYvv8JUvllZsRnfrcMQ+hJ9jNICmcgKPAO1CER25Wg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE=
|
||||
github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/bep/clocks v0.5.0 h1:hhvKVGLPQWRVsBP/UB7ErrHYIO42gINVbvqxvYTPVps=
|
||||
github.com/bep/clocks v0.5.0/go.mod h1:SUq3q+OOq41y2lRQqH5fsOoxN8GbxSiT6jvoVVLCVhU=
|
||||
github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo=
|
||||
github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||
github.com/bep/gitmap v1.4.0 h1:GeWbPb2QDTfcZLBQmCB693N3sJmPQfeu81fDrD5r8x8=
|
||||
github.com/bep/gitmap v1.4.0/go.mod h1:n+3W1f/rot2hynsqEGxGMErPRgT41n9CkGuzPvz9cIw=
|
||||
github.com/bep/gitmap v1.6.0 h1:sDuQMm9HoTL0LtlrfxjbjgAg2wHQd4nkMup2FInYzhA=
|
||||
github.com/bep/gitmap v1.6.0/go.mod h1:n+3W1f/rot2hynsqEGxGMErPRgT41n9CkGuzPvz9cIw=
|
||||
github.com/bep/goat v0.5.0 h1:S8jLXHCVy/EHIoCY+btKkmcxcXFd34a0Q63/0D4TKeA=
|
||||
github.com/bep/goat v0.5.0/go.mod h1:Md9x7gRxiWKs85yHlVTvHQw9rg86Bm+Y4SuYE8CTH7c=
|
||||
github.com/bep/godartsass v1.2.0 h1:E2VvQrxAHAFwbjyOIExAMmogTItSKodoKuijNrGm5yU=
|
||||
@@ -132,6 +132,8 @@ github.com/bep/gowebp v0.3.0 h1:MhmMrcf88pUY7/PsEhMgEP0T6fDUnRTMpN8OclDrbrY=
|
||||
github.com/bep/gowebp v0.3.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||
github.com/bep/helpers v0.4.0 h1:ab9veaAiWY4ST48Oxp5usaqivDmYdB744fz+tcZ3Ifs=
|
||||
github.com/bep/helpers v0.4.0/go.mod h1:/QpHdmcPagDw7+RjkLFCvnlUc8lQ5kg4KDrEkb2Yyco=
|
||||
github.com/bep/imagemeta v0.7.5 h1:swAwB5GeCIKcjS7+iFruIiuUl6Kj0qIGYxd5/EC67iw=
|
||||
github.com/bep/imagemeta v0.7.5/go.mod h1:5piPAq5Qomh07m/dPPCLN3mDJyFusvUG7VwdRD/vX0s=
|
||||
github.com/bep/lazycache v0.4.0 h1:X8yVyWNVupPd4e1jV7efi3zb7ZV/qcjKQgIQ5aPbkYI=
|
||||
github.com/bep/lazycache v0.4.0/go.mod h1:NmRm7Dexh3pmR1EignYR8PjO2cWybFQ68+QgY3VMCSc=
|
||||
github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ=
|
||||
@@ -172,7 +174,6 @@ github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvd
|
||||
github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -213,6 +214,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ=
|
||||
github.com/gohugoio/hashstructure v0.1.0 h1:kBSTMLMyTXbrJVAxaKI+wv30MMJJxn9Q8kfQtJaZ400=
|
||||
github.com/gohugoio/hashstructure v0.1.0/go.mod h1:8ohPTAfQLTs2WdzB6k9etmQYclDUeNsIHGPAFejbsEA=
|
||||
github.com/gohugoio/httpcache v0.7.0 h1:ukPnn04Rgvx48JIinZvZetBfHaWE7I01JR2Q2RrQ3Vs=
|
||||
github.com/gohugoio/httpcache v0.7.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.2.0 h1:MNdY6hYCTQEekY0oAfsxWZU1CDt6iH+tMLgyMJQh/sg=
|
||||
@@ -225,8 +228,8 @@ github.com/gohugoio/localescompressed v1.0.1 h1:KTYMi8fCWYLswFyJAeOtuk/EkXR/KPTH
|
||||
github.com/gohugoio/localescompressed v1.0.1/go.mod h1:jBF6q8D7a0vaEmcWPNcAjUZLJaIVNiwvM3WlmTvooB0=
|
||||
github.com/gohugoio/testmodBuilder/mods v0.0.0-20190520184928-c56af20f2e95 h1:sgew0XCnZwnzpWxTt3V8LLiCO7OQi3C6dycaE67wfkU=
|
||||
github.com/gohugoio/testmodBuilder/mods v0.0.0-20190520184928-c56af20f2e95/go.mod h1:bOlVlCa1/RajcHpXkrUXPSHB/Re1UnlXxD1Qp8SKOd8=
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU=
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -297,8 +300,8 @@ github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
|
||||
github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
@@ -364,8 +367,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=
|
||||
github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=
|
||||
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
@@ -386,8 +387,8 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -405,7 +406,6 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
||||
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
|
||||
@@ -474,8 +474,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -516,8 +516,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -553,8 +553,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -617,14 +617,14 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -693,8 +693,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -15,8 +15,6 @@ package helpers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -27,11 +25,11 @@ import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
bp "github.com/gohugoio/hugo/bufferpool"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/jdkato/prose/transform"
|
||||
|
||||
bp "github.com/gohugoio/hugo/bufferpool"
|
||||
)
|
||||
|
||||
// FilePathSeparator as defined by os.Separator.
|
||||
@@ -257,64 +255,6 @@ func SliceToLower(s []string) []string {
|
||||
return l
|
||||
}
|
||||
|
||||
// MD5String takes a string and returns its MD5 hash.
|
||||
func MD5String(f string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(f))
|
||||
return hex.EncodeToString(h.Sum([]byte{}))
|
||||
}
|
||||
|
||||
// MD5FromReaderFast creates a MD5 hash from the given file. It only reads parts of
|
||||
// the file for speed, so don't use it if the files are very subtly different.
|
||||
// It will not close the file.
|
||||
// It will return the MD5 hash and the size of r in bytes.
|
||||
func MD5FromReaderFast(r io.ReadSeeker) (string, int64, error) {
|
||||
const (
|
||||
// Do not change once set in stone!
|
||||
maxChunks = 8
|
||||
peekSize = 64
|
||||
seek = 2048
|
||||
)
|
||||
|
||||
h := md5.New()
|
||||
buff := make([]byte, peekSize)
|
||||
|
||||
for i := 0; i < maxChunks; i++ {
|
||||
if i > 0 {
|
||||
_, err := r.Seek(seek, 0)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return "", 0, err
|
||||
}
|
||||
}
|
||||
|
||||
_, err := io.ReadAtLeast(r, buff, peekSize)
|
||||
if err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
h.Write(buff)
|
||||
break
|
||||
}
|
||||
return "", 0, err
|
||||
}
|
||||
h.Write(buff)
|
||||
}
|
||||
|
||||
size, _ := r.Seek(0, io.SeekEnd)
|
||||
|
||||
return hex.EncodeToString(h.Sum(nil)), size, nil
|
||||
}
|
||||
|
||||
// MD5FromReader creates a MD5 hash from the given reader.
|
||||
func MD5FromReader(r io.Reader) (string, error) {
|
||||
h := md5.New()
|
||||
if _, err := io.Copy(h, r); err != nil {
|
||||
return "", nil
|
||||
}
|
||||
return hex.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// IsWhitespace determines if the given rune is whitespace.
|
||||
func IsWhitespace(r rune) bool {
|
||||
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
package helpers_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -22,7 +21,6 @@ import (
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
func TestResolveMarkup(t *testing.T) {
|
||||
@@ -256,93 +254,6 @@ func TestUniqueStringsSorted(t *testing.T) {
|
||||
c.Assert(helpers.UniqueStringsSorted(nil), qt.IsNil)
|
||||
}
|
||||
|
||||
func TestFastMD5FromFile(t *testing.T) {
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
if err := afero.WriteFile(fs, "small.txt", []byte("abc"), 0o777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := afero.WriteFile(fs, "small2.txt", []byte("abd"), 0o777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := afero.WriteFile(fs, "bigger.txt", []byte(strings.Repeat("a bc d e", 100)), 0o777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := afero.WriteFile(fs, "bigger2.txt", []byte(strings.Repeat("c d e f g", 100)), 0o777); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
sf1, err := fs.Open("small.txt")
|
||||
c.Assert(err, qt.IsNil)
|
||||
sf2, err := fs.Open("small2.txt")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
bf1, err := fs.Open("bigger.txt")
|
||||
c.Assert(err, qt.IsNil)
|
||||
bf2, err := fs.Open("bigger2.txt")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
defer sf1.Close()
|
||||
defer sf2.Close()
|
||||
defer bf1.Close()
|
||||
defer bf2.Close()
|
||||
|
||||
m1, _, err := helpers.MD5FromReaderFast(sf1)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(m1, qt.Equals, "e9c8989b64b71a88b4efb66ad05eea96")
|
||||
|
||||
m2, _, err := helpers.MD5FromReaderFast(sf2)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(m2, qt.Not(qt.Equals), m1)
|
||||
|
||||
m3, _, err := helpers.MD5FromReaderFast(bf1)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(m3, qt.Not(qt.Equals), m2)
|
||||
|
||||
m4, _, err := helpers.MD5FromReaderFast(bf2)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(m4, qt.Not(qt.Equals), m3)
|
||||
|
||||
m5, err := helpers.MD5FromReader(bf2)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(m5, qt.Not(qt.Equals), m4)
|
||||
}
|
||||
|
||||
func BenchmarkMD5FromFileFast(b *testing.B) {
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
for _, full := range []bool{false, true} {
|
||||
b.Run(fmt.Sprintf("full=%t", full), func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
if err := afero.WriteFile(fs, "file.txt", []byte(strings.Repeat("1234567890", 2000)), 0o777); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
f, err := fs.Open("file.txt")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
b.StartTimer()
|
||||
if full {
|
||||
if _, err := helpers.MD5FromReader(f); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
if _, _, err := helpers.MD5FromReaderFast(f); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUniqueStrings(b *testing.B) {
|
||||
input := []string{"a", "b", "d", "e", "d", "h", "a", "i"}
|
||||
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
package hugolib
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/bep/gitmap"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/source"
|
||||
)
|
||||
@@ -38,10 +40,24 @@ func (g *gitInfo) forPage(p page.Page) source.GitInfo {
|
||||
return source.NewGitInfo(*gi)
|
||||
}
|
||||
|
||||
func newGitInfo(conf config.AllProvider) (*gitInfo, error) {
|
||||
workingDir := conf.BaseConfig().WorkingDir
|
||||
func newGitInfo(d *deps.Deps) (*gitInfo, error) {
|
||||
opts := gitmap.Options{
|
||||
Repository: d.Conf.BaseConfig().WorkingDir,
|
||||
GetGitCommandFunc: func(stdout, stderr io.Writer, args ...string) (gitmap.Runner, error) {
|
||||
var argsv []any
|
||||
for _, arg := range args {
|
||||
argsv = append(argsv, arg)
|
||||
}
|
||||
argsv = append(
|
||||
argsv,
|
||||
hexec.WithStdout(stdout),
|
||||
hexec.WithStderr(stderr),
|
||||
)
|
||||
return d.ExecHelper.New("git", argsv...)
|
||||
},
|
||||
}
|
||||
|
||||
gitRepo, err := gitmap.Map(workingDir, "")
|
||||
gitRepo, err := gitmap.Map(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ func (h *HugoSites) GetContentPage(filename string) page.Page {
|
||||
|
||||
func (h *HugoSites) loadGitInfo() error {
|
||||
if h.Configs.Base.EnableGitInfo {
|
||||
gi, err := newGitInfo(h.Conf)
|
||||
gi, err := newGitInfo(h.Deps)
|
||||
if err != nil {
|
||||
h.Log.Errorln("Failed to read Git log:", err)
|
||||
} else {
|
||||
|
||||
@@ -203,9 +203,9 @@ title: mybundle-en
|
||||
b.AssertFileExists("public/de/mybundle/pixel.png", true)
|
||||
b.AssertFileExists("public/en/mybundle/pixel.png", true)
|
||||
|
||||
b.AssertFileExists("public/de/mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x2_resize_box_3.png", true)
|
||||
b.AssertFileExists("public/de/mybundle/pixel_hu8581513846771248023.png", true)
|
||||
// failing test below
|
||||
b.AssertFileExists("public/en/mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x2_resize_box_3.png", true)
|
||||
b.AssertFileExists("public/en/mybundle/pixel_hu8581513846771248023.png", true)
|
||||
}
|
||||
|
||||
func TestMultihostResourceOneBaseURLWithSuPath(t *testing.T) {
|
||||
|
||||
@@ -72,22 +72,21 @@ SUNSET2: {{ $resized2.RelPermalink }}/{{ $resized2.Width }}/Lat: {{ $resized2.Ex
|
||||
|
||||
b.Build(BuildCfg{})
|
||||
|
||||
b.AssertFileContent("public/index.html", "SUNSET FOR: en: /bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg/200/Lat: 36.59744166666667")
|
||||
b.AssertFileContent("public/fr/index.html", "SUNSET FOR: fr: /bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg/200/Lat: 36.59744166666667")
|
||||
b.AssertFileContent("public/index.html", " SUNSET2: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg/123/Lat: 36.59744166666667")
|
||||
b.AssertFileContent("public/nn/index.html", " SUNSET2: /images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg/123/Lat: 36.59744166666667")
|
||||
b.AssertFileContent("public/index.html", "SUNSET FOR: en: /bundle/sunset_hu13235715490294913361.jpg/200/Lat: 36.59744166666667")
|
||||
b.AssertFileContent("public/fr/index.html", "SUNSET FOR: fr: /bundle/sunset_hu13235715490294913361.jpg/200/Lat: 36.59744166666667")
|
||||
b.AssertFileContent("public/index.html", " SUNSET2: /images/sunset_hu1573057890424052540.jpg/123/Lat: 36.59744166666667")
|
||||
b.AssertFileContent("public/nn/index.html", " SUNSET2: /images/sunset_hu1573057890424052540.jpg/123/Lat: 36.59744166666667")
|
||||
|
||||
b.AssertImage(200, 200, "public/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg")
|
||||
b.AssertImage(200, 200, "public/bundle/sunset_hu13235715490294913361.jpg")
|
||||
|
||||
// Check the file cache
|
||||
b.AssertImage(200, 200, "resources/_gen/images/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg")
|
||||
b.AssertImage(200, 200, "resources/_gen/images/bundle/sunset_hu13235715490294913361.jpg")
|
||||
|
||||
b.AssertFileContent("resources/_gen/images/bundle/sunset_3166614710256882113.json",
|
||||
"DateTimeDigitized|time.Time", "PENTAX")
|
||||
b.AssertFileContent("resources/_gen/images/bundle/sunset_17710516992648092201.json",
|
||||
"FocalLengthIn35mmFormat|uint16", "PENTAX")
|
||||
|
||||
b.AssertImage(123, 234, "resources/_gen/images/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg")
|
||||
b.AssertFileContent("resources/_gen/images/images/sunset_3166614710256882113.json",
|
||||
"DateTimeDigitized|time.Time", "PENTAX")
|
||||
b.AssertFileContent("resources/_gen/images/images/sunset_17710516992648092201.json",
|
||||
"FocalLengthIn35mmFormat|uint16", "PENTAX")
|
||||
|
||||
b.AssertNoDuplicateWrites()
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gobuffalo/flect"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/markup/converter"
|
||||
xmaps "golang.org/x/exp/maps"
|
||||
@@ -32,6 +31,7 @@ import (
|
||||
"github.com/gohugoio/hugo/source"
|
||||
|
||||
"github.com/gohugoio/hugo/common/constants"
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
@@ -328,7 +328,7 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
|
||||
ps.m.setMetaPostCount++
|
||||
var cascadeHashPre uint64
|
||||
if ps.m.setMetaPostCount > 1 {
|
||||
cascadeHashPre = identity.HashUint64(ps.m.pageConfig.CascadeCompiled)
|
||||
cascadeHashPre = hashing.HashUint64(ps.m.pageConfig.CascadeCompiled)
|
||||
ps.m.pageConfig.CascadeCompiled = xmaps.Clone[map[page.PageMatcher]maps.Params](ps.m.cascadeOriginal)
|
||||
|
||||
}
|
||||
@@ -360,7 +360,7 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
|
||||
}
|
||||
|
||||
if ps.m.setMetaPostCount > 1 {
|
||||
ps.m.setMetaPostCascadeChanged = cascadeHashPre != identity.HashUint64(ps.m.pageConfig.CascadeCompiled)
|
||||
ps.m.setMetaPostCascadeChanged = cascadeHashPre != hashing.HashUint64(ps.m.pageConfig.CascadeCompiled)
|
||||
if !ps.m.setMetaPostCascadeChanged {
|
||||
|
||||
// No changes, restore any value that may be changed by aggregation.
|
||||
|
||||
@@ -23,13 +23,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bep/clocks"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/markup/asciidocext"
|
||||
"github.com/gohugoio/hugo/markup/rst"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
|
||||
@@ -2040,8 +2040,8 @@ title: "p2"
|
||||
|
||||
b.Assert(p1, qt.Not(qt.Equals), p2)
|
||||
|
||||
b.Assert(identity.HashString(p1), qt.Not(qt.Equals), identity.HashString(p2))
|
||||
b.Assert(identity.HashString(sites[0]), qt.Not(qt.Equals), identity.HashString(sites[1]))
|
||||
b.Assert(hashing.HashString(p1), qt.Not(qt.Equals), hashing.HashString(p2))
|
||||
b.Assert(hashing.HashString(sites[0]), qt.Not(qt.Equals), hashing.HashString(sites[1]))
|
||||
}
|
||||
|
||||
// Issue #11243
|
||||
|
||||
@@ -19,12 +19,11 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/gohugoio/hugo/resources/kinds"
|
||||
@@ -701,13 +700,13 @@ bundle min min key: {{ $jsonMinMin.Key }}
|
||||
b.AssertFileContent(index, fmt.Sprintf("data content unmarshaled: v%d", i))
|
||||
b.AssertFileContent(index, fmt.Sprintf("data assets content unmarshaled: v%d", i))
|
||||
|
||||
md5Asset := helpers.MD5String(fmt.Sprintf(`vdata: v%d`, i))
|
||||
md5Asset := hashing.MD5FromStringHexEncoded(fmt.Sprintf(`vdata: v%d`, i))
|
||||
b.AssertFileContent(index, fmt.Sprintf("assets fingerprinted: /data%d/data.%s.yaml", i, md5Asset))
|
||||
|
||||
// The original is not used, make sure it's not published.
|
||||
b.Assert(b.CheckExists(fmt.Sprintf("public/data%d/data.yaml", i)), qt.Equals, false)
|
||||
|
||||
md5Bundle := helpers.MD5String(fmt.Sprintf(`data: v%d`, i))
|
||||
md5Bundle := hashing.MD5FromStringHexEncoded(fmt.Sprintf(`data: v%d`, i))
|
||||
b.AssertFileContent(index, fmt.Sprintf("bundle fingerprinted: /bundle%d/data.%s.yaml", i, md5Bundle))
|
||||
|
||||
b.AssertFileContent(index,
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
@@ -219,7 +220,7 @@ type BuildState struct {
|
||||
}
|
||||
|
||||
func (b *BuildState) hash(v any) uint64 {
|
||||
return identity.HashUint64(v)
|
||||
return hashing.HashUint64(v)
|
||||
}
|
||||
|
||||
func (b *BuildState) checkHasChangedAndSetSourceInfo(changedPath string, v any) bool {
|
||||
|
||||
@@ -119,7 +119,7 @@ docs/p1/sub/mymixcasetext2.txt
|
||||
"RelPermalink: /docs/p1/sub/mymixcasetext2.txt|Name: sub/mymixcasetext2.txt|",
|
||||
"RelPermalink: /mydata.yaml|Name: sub/data1.yaml|Title: Sub data|Params: map[]|",
|
||||
"Featured Image: /a/pixel.png|featured.png|",
|
||||
"Resized Featured Image: /a/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_10x10_resize_box_3.png|10|",
|
||||
"Resized Featured Image: /a/pixel_hu16809842526914527184.png|10|",
|
||||
// Resource from string
|
||||
"RelPermalink: /docs/p1/mytext.txt|Name: textresource|Title: My Text Resource|Params: map[param1:param1v]|",
|
||||
// Dates
|
||||
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss"
|
||||
)
|
||||
|
||||
@@ -106,12 +106,12 @@ FAILED REMOTE ERROR DETAILS CONTENT: {{ with $failedImg.Err }}|{{ . }}|{{ with .
|
||||
b.AssertFileContent("public/index.html",
|
||||
fmt.Sprintf(`
|
||||
SUNSET: /images/sunset.jpg|/images/sunset.a9bf1d944e19c0f382e0d8f51de690f7d0bc8fa97390c4242a86c3e5c0737e71.jpg|900|90587
|
||||
FIT: /images/sunset.jpg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_fit_q75_box.jpg|200
|
||||
FIT: /images/sunset.jpg|/images/sunset_hu15210517121918042184.jpg|200
|
||||
CSS integrity Data first: sha256-od9YaHw8nMOL8mUy97Sy8sKwMV3N4hI3aVmZXATxH+8= /styles.min.a1df58687c3c9cc38bf26532f7b4b2f2c2b0315dcde212376959995c04f11fef.css
|
||||
CSS integrity Data last: /styles2.min.1cfc52986836405d37f9998a63fd6dd8608e8c410e5e3db1daaa30f78bc273ba.css sha256-HPxSmGg2QF03+ZmKY/1t2GCOjEEOXj2x2qow94vCc7o=
|
||||
|
||||
SUNSET REMOTE: /sunset_%[1]s.jpg|/sunset_%[1]s.a9bf1d944e19c0f382e0d8f51de690f7d0bc8fa97390c4242a86c3e5c0737e71.jpg|900|90587
|
||||
FIT REMOTE: /sunset_%[1]s.jpg|/sunset_%[1]s_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_fit_q75_box.jpg|200
|
||||
FIT REMOTE: /sunset_%[1]s.jpg|/sunset_%[1]s_hu15210517121918042184.jpg|200
|
||||
REMOTE NOT FOUND: OK
|
||||
LOCAL NOT FOUND: OK
|
||||
PRINT PROTOCOL ERROR DETAILS: Err: error calling resources.GetRemote: Get "gopher://example.org": unsupported protocol scheme "gopher"||
|
||||
@@ -119,7 +119,7 @@ FAILED REMOTE ERROR DETAILS CONTENT: |failed to fetch remote resource: Not Imple
|
||||
|StatusCode: 501|ContentLength: 16|ContentType: text/plain; charset=utf-8|
|
||||
|
||||
|
||||
`, identity.HashString(ts.URL+"/sunset.jpg", map[string]any{})))
|
||||
`, hashing.HashString(ts.URL+"/sunset.jpg", map[string]any{})))
|
||||
|
||||
b.AssertFileContent("public/styles.min.a1df58687c3c9cc38bf26532f7b4b2f2c2b0315dcde212376959995c04f11fef.css", "body{background-color:#add8e6}")
|
||||
b.AssertFileContent("public//styles2.min.1cfc52986836405d37f9998a63fd6dd8608e8c410e5e3db1daaa30f78bc273ba.css", "body{background-color:orange}")
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Release env.
|
||||
# These will be replaced by script before release.
|
||||
HUGORELEASER_TAG=v0.128.2
|
||||
HUGORELEASER_COMMITISH=de36c1a95d28595d8243fd8b891665b069ed0850
|
||||
HUGORELEASER_TAG=v0.130.0
|
||||
HUGORELEASER_COMMITISH=9b1b11c8a59a900458e9e460f197a44367c022ee
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
// 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 identity
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/mitchellh/hashstructure"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
hash, err := hashstructure.Hash(o, nil)
|
||||
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 IdentityProvider:
|
||||
return t.GetIdentity()
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/compare"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
)
|
||||
|
||||
// The Provider interface defines an interface for measuring metrics.
|
||||
@@ -241,7 +241,7 @@ func howSimilar(a, b any) int {
|
||||
return 90
|
||||
}
|
||||
|
||||
h1, h2 := identity.HashString(a), identity.HashString(b)
|
||||
h1, h2 := hashing.HashString(a), hashing.HashString(b)
|
||||
if h1 == h2 {
|
||||
return 100
|
||||
}
|
||||
|
||||
@@ -29,9 +29,9 @@ import (
|
||||
color_extractor "github.com/marekm4/color-extractor"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/hstrings"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
|
||||
"github.com/disintegration/gift"
|
||||
|
||||
@@ -40,7 +40,6 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/resources/images"
|
||||
|
||||
// Blind import for image.Decode
|
||||
@@ -82,8 +81,9 @@ func (i *imageResource) Exif() *exif.ExifInfo {
|
||||
|
||||
func (i *imageResource) getExif() *exif.ExifInfo {
|
||||
i.metaInit.Do(func() {
|
||||
supportsExif := i.Format == images.JPEG || i.Format == images.TIFF
|
||||
if !supportsExif {
|
||||
mf := i.Format.ToImageMetaImageFormatFormat()
|
||||
if mf == -1 {
|
||||
// No Exif support for this format.
|
||||
return
|
||||
}
|
||||
|
||||
@@ -114,7 +114,8 @@ func (i *imageResource) getExif() *exif.ExifInfo {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
x, err := i.getSpec().imaging.DecodeExif(f)
|
||||
filename := i.getResourcePaths().Path()
|
||||
x, err := i.getSpec().imaging.DecodeExif(filename, mf, f)
|
||||
if err != nil {
|
||||
i.getSpec().Logger.Warnf("Unable to decode Exif metadata from image: %s", i.Key())
|
||||
return nil
|
||||
@@ -272,7 +273,7 @@ func (i *imageResource) Filter(filters ...any) (images.ImageResource, error) {
|
||||
}
|
||||
|
||||
conf.Action = "filter"
|
||||
conf.Key = identity.HashString(gfilters)
|
||||
conf.Key = hashing.HashString(gfilters)
|
||||
conf.TargetFormat = targetFormat
|
||||
if conf.TargetFormat == 0 {
|
||||
conf.TargetFormat = i.Format
|
||||
@@ -471,13 +472,15 @@ func (i *imageResource) clone(img image.Image) *imageResource {
|
||||
}
|
||||
|
||||
func (i *imageResource) getImageMetaCacheTargetPath() string {
|
||||
const imageMetaVersionNumber = 1 // Increment to invalidate the meta cache
|
||||
// Increment to invalidate the meta cache
|
||||
// Last increment: v0.130.0 when change to the new imagemeta library for Exif.
|
||||
const imageMetaVersionNumber = 2
|
||||
|
||||
cfgHash := i.getSpec().imaging.Cfg.SourceHash
|
||||
df := i.getResourcePaths()
|
||||
p1, _ := paths.FileAndExt(df.File)
|
||||
h := i.hash()
|
||||
idStr := identity.HashString(h, i.size(), imageMetaVersionNumber, cfgHash)
|
||||
idStr := hashing.HashString(h, i.size(), imageMetaVersionNumber, cfgHash)
|
||||
df.File = fmt.Sprintf("%s_%s.json", p1, idStr)
|
||||
return df.TargetPath()
|
||||
}
|
||||
@@ -487,36 +490,16 @@ func (i *imageResource) relTargetPathFromConfig(conf images.ImageConfig) interna
|
||||
if conf.TargetFormat != i.Format {
|
||||
p2 = conf.TargetFormat.DefaultExtension()
|
||||
}
|
||||
|
||||
h := i.hash()
|
||||
idStr := fmt.Sprintf("_hu%s_%d", h, i.size())
|
||||
|
||||
// Do not change for no good reason.
|
||||
const md5Threshold = 100
|
||||
|
||||
key := conf.GetKey(i.Format)
|
||||
|
||||
// It is useful to have the key in clear text, but when nesting transforms, it
|
||||
// can easily be too long to read, and maybe even too long
|
||||
// for the different OSes to handle.
|
||||
if len(p1)+len(idStr)+len(p2) > md5Threshold {
|
||||
key = helpers.MD5String(p1 + key + p2)
|
||||
huIdx := strings.Index(p1, "_hu")
|
||||
if huIdx != -1 {
|
||||
p1 = p1[:huIdx]
|
||||
} else {
|
||||
// This started out as a very long file name. Making it even longer
|
||||
// could melt ice in the Arctic.
|
||||
p1 = ""
|
||||
}
|
||||
} else if strings.Contains(p1, idStr) {
|
||||
// On scaling an already scaled image, we get the file info from the original.
|
||||
// Repeating the same info in the filename makes it stuttery for no good reason.
|
||||
idStr = ""
|
||||
const prefix = "_hu"
|
||||
huIdx := strings.LastIndex(p1, prefix)
|
||||
incomingID := "i"
|
||||
if huIdx > -1 {
|
||||
incomingID = p1[huIdx+len(prefix):]
|
||||
p1 = p1[:huIdx]
|
||||
}
|
||||
|
||||
hash := hashing.HashUint64(incomingID, i.hash(), conf.GetKey(i.Format))
|
||||
rp := i.getResourcePaths()
|
||||
rp.File = fmt.Sprintf("%s%s_%s%s", p1, idStr, key, p2)
|
||||
rp.File = fmt.Sprintf("%s%s%d%s", p1, prefix, hash, p2)
|
||||
|
||||
return rp
|
||||
}
|
||||
|
||||
@@ -20,22 +20,28 @@ import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/htesting/hqt"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
)
|
||||
|
||||
func TestImageResizeWebP(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
_, image := fetchImage(c, "sunset.webp")
|
||||
_, image := fetchImage(c, "sunrise.webp")
|
||||
|
||||
c.Assert(image.MediaType(), qt.Equals, media.Builtin.WEBPType)
|
||||
c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.webp")
|
||||
c.Assert(image.RelPermalink(), qt.Equals, "/a/sunrise.webp")
|
||||
c.Assert(image.ResourceType(), qt.Equals, "image")
|
||||
c.Assert(image.Exif(), qt.IsNil)
|
||||
exif := image.Exif()
|
||||
c.Assert(exif, qt.Not(qt.IsNil))
|
||||
c.Assert(exif.Tags["Copyright"], qt.Equals, "Bjørn Erik Pedersen")
|
||||
c.Assert(exif.Lat, hqt.IsSameFloat64, 36.59744166666667)
|
||||
c.Assert(exif.Long, hqt.IsSameFloat64, -4.50846)
|
||||
c.Assert(exif.Date.IsZero(), qt.Equals, false)
|
||||
|
||||
resized, err := image.Resize("123x")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(image.MediaType(), qt.Equals, media.Builtin.WEBPType)
|
||||
c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu36ee0b61ba924719ad36da960c273f96_59826_123x0_resize_q68_h2_linear_2.webp")
|
||||
c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunrise_hu544374262273649331.webp")
|
||||
c.Assert(resized.Width(), qt.Equals, 123)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"image"
|
||||
"image/gif"
|
||||
"io/fs"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -30,17 +29,17 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bep/imagemeta"
|
||||
"github.com/gohugoio/hugo/htesting"
|
||||
"github.com/gohugoio/hugo/resources/images/webp"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/disintegration/gift"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/resources/images"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -67,8 +66,13 @@ var eq = qt.CmpEquals(
|
||||
return m1.Type == m2.Type
|
||||
}),
|
||||
cmp.Comparer(
|
||||
func(v1, v2 *big.Rat) bool {
|
||||
return v1.RatString() == v2.RatString()
|
||||
func(v1, v2 imagemeta.Rat[uint32]) bool {
|
||||
return v1.String() == v2.String()
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
func(v1, v2 imagemeta.Rat[int32]) bool {
|
||||
return v1.String() == v2.String()
|
||||
},
|
||||
),
|
||||
cmp.Comparer(func(v1, v2 time.Time) bool {
|
||||
@@ -119,28 +123,28 @@ func TestImageTransformBasic(t *testing.T) {
|
||||
assertWidthHeight(resizedAndRotated, 125, 200)
|
||||
|
||||
assertWidthHeight(resized, 300, 200)
|
||||
c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x200_resize_q68_linear.jpg")
|
||||
c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu2082030801149749592.jpg")
|
||||
|
||||
fitted, err := resized.Fit("50x50")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fitted.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_625708021e2bb281c9f1002f88e4753f.jpg")
|
||||
c.Assert(fitted.RelPermalink(), qt.Equals, "/a/sunset_hu16263619592447877226.jpg")
|
||||
assertWidthHeight(fitted, 50, 33)
|
||||
|
||||
// Check the MD5 key threshold
|
||||
fittedAgain, _ := fitted.Fit("10x20")
|
||||
fittedAgain, err = fittedAgain.Fit("10x20")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(fittedAgain.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_3f65ba24dc2b7fba0f56d7f104519157.jpg")
|
||||
c.Assert(fittedAgain.RelPermalink(), qt.Equals, "/a/sunset_hu847809310637164306.jpg")
|
||||
assertWidthHeight(fittedAgain, 10, 7)
|
||||
|
||||
filled, err := image.Fill("200x100 bottomLeft")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(filled.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_fill_q68_linear_bottomleft.jpg")
|
||||
c.Assert(filled.RelPermalink(), qt.Equals, "/a/sunset_hu18289448341423092707.jpg")
|
||||
assertWidthHeight(filled, 200, 100)
|
||||
|
||||
smart, err := image.Fill("200x100 smart")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(smart.RelPermalink(), qt.Equals, fmt.Sprintf("/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x100_fill_q68_linear_smart%d.jpg", 1))
|
||||
c.Assert(smart.RelPermalink(), qt.Equals, "/a/sunset_hu11649371610839769766.jpg")
|
||||
assertWidthHeight(smart, 200, 100)
|
||||
|
||||
// Check cache
|
||||
@@ -150,12 +154,12 @@ func TestImageTransformBasic(t *testing.T) {
|
||||
|
||||
cropped, err := image.Crop("300x300 topRight")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(cropped.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_300x300_crop_q68_linear_topright.jpg")
|
||||
c.Assert(cropped.RelPermalink(), qt.Equals, "/a/sunset_hu2242042514052853140.jpg")
|
||||
assertWidthHeight(cropped, 300, 300)
|
||||
|
||||
smartcropped, err := image.Crop("200x200 smart")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(smartcropped.RelPermalink(), qt.Equals, fmt.Sprintf("/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_crop_q68_linear_smart%d.jpg", 1))
|
||||
c.Assert(smartcropped.RelPermalink(), qt.Equals, "/a/sunset_hu12983255101170993571.jpg")
|
||||
assertWidthHeight(smartcropped, 200, 200)
|
||||
|
||||
// Check cache
|
||||
@@ -222,7 +226,7 @@ func TestImageTransformFormat(t *testing.T) {
|
||||
|
||||
imagePng, err := image.Resize("450x png")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(imagePng.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_450x0_resize_linear.png")
|
||||
c.Assert(imagePng.RelPermalink(), qt.Equals, "/a/sunset_hu11737890885216583918.png")
|
||||
c.Assert(imagePng.ResourceType(), qt.Equals, "image")
|
||||
assertExtWidthHeight(imagePng, ".png", 450, 281)
|
||||
c.Assert(imagePng.Name(), qt.Equals, "sunset.jpg")
|
||||
@@ -230,7 +234,7 @@ func TestImageTransformFormat(t *testing.T) {
|
||||
|
||||
imageGif, err := image.Resize("225x gif")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(imageGif.RelPermalink(), qt.Equals, "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_225x0_resize_linear.gif")
|
||||
c.Assert(imageGif.RelPermalink(), qt.Equals, "/a/sunset_hu1431827106749674475.gif")
|
||||
c.Assert(imageGif.ResourceType(), qt.Equals, "image")
|
||||
assertExtWidthHeight(imageGif, ".gif", 225, 141)
|
||||
c.Assert(imageGif.Name(), qt.Equals, "sunset.jpg")
|
||||
@@ -253,7 +257,7 @@ func TestImagePermalinkPublishOrder(t *testing.T) {
|
||||
}()
|
||||
|
||||
check1 := func(img images.ImageResource) {
|
||||
resizedLink := "/a/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_100x50_resize_q75_box.jpg"
|
||||
resizedLink := "/a/sunset_hu7919355342577096259.jpg"
|
||||
c.Assert(img.RelPermalink(), qt.Equals, resizedLink)
|
||||
assertImageFile(c, spec.PublishFs, resizedLink, 100, 50)
|
||||
}
|
||||
@@ -294,12 +298,12 @@ func TestImageBugs(t *testing.T) {
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(resized, qt.Not(qt.IsNil))
|
||||
c.Assert(resized.Width(), qt.Equals, 200)
|
||||
c.Assert(resized.RelPermalink(), qt.Equals, "/a/_hu59e56ffff1bc1d8d122b1403d34e039f_90587_65b757a6e14debeae720fe8831f0a9bc.jpg")
|
||||
c.Assert(resized.RelPermalink(), qt.Equals, "/a/1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph_hu9514381480012510326.jpg")
|
||||
resized, err = resized.Resize("100x")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(resized, qt.Not(qt.IsNil))
|
||||
c.Assert(resized.Width(), qt.Equals, 100)
|
||||
c.Assert(resized.RelPermalink(), qt.Equals, "/a/_hu59e56ffff1bc1d8d122b1403d34e039f_90587_c876768085288f41211f768147ba2647.jpg")
|
||||
c.Assert(resized.RelPermalink(), qt.Equals, "/a/1234567890qwertyuiopasdfghjklzxcvbnm5to6eeeeee7via8eleph_hu1776700126481066216.jpg")
|
||||
})
|
||||
|
||||
// Issue #6137
|
||||
@@ -392,12 +396,12 @@ func TestImageResize8BitPNG(t *testing.T) {
|
||||
c.Assert(image.MediaType().Type, qt.Equals, "image/png")
|
||||
c.Assert(image.RelPermalink(), qt.Equals, "/a/gohugoio.png")
|
||||
c.Assert(image.ResourceType(), qt.Equals, "image")
|
||||
c.Assert(image.Exif(), qt.IsNil)
|
||||
c.Assert(image.Exif(), qt.IsNotNil)
|
||||
|
||||
resized, err := image.Resize("800x")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(resized.MediaType().Type, qt.Equals, "image/png")
|
||||
c.Assert(resized.RelPermalink(), qt.Equals, "/a/gohugoio_hu0e1b9e4a4be4d6f86c7b37b9ccce3fbc_73886_800x0_resize_linear_3.png")
|
||||
c.Assert(resized.RelPermalink(), qt.Equals, "/a/gohugoio_hu8582372628235034388.png")
|
||||
c.Assert(resized.Width(), qt.Equals, 800)
|
||||
}
|
||||
|
||||
@@ -443,6 +447,7 @@ func TestImageExif(t *testing.T) {
|
||||
c.Assert(lensModel, qt.Equals, "smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM")
|
||||
resized, _ := image.Resize("300x200")
|
||||
x2 := resized.Exif()
|
||||
|
||||
c.Assert(x2, eq, x)
|
||||
}
|
||||
|
||||
@@ -809,10 +814,10 @@ func assetGoldenDirs(c *qt.C, dir1, dir2 string) {
|
||||
|
||||
if !goldenEqual(nrgba1, nrgba2) {
|
||||
switch fi1.Name() {
|
||||
case "gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_73c19c5f80881858a85aa23cd0ca400d.png",
|
||||
"gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_ae631e5252bb5d7b92bc766ad1a89069.png",
|
||||
"gohugoio8_hu7f72c00afdf7634587afaa5eff2a25b2_73538_d1bbfa2629bffb90118cacce3fcfb924.png",
|
||||
"giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_200x0_resize_box_1.gif":
|
||||
case "giphy_hu13007323561585908901.gif",
|
||||
"gohugoio8_hu12690451569630232821.png",
|
||||
"gohugoio8_hu1619987041333606118.png",
|
||||
"gohugoio8_hu18164141965527013334.png":
|
||||
c.Log("expectedly differs from golden due to dithering:", fi1.Name())
|
||||
default:
|
||||
c.Errorf("resulting image differs from golden: %s", fi1.Name())
|
||||
@@ -829,9 +834,9 @@ func assetGoldenDirs(c *qt.C, dir1, dir2 string) {
|
||||
_, err = f2.Seek(0, 0)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
hash1, err := helpers.MD5FromReader(f1)
|
||||
hash1, _, err := hashing.XXHashFromReader(f1)
|
||||
c.Assert(err, qt.IsNil)
|
||||
hash2, err := helpers.MD5FromReader(f2)
|
||||
hash2, _, err := hashing.XXHashFromReader(f2)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
c.Assert(hash1, qt.Equals, hash2)
|
||||
|
||||
@@ -14,25 +14,18 @@
|
||||
package exif
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/bep/imagemeta"
|
||||
"github.com/bep/logg"
|
||||
"github.com/bep/tmc"
|
||||
|
||||
_exif "github.com/rwcarlsen/goexif/exif"
|
||||
"github.com/rwcarlsen/goexif/tiff"
|
||||
)
|
||||
|
||||
const exifTimeLayout = "2006:01:02 15:04:05"
|
||||
|
||||
// ExifInfo holds the decoded Exif data for an Image.
|
||||
type ExifInfo struct {
|
||||
// GPS latitude in degrees.
|
||||
@@ -53,6 +46,15 @@ type Decoder struct {
|
||||
excludeFieldsrRe *regexp.Regexp
|
||||
noDate bool
|
||||
noLatLong bool
|
||||
warnl logg.LevelLogger
|
||||
}
|
||||
|
||||
func (d *Decoder) shouldInclude(s string) bool {
|
||||
return (d.includeFieldsRe == nil || d.includeFieldsRe.MatchString(s))
|
||||
}
|
||||
|
||||
func (d *Decoder) shouldExclude(s string) bool {
|
||||
return d.excludeFieldsrRe != nil && d.excludeFieldsrRe.MatchString(s)
|
||||
}
|
||||
|
||||
func IncludeFields(expression string) func(*Decoder) error {
|
||||
@@ -91,6 +93,13 @@ func WithDateDisabled(disabled bool) func(*Decoder) error {
|
||||
}
|
||||
}
|
||||
|
||||
func WithWarnLogger(warnl logg.LevelLogger) func(*Decoder) error {
|
||||
return func(d *Decoder) error {
|
||||
d.warnl = warnl
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func compileRegexp(expression string) (*regexp.Regexp, error) {
|
||||
expression = strings.TrimSpace(expression)
|
||||
if expression == "" {
|
||||
@@ -115,148 +124,222 @@ func NewDecoder(options ...func(*Decoder) error) (*Decoder, error) {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) Decode(r io.Reader) (ex *ExifInfo, err error) {
|
||||
var (
|
||||
isTimeTag = func(s string) bool {
|
||||
return strings.Contains(s, "Time")
|
||||
}
|
||||
isGPSTag = func(s string) bool {
|
||||
return strings.HasPrefix(s, "GPS")
|
||||
}
|
||||
)
|
||||
|
||||
// Filename is only used for logging.
|
||||
func (d *Decoder) Decode(filename string, format imagemeta.ImageFormat, r io.Reader) (ex *ExifInfo, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("exif failed: %v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
var x *_exif.Exif
|
||||
x, err = _exif.Decode(r)
|
||||
if err != nil {
|
||||
if err.Error() == "EOF" {
|
||||
// Found no Exif
|
||||
return nil, nil
|
||||
}
|
||||
return
|
||||
var tagInfos imagemeta.Tags
|
||||
handleTag := func(ti imagemeta.TagInfo) error {
|
||||
tagInfos.Add(ti)
|
||||
return nil
|
||||
}
|
||||
|
||||
shouldInclude := func(ti imagemeta.TagInfo) bool {
|
||||
if ti.Source == imagemeta.EXIF {
|
||||
if !d.noDate {
|
||||
// We need the time tags to calculate the date.
|
||||
if isTimeTag(ti.Tag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if !d.noLatLong {
|
||||
// We need to GPS tags to calculate the lat/long.
|
||||
if isGPSTag(ti.Tag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(ti.Namespace, "IFD0") {
|
||||
// Drop thumbnail tags.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if d.shouldExclude(ti.Tag) {
|
||||
return false
|
||||
}
|
||||
|
||||
return d.shouldInclude(ti.Tag)
|
||||
}
|
||||
|
||||
var warnf func(string, ...any)
|
||||
if d.warnl != nil {
|
||||
// There should be very little warnings (fingers crossed!),
|
||||
// but this will typically be unrecognized formats.
|
||||
// To be able to possibly get rid of these warnings,
|
||||
// we need to know what images are causing them.
|
||||
warnf = func(format string, args ...any) {
|
||||
format = fmt.Sprintf("%q: %s: ", filename, format)
|
||||
d.warnl.Logf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
err = imagemeta.Decode(
|
||||
imagemeta.Options{
|
||||
R: r.(io.ReadSeeker),
|
||||
ImageFormat: format,
|
||||
ShouldHandleTag: shouldInclude,
|
||||
HandleTag: handleTag,
|
||||
Sources: imagemeta.EXIF, // For now. TODO(bep)
|
||||
Warnf: warnf,
|
||||
},
|
||||
)
|
||||
|
||||
var tm time.Time
|
||||
var lat, long float64
|
||||
|
||||
if !d.noDate {
|
||||
tm, _ = x.DateTime()
|
||||
tm, _ = tagInfos.GetDateTime()
|
||||
}
|
||||
|
||||
if !d.noLatLong {
|
||||
lat, long, _ = x.LatLong()
|
||||
if math.IsNaN(lat) {
|
||||
lat = 0
|
||||
}
|
||||
if math.IsNaN(long) {
|
||||
long = 0
|
||||
}
|
||||
lat, long, _ = tagInfos.GetLatLong()
|
||||
}
|
||||
|
||||
walker := &exifWalker{x: x, vals: make(map[string]any), includeMatcher: d.includeFieldsRe, excludeMatcher: d.excludeFieldsrRe}
|
||||
if err = x.Walk(walker); err != nil {
|
||||
return
|
||||
tags := make(map[string]any)
|
||||
for k, v := range tagInfos.All() {
|
||||
if d.shouldExclude(k) {
|
||||
continue
|
||||
}
|
||||
if !d.shouldInclude(k) {
|
||||
continue
|
||||
}
|
||||
tags[k] = v.Value
|
||||
}
|
||||
|
||||
ex = &ExifInfo{Lat: lat, Long: long, Date: tm, Tags: walker.vals}
|
||||
ex = &ExifInfo{Lat: lat, Long: long, Date: tm, Tags: tags}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func decodeTag(x *_exif.Exif, f _exif.FieldName, t *tiff.Tag) (any, error) {
|
||||
switch t.Format() {
|
||||
case tiff.StringVal, tiff.UndefVal:
|
||||
s := nullString(t.Val)
|
||||
if strings.Contains(string(f), "DateTime") {
|
||||
if d, err := tryParseDate(x, s); err == nil {
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
case tiff.OtherVal:
|
||||
return "unknown", nil
|
||||
}
|
||||
|
||||
var rv []any
|
||||
|
||||
for i := 0; i < int(t.Count); i++ {
|
||||
switch t.Format() {
|
||||
case tiff.RatVal:
|
||||
n, d, _ := t.Rat2(i)
|
||||
rat := big.NewRat(n, d)
|
||||
// if t is int or t > 1, use float64
|
||||
if rat.IsInt() || rat.Cmp(big.NewRat(1, 1)) == 1 {
|
||||
f, _ := rat.Float64()
|
||||
rv = append(rv, f)
|
||||
} else {
|
||||
rv = append(rv, rat)
|
||||
}
|
||||
|
||||
case tiff.FloatVal:
|
||||
v, _ := t.Float(i)
|
||||
rv = append(rv, v)
|
||||
case tiff.IntVal:
|
||||
v, _ := t.Int(i)
|
||||
rv = append(rv, v)
|
||||
}
|
||||
}
|
||||
|
||||
if t.Count == 1 {
|
||||
if len(rv) == 1 {
|
||||
return rv[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
// Code borrowed from exif.DateTime and adjusted.
|
||||
func tryParseDate(x *_exif.Exif, s string) (time.Time, error) {
|
||||
dateStr := strings.TrimRight(s, "\x00")
|
||||
// TODO(bep): look for timezone offset, GPS time, etc.
|
||||
timeZone := time.Local
|
||||
if tz, _ := x.TimeZone(); tz != nil {
|
||||
timeZone = tz
|
||||
}
|
||||
return time.ParseInLocation(exifTimeLayout, dateStr, timeZone)
|
||||
}
|
||||
|
||||
type exifWalker struct {
|
||||
x *_exif.Exif
|
||||
vals map[string]any
|
||||
includeMatcher *regexp.Regexp
|
||||
excludeMatcher *regexp.Regexp
|
||||
}
|
||||
|
||||
func (e *exifWalker) Walk(f _exif.FieldName, tag *tiff.Tag) error {
|
||||
name := string(f)
|
||||
if e.excludeMatcher != nil && e.excludeMatcher.MatchString(name) {
|
||||
return nil
|
||||
}
|
||||
if e.includeMatcher != nil && !e.includeMatcher.MatchString(name) {
|
||||
return nil
|
||||
}
|
||||
val, err := decodeTag(e.x, f, tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e.vals[name] = val
|
||||
return nil
|
||||
}
|
||||
|
||||
func nullString(in []byte) string {
|
||||
var rv bytes.Buffer
|
||||
for len(in) > 0 {
|
||||
r, size := utf8.DecodeRune(in)
|
||||
if unicode.IsGraphic(r) {
|
||||
rv.WriteRune(r)
|
||||
}
|
||||
in = in[size:]
|
||||
}
|
||||
return rv.String()
|
||||
}
|
||||
|
||||
var tcodec *tmc.Codec
|
||||
|
||||
func init() {
|
||||
newIntadapter := func(target any) tmc.Adapter {
|
||||
var bitSize int
|
||||
var isSigned bool
|
||||
|
||||
switch target.(type) {
|
||||
case int:
|
||||
bitSize = 0
|
||||
isSigned = true
|
||||
case int8:
|
||||
bitSize = 8
|
||||
isSigned = true
|
||||
case int16:
|
||||
bitSize = 16
|
||||
isSigned = true
|
||||
case int32:
|
||||
bitSize = 32
|
||||
isSigned = true
|
||||
case int64:
|
||||
bitSize = 64
|
||||
isSigned = true
|
||||
case uint:
|
||||
bitSize = 0
|
||||
case uint8:
|
||||
bitSize = 8
|
||||
case uint16:
|
||||
bitSize = 16
|
||||
case uint32:
|
||||
bitSize = 32
|
||||
case uint64:
|
||||
bitSize = 64
|
||||
}
|
||||
|
||||
intFromString := func(s string) (any, error) {
|
||||
if bitSize == 0 {
|
||||
return strconv.Atoi(s)
|
||||
}
|
||||
|
||||
var v any
|
||||
var err error
|
||||
|
||||
if isSigned {
|
||||
v, err = strconv.ParseInt(s, 10, bitSize)
|
||||
} else {
|
||||
v, err = strconv.ParseUint(s, 10, bitSize)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if isSigned {
|
||||
i := v.(int64)
|
||||
switch target.(type) {
|
||||
case int:
|
||||
return int(i), nil
|
||||
case int8:
|
||||
return int8(i), nil
|
||||
case int16:
|
||||
return int16(i), nil
|
||||
case int32:
|
||||
return int32(i), nil
|
||||
case int64:
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
|
||||
i := v.(uint64)
|
||||
switch target.(type) {
|
||||
case uint:
|
||||
return uint(i), nil
|
||||
case uint8:
|
||||
return uint8(i), nil
|
||||
case uint16:
|
||||
return uint16(i), nil
|
||||
case uint32:
|
||||
return uint32(i), nil
|
||||
case uint64:
|
||||
return i, nil
|
||||
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unsupported target type %T", target)
|
||||
}
|
||||
|
||||
intToString := func(v any) (string, error) {
|
||||
return fmt.Sprintf("%d", v), nil
|
||||
}
|
||||
|
||||
return tmc.NewAdapter(target, intFromString, intToString)
|
||||
}
|
||||
|
||||
ru, _ := imagemeta.NewRat[uint32](1, 2)
|
||||
ri, _ := imagemeta.NewRat[int32](1, 2)
|
||||
tmcAdapters := []tmc.Adapter{
|
||||
tmc.NewAdapter(ru, nil, nil),
|
||||
tmc.NewAdapter(ri, nil, nil),
|
||||
newIntadapter(int(1)),
|
||||
newIntadapter(int8(1)),
|
||||
newIntadapter(int16(1)),
|
||||
newIntadapter(int32(1)),
|
||||
newIntadapter(int64(1)),
|
||||
newIntadapter(uint(1)),
|
||||
newIntadapter(uint8(1)),
|
||||
newIntadapter(uint16(1)),
|
||||
newIntadapter(uint32(1)),
|
||||
newIntadapter(uint64(1)),
|
||||
}
|
||||
|
||||
tmcAdapters = append(tmc.DefaultTypeAdapters, tmcAdapters...)
|
||||
|
||||
var err error
|
||||
tcodec, err = tmc.New()
|
||||
tcodec, err = tmc.New(tmc.WithTypeAdapters(tmcAdapters))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -15,13 +15,12 @@ package exif
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/htesting/hqt"
|
||||
"github.com/bep/imagemeta"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
@@ -35,11 +34,12 @@ func TestExif(t *testing.T) {
|
||||
|
||||
d, err := NewDecoder(IncludeFields("Lens|Date"))
|
||||
c.Assert(err, qt.IsNil)
|
||||
x, err := d.Decode(f)
|
||||
x, err := d.Decode("", imagemeta.JPEG, f)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(x.Date.Format("2006-01-02"), qt.Equals, "2017-10-27")
|
||||
|
||||
// Malaga: https://goo.gl/taazZy
|
||||
|
||||
c.Assert(x.Lat, qt.Equals, float64(36.59744166666667))
|
||||
c.Assert(x.Long, qt.Equals, float64(-4.50846))
|
||||
|
||||
@@ -49,9 +49,9 @@ func TestExif(t *testing.T) {
|
||||
c.Assert(ok, qt.Equals, true)
|
||||
c.Assert(lensModel, qt.Equals, "smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM")
|
||||
|
||||
v, found = x.Tags["DateTime"]
|
||||
v, found = x.Tags["ModifyDate"]
|
||||
c.Assert(found, qt.Equals, true)
|
||||
c.Assert(v, hqt.IsSameType, time.Time{})
|
||||
c.Assert(v, qt.Equals, "2017:11:23 09:56:54")
|
||||
|
||||
// Verify that it survives a round-trip to JSON and back.
|
||||
data, err := json.Marshal(x)
|
||||
@@ -72,8 +72,8 @@ func TestExifPNG(t *testing.T) {
|
||||
|
||||
d, err := NewDecoder()
|
||||
c.Assert(err, qt.IsNil)
|
||||
_, err = d.Decode(f)
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
_, err = d.Decode("", imagemeta.PNG, f)
|
||||
c.Assert(err, qt.IsNil)
|
||||
}
|
||||
|
||||
func TestIssue8079(t *testing.T) {
|
||||
@@ -85,28 +85,11 @@ func TestIssue8079(t *testing.T) {
|
||||
|
||||
d, err := NewDecoder()
|
||||
c.Assert(err, qt.IsNil)
|
||||
x, err := d.Decode(f)
|
||||
x, err := d.Decode("", imagemeta.JPEG, f)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(x.Tags["ImageDescription"], qt.Equals, "Città del Vaticano #nanoblock #vatican #vaticancity")
|
||||
}
|
||||
|
||||
func TestNullString(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
expect string
|
||||
}{
|
||||
{"foo", "foo"},
|
||||
{"\x20", "\x20"},
|
||||
{"\xc4\x81", "\xc4\x81"}, // \u0101
|
||||
{"\u0160", "\u0160"}, // non-breaking space
|
||||
} {
|
||||
res := nullString([]byte(test.in))
|
||||
c.Assert(res, qt.Equals, test.expect)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDecodeExif(b *testing.B) {
|
||||
c := qt.New(b)
|
||||
f, err := os.Open(filepath.FromSlash("../../testdata/sunset.jpg"))
|
||||
@@ -118,7 +101,7 @@ func BenchmarkDecodeExif(b *testing.B) {
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err = d.Decode(f)
|
||||
_, err = d.Decode("", imagemeta.JPEG, f)
|
||||
c.Assert(err, qt.IsNil)
|
||||
f.Seek(0, 0)
|
||||
}
|
||||
@@ -126,8 +109,13 @@ func BenchmarkDecodeExif(b *testing.B) {
|
||||
|
||||
var eq = qt.CmpEquals(
|
||||
cmp.Comparer(
|
||||
func(v1, v2 *big.Rat) bool {
|
||||
return v1.RatString() == v2.RatString()
|
||||
func(v1, v2 imagemeta.Rat[uint32]) bool {
|
||||
return v1.String() == v2.String()
|
||||
},
|
||||
),
|
||||
cmp.Comparer(
|
||||
func(v1, v2 imagemeta.Rat[int32]) bool {
|
||||
return v1.String() == v2.String()
|
||||
},
|
||||
),
|
||||
cmp.Comparer(func(v1, v2 time.Time) bool {
|
||||
@@ -138,14 +126,15 @@ var eq = qt.CmpEquals(
|
||||
func TestIssue10738(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
testFunc := func(path, include string) any {
|
||||
testFunc := func(c *qt.C, path, include string) any {
|
||||
c.Helper()
|
||||
f, err := os.Open(filepath.FromSlash(path))
|
||||
c.Assert(err, qt.IsNil)
|
||||
defer f.Close()
|
||||
|
||||
d, err := NewDecoder(IncludeFields(include))
|
||||
c.Assert(err, qt.IsNil)
|
||||
x, err := d.Decode(f)
|
||||
x, err := d.Decode("", imagemeta.JPEG, f)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
// Verify that it survives a round-trip to JSON and back.
|
||||
@@ -194,7 +183,7 @@ func TestIssue10738(t *testing.T) {
|
||||
include: "Lens|Date|ExposureTime",
|
||||
}, want{
|
||||
10,
|
||||
0,
|
||||
1,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -221,7 +210,7 @@ func TestIssue10738(t *testing.T) {
|
||||
include: "Lens|Date|ExposureTime",
|
||||
}, want{
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -266,7 +255,7 @@ func TestIssue10738(t *testing.T) {
|
||||
include: "Lens|Date|ExposureTime",
|
||||
}, want{
|
||||
30,
|
||||
0,
|
||||
1,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -293,19 +282,21 @@ func TestIssue10738(t *testing.T) {
|
||||
include: "Lens|Date|ExposureTime",
|
||||
}, want{
|
||||
4,
|
||||
0,
|
||||
1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
c.Run(tt.name, func(c *qt.C) {
|
||||
got := testFunc(tt.args.path, tt.args.include)
|
||||
got := testFunc(c, tt.args.path, tt.args.include)
|
||||
switch v := got.(type) {
|
||||
case float64:
|
||||
c.Assert(v, qt.Equals, float64(tt.want.vN))
|
||||
case *big.Rat:
|
||||
c.Assert(v, eq, big.NewRat(tt.want.vN, tt.want.vD))
|
||||
case imagemeta.Rat[uint32]:
|
||||
r, err := imagemeta.NewRat[uint32](uint32(tt.want.vN), uint32(tt.want.vD))
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(v, eq, r)
|
||||
default:
|
||||
c.Fatalf("unexpected type: %T", got)
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
)
|
||||
|
||||
func TestFilterHash(t *testing.T) {
|
||||
@@ -25,8 +25,8 @@ func TestFilterHash(t *testing.T) {
|
||||
|
||||
f := &Filters{}
|
||||
|
||||
c.Assert(identity.HashString(f.Grayscale()), qt.Equals, identity.HashString(f.Grayscale()))
|
||||
c.Assert(identity.HashString(f.Grayscale()), qt.Not(qt.Equals), identity.HashString(f.Invert()))
|
||||
c.Assert(identity.HashString(f.Gamma(32)), qt.Not(qt.Equals), identity.HashString(f.Gamma(33)))
|
||||
c.Assert(identity.HashString(f.Gamma(32)), qt.Equals, identity.HashString(f.Gamma(32)))
|
||||
c.Assert(hashing.HashString(f.Grayscale()), qt.Equals, hashing.HashString(f.Grayscale()))
|
||||
c.Assert(hashing.HashString(f.Grayscale()), qt.Not(qt.Equals), hashing.HashString(f.Invert()))
|
||||
c.Assert(hashing.HashString(f.Gamma(32)), qt.Not(qt.Equals), hashing.HashString(f.Gamma(33)))
|
||||
c.Assert(hashing.HashString(f.Gamma(32)), qt.Equals, hashing.HashString(f.Gamma(32)))
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/bep/gowebp/libwebp/webpoptions"
|
||||
"github.com/bep/imagemeta"
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/resources/images/webp"
|
||||
|
||||
@@ -174,13 +176,14 @@ func (i *Image) initConfig() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewImageProcessor(cfg *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal]) (*ImageProcessor, error) {
|
||||
func NewImageProcessor(warnl logg.LevelLogger, cfg *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal]) (*ImageProcessor, error) {
|
||||
e := cfg.Config.Imaging.Exif
|
||||
exifDecoder, err := exif.NewDecoder(
|
||||
exif.WithDateDisabled(e.DisableDate),
|
||||
exif.WithLatLongDisabled(e.DisableLatLong),
|
||||
exif.ExcludeFields(e.ExcludeFields),
|
||||
exif.IncludeFields(e.IncludeFields),
|
||||
exif.WithWarnLogger(warnl),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -197,8 +200,9 @@ type ImageProcessor struct {
|
||||
exifDecoder *exif.Decoder
|
||||
}
|
||||
|
||||
func (p *ImageProcessor) DecodeExif(r io.Reader) (*exif.ExifInfo, error) {
|
||||
return p.exifDecoder.Decode(r)
|
||||
// Filename is only used for logging.
|
||||
func (p *ImageProcessor) DecodeExif(filename string, format imagemeta.ImageFormat, r io.Reader) (*exif.ExifInfo, error) {
|
||||
return p.exifDecoder.Decode(filename, format, r)
|
||||
}
|
||||
|
||||
func (p *ImageProcessor) FiltersFromConfig(src image.Image, conf ImageConfig) ([]gift.Filter, error) {
|
||||
@@ -353,6 +357,21 @@ const (
|
||||
WEBP
|
||||
)
|
||||
|
||||
func (f Format) ToImageMetaImageFormatFormat() imagemeta.ImageFormat {
|
||||
switch f {
|
||||
case JPEG:
|
||||
return imagemeta.JPEG
|
||||
case PNG:
|
||||
return imagemeta.PNG
|
||||
case TIFF:
|
||||
return imagemeta.TIFF
|
||||
case WEBP:
|
||||
return imagemeta.WebP
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// RequiresDefaultQuality returns if the default quality needs to be applied to
|
||||
// images of this format.
|
||||
func (f Format) RequiresDefaultQuality() bool {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
package internal
|
||||
|
||||
import "github.com/gohugoio/hugo/identity"
|
||||
import "github.com/gohugoio/hugo/common/hashing"
|
||||
|
||||
// ResourceTransformationKey are provided by the different transformation implementations.
|
||||
// It identifies the transformation (name) and its configuration (elements).
|
||||
@@ -38,5 +38,5 @@ func (k ResourceTransformationKey) Value() string {
|
||||
return k.Name
|
||||
}
|
||||
|
||||
return k.Name + "_" + identity.HashString(k.elements...)
|
||||
return k.Name + "_" + hashing.HashString(k.elements...)
|
||||
}
|
||||
|
||||
@@ -32,5 +32,5 @@ func TestResourceTransformationKey(t *testing.T) {
|
||||
key := NewResourceTransformationKey("testing",
|
||||
testStruct{Name: "test", V1: int64(10), V2: int32(20), V3: 30, V4: uint64(40)})
|
||||
c := qt.New(t)
|
||||
c.Assert(key.Value(), qt.Equals, "testing_518996646957295636")
|
||||
c.Assert(key.Value(), qt.Equals, "testing_4231238781487357822")
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/resources/internal"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
@@ -307,7 +308,7 @@ type fileInfo interface {
|
||||
}
|
||||
|
||||
type hashProvider interface {
|
||||
hash() string
|
||||
hash() uint64
|
||||
}
|
||||
|
||||
var _ resource.StaleInfo = (*StaleValue[any])(nil)
|
||||
@@ -403,7 +404,7 @@ func (l *genericResource) size() int64 {
|
||||
return l.h.size
|
||||
}
|
||||
|
||||
func (l *genericResource) hash() string {
|
||||
func (l *genericResource) hash() uint64 {
|
||||
if err := l.h.init(l); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -628,7 +629,7 @@ type targetPather interface {
|
||||
}
|
||||
|
||||
type resourceHash struct {
|
||||
value string
|
||||
value uint64
|
||||
size int64
|
||||
initOnce sync.Once
|
||||
}
|
||||
@@ -636,7 +637,7 @@ type resourceHash struct {
|
||||
func (r *resourceHash) init(l hugio.ReadSeekCloserProvider) error {
|
||||
var initErr error
|
||||
r.initOnce.Do(func() {
|
||||
var hash string
|
||||
var hash uint64
|
||||
var size int64
|
||||
f, err := l.ReadSeekCloser()
|
||||
if err != nil {
|
||||
@@ -644,7 +645,7 @@ func (r *resourceHash) init(l hugio.ReadSeekCloserProvider) error {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
hash, size, err = helpers.MD5FromReaderFast(f)
|
||||
hash, size, err = hashImage(f)
|
||||
if err != nil {
|
||||
initErr = fmt.Errorf("failed to calculate hash: %w", err)
|
||||
return
|
||||
@@ -655,3 +656,7 @@ func (r *resourceHash) init(l hugio.ReadSeekCloserProvider) error {
|
||||
|
||||
return initErr
|
||||
}
|
||||
|
||||
func hashImage(r io.ReadSeeker) (uint64, int64, error) {
|
||||
return hashing.XXHashFromReader(r)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/httpcache"
|
||||
hhttpcache "github.com/gohugoio/hugo/cache/httpcache"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs/glob"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
|
||||
@@ -34,6 +33,7 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/cache/dynacache"
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/hcontext"
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/common/tasks"
|
||||
@@ -226,7 +226,7 @@ func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource)
|
||||
// TODO(bep) see #10912; we currently emit a warning for this config scenario.
|
||||
func (c *Client) FromString(targetPath, content string) (resource.Resource, error) {
|
||||
targetPath = path.Clean(targetPath)
|
||||
key := dynacache.CleanKey(targetPath) + helpers.MD5String(content)
|
||||
key := dynacache.CleanKey(targetPath) + hashing.MD5FromStringHexEncoded(content)
|
||||
r, err := c.rs.ResourceCache.GetOrCreate(key, func() (resource.Resource, error) {
|
||||
return c.rs.NewResource(
|
||||
resources.ResourceSourceDescriptor{
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
gmaps "maps"
|
||||
|
||||
"github.com/gohugoio/httpcache"
|
||||
"github.com/gohugoio/hugo/common/hashing"
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
@@ -310,10 +311,10 @@ func (c *Client) validateFromRemoteArgs(uri string, options fromRemoteOptions) e
|
||||
func remoteResourceKeys(uri string, optionsm map[string]any) (string, string) {
|
||||
var userKey string
|
||||
if key, k, found := maps.LookupEqualFold(optionsm, "key"); found {
|
||||
userKey = identity.HashString(key)
|
||||
userKey = hashing.HashString(key)
|
||||
delete(optionsm, k)
|
||||
}
|
||||
optionsKey := identity.HashString(uri, optionsm)
|
||||
optionsKey := hashing.HashString(uri, optionsm)
|
||||
if userKey == "" {
|
||||
userKey = optionsKey
|
||||
}
|
||||
|
||||
@@ -121,15 +121,16 @@ func TestRemoteResourceKeys(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
check := func(uri string, optionsm map[string]any, expect1, expect2 string) {
|
||||
c.Helper()
|
||||
got1, got2 := remoteResourceKeys(uri, optionsm)
|
||||
c.Assert(got1, qt.Equals, expect1)
|
||||
c.Assert(got2, qt.Equals, expect2)
|
||||
}
|
||||
|
||||
check("foo", nil, "5917621528921068675", "5917621528921068675")
|
||||
check("foo", map[string]any{"bar": "baz"}, "7294498335241413323", "7294498335241413323")
|
||||
check("foo", map[string]any{"key": "1234", "bar": "baz"}, "14904296279238663669", "7294498335241413323")
|
||||
check("foo", map[string]any{"key": "12345", "bar": "baz"}, "12191037851845371770", "7294498335241413323")
|
||||
check("asdf", map[string]any{"key": "1234", "bar": "asdf"}, "14904296279238663669", "3787889110563790121")
|
||||
check("asdf", map[string]any{"key": "12345", "bar": "asdf"}, "12191037851845371770", "3787889110563790121")
|
||||
check("foo", nil, "7763396052142361238", "7763396052142361238")
|
||||
check("foo", map[string]any{"bar": "baz"}, "5783339285578751849", "5783339285578751849")
|
||||
check("foo", map[string]any{"key": "1234", "bar": "baz"}, "15578353952571222948", "5783339285578751849")
|
||||
check("foo", map[string]any{"key": "12345", "bar": "baz"}, "14335752410685132726", "5783339285578751849")
|
||||
check("asdf", map[string]any{"key": "1234", "bar": "asdf"}, "15578353952571222948", "15615023578599429261")
|
||||
check("asdf", map[string]any{"key": "12345", "bar": "asdf"}, "14335752410685132726", "15615023578599429261")
|
||||
}
|
||||
|
||||
@@ -60,7 +60,9 @@ func NewSpec(
|
||||
conf := s.Cfg.GetConfig().(*allconfig.Config)
|
||||
imgConfig := conf.Imaging
|
||||
|
||||
imaging, err := images.NewImageProcessor(imgConfig)
|
||||
imagesWarnl := logger.WarnCommand("images")
|
||||
|
||||
imaging, err := images.NewImageProcessor(imagesWarnl, imgConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -11,34 +11,26 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package identity
|
||||
package resources
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestHashString(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
func BenchmarkHashImage(b *testing.B) {
|
||||
f, err := os.Open("testdata/sunset.jpg")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
c.Assert(HashString("a", "b"), qt.Equals, "2712570657419664240")
|
||||
c.Assert(HashString("ab"), qt.Equals, "590647783936702392")
|
||||
|
||||
var vals []any = []any{"a", "b", tstKeyer{"c"}}
|
||||
|
||||
c.Assert(HashString(vals...), qt.Equals, "12599484872364427450")
|
||||
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
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _, err := hashImage(f)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
f.Seek(0, 0)
|
||||
}
|
||||
}
|
||||
@@ -99,11 +99,6 @@ func (t *tailwindcssTransformation) Transform(ctx *resources.ResourceTransformat
|
||||
|
||||
cmdArgs = append(cmdArgs, options.toArgs()...)
|
||||
|
||||
// TODO1
|
||||
// npm i tailwindcss @tailwindcss/cli
|
||||
// npm i tailwindcss@next @tailwindcss/cli@next
|
||||
// npx tailwindcss -h
|
||||
|
||||
var errBuf bytes.Buffer
|
||||
|
||||
stderr := io.MultiWriter(infow, &errBuf)
|
||||
@@ -134,7 +129,6 @@ func (t *tailwindcssTransformation) Transform(ctx *resources.ResourceTransformat
|
||||
t.rs.Assets.Fs, t.rs.Logger, ctx.DependencyManager,
|
||||
)
|
||||
|
||||
// TODO1 option {
|
||||
src, err = imp.resolve()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestOptionKey(t *testing.T) {
|
||||
|
||||
key := (&buildTransformation{optsm: opts}).Key()
|
||||
|
||||
c.Assert(key.Value(), qt.Equals, "jsbuild_7891849149754191852")
|
||||
c.Assert(key.Value(), qt.Equals, "jsbuild_1533819657654811600")
|
||||
}
|
||||
|
||||
func TestToBuildOptions(t *testing.T) {
|
||||
|
||||
@@ -27,6 +27,7 @@ func TestImageCache(t *testing.T) {
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
disableLiveReload = true
|
||||
baseURL = "https://example.org"
|
||||
-- content/mybundle/index.md --
|
||||
---
|
||||
@@ -61,9 +62,9 @@ anigif: {{ $anigif.RelPermalink }}|{{ $anigif.Width }}|{{ $anigif.Height }}|{{ $
|
||||
|
||||
assertImages := func() {
|
||||
b.AssertFileContent("public/index.html", `
|
||||
gif: /mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_1x2_resize_box_3.gif|}|1|2|image/gif|
|
||||
bmp: /mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x3_resize_box_3.bmp|}|2|3|image/bmp|
|
||||
anigif: /mybundle/giphy_hu3eafc418e52414ace6236bf1d31f82e1_52213_4x5_resize_box_1.gif|4|5|image/gif|
|
||||
gif: /mybundle/pixel_hu14657638653019978294.gif|}|1|2|image/gif|
|
||||
bmp: /mybundle/pixel_hu14705577916774115224.bmp|}|2|3|image/bmp|
|
||||
anigif: /mybundle/giphy_hu3665406585348417395.gif|4|5|image/gif|
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -159,9 +160,9 @@ resize 2|RelPermalink: {{ $image.RelPermalink }}|MediaType: {{ $image.MediaType
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html",
|
||||
"jpg|RelPermalink: /images/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_filter_17010532266664966692.jpg|MediaType: image/jpeg|Width: 1|Height: 1|",
|
||||
"resize 1|RelPermalink: /images/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_filter_6707036659822075562.jpg|MediaType: image/jpeg|Width: 20|Height: 30|",
|
||||
"resize 2|RelPermalink: /images/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_filter_6707036659822075562.jpg|MediaType: image/jpeg|Width: 20|Height: 30|",
|
||||
"jpg|RelPermalink: /images/pixel_hu13683954895608450100.jpg|MediaType: image/jpeg|Width: 1|Height: 1|",
|
||||
"resize 1|RelPermalink: /images/pixel_hu3453403302435331853.jpg|MediaType: image/jpeg|Width: 20|Height: 30|",
|
||||
"resize 2|RelPermalink: /images/pixel_hu3453403302435331853.jpg|MediaType: image/jpeg|Width: 20|Height: 30|",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 304 KiB After Width: | Height: | Size: 304 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |