aboutsummaryrefslogtreecommitdiffhomepage
path: root/common
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2024-06-08 11:52:22 +0200
committerBjørn Erik Pedersen <[email protected]>2024-06-23 11:25:47 +0200
commit6cd0784e447f18e009cbbf30de471e486f7cf356 (patch)
tree0684d05d7e487ebe93463636ed8b5a1bb78e4704 /common
parent8731d8822216dd3c7587769e3cf5d98690717b0c (diff)
downloadhugo-6cd0784e447f18e009cbbf30de471e486f7cf356.tar.gz
hugo-6cd0784e447f18e009cbbf30de471e486f7cf356.zip
Implement defer
Closes #8086 Closes #12589
Diffstat (limited to 'common')
-rw-r--r--common/collections/stack.go13
-rw-r--r--common/herrors/errors.go41
-rw-r--r--common/hugio/hasBytesWriter.go41
-rw-r--r--common/hugio/hasBytesWriter_test.go13
-rw-r--r--common/maps/cache.go20
-rw-r--r--common/paths/path.go41
-rw-r--r--common/paths/path_test.go49
7 files changed, 195 insertions, 23 deletions
diff --git a/common/collections/stack.go b/common/collections/stack.go
index 0f1581626..96d32fe4b 100644
--- a/common/collections/stack.go
+++ b/common/collections/stack.go
@@ -65,3 +65,16 @@ func (s *Stack[T]) Drain() []T {
s.items = nil
return items
}
+
+func (s *Stack[T]) DrainMatching(predicate func(T) bool) []T {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ var items []T
+ for i := len(s.items) - 1; i >= 0; i-- {
+ if predicate(s.items[i]) {
+ items = append(items, s.items[i])
+ s.items = append(s.items[:i], s.items[i+1:]...)
+ }
+ }
+ return items
+}
diff --git a/common/herrors/errors.go b/common/herrors/errors.go
index 7c389c1ae..e7f91462e 100644
--- a/common/herrors/errors.go
+++ b/common/herrors/errors.go
@@ -68,6 +68,20 @@ func (e *TimeoutError) Is(target error) bool {
return ok
}
+// errMessage wraps an error with a message.
+type errMessage struct {
+ msg string
+ err error
+}
+
+func (e *errMessage) Error() string {
+ return e.msg
+}
+
+func (e *errMessage) Unwrap() error {
+ return e.err
+}
+
// IsFeatureNotAvailableError returns true if the given error is or contains a FeatureNotAvailableError.
func IsFeatureNotAvailableError(err error) bool {
return errors.Is(err, &FeatureNotAvailableError{})
@@ -121,19 +135,38 @@ func IsNotExist(err error) bool {
var nilPointerErrRe = regexp.MustCompile(`at <(.*)>: error calling (.*?): runtime error: invalid memory address or nil pointer dereference`)
-func ImproveIfNilPointer(inErr error) (outErr error) {
+const deferredPrefix = "__hdeferred/"
+
+var deferredStringToRemove = regexp.MustCompile(`executing "__hdeferred/.*" `)
+
+// ImproveRenderErr improves the error message for rendering errors.
+func ImproveRenderErr(inErr error) (outErr error) {
outErr = inErr
+ msg := improveIfNilPointerMsg(inErr)
+ if msg != "" {
+ outErr = &errMessage{msg: msg, err: outErr}
+ }
+ if strings.Contains(inErr.Error(), deferredPrefix) {
+ msg := deferredStringToRemove.ReplaceAllString(inErr.Error(), "executing ")
+ outErr = &errMessage{msg: msg, err: outErr}
+ }
+ return
+}
+
+func improveIfNilPointerMsg(inErr error) string {
m := nilPointerErrRe.FindStringSubmatch(inErr.Error())
if len(m) == 0 {
- return
+ return ""
}
call := m[1]
field := m[2]
parts := strings.Split(call, ".")
+ if len(parts) < 2 {
+ return ""
+ }
receiverName := parts[len(parts)-2]
receiver := strings.Join(parts[:len(parts)-1], ".")
s := fmt.Sprintf("– %s is nil; wrap it in if or with: {{ with %s }}{{ .%s }}{{ end }}", receiverName, receiver, field)
- outErr = errors.New(nilPointerErrRe.ReplaceAllString(inErr.Error(), s))
- return
+ return nilPointerErrRe.ReplaceAllString(inErr.Error(), s)
}
diff --git a/common/hugio/hasBytesWriter.go b/common/hugio/hasBytesWriter.go
index 5148c82f9..d2bcd1bb4 100644
--- a/common/hugio/hasBytesWriter.go
+++ b/common/hugio/hasBytesWriter.go
@@ -17,24 +17,35 @@ import (
"bytes"
)
-// HasBytesWriter is a writer that will set Match to true if the given pattern
-// is found in the stream.
+// HasBytesWriter is a writer will match against a slice of patterns.
type HasBytesWriter struct {
- Match bool
- Pattern []byte
+ Patterns []*HasBytesPattern
i int
done bool
buff []byte
}
+type HasBytesPattern struct {
+ Match bool
+ Pattern []byte
+}
+
+func (h *HasBytesWriter) patternLen() int {
+ l := 0
+ for _, p := range h.Patterns {
+ l += len(p.Pattern)
+ }
+ return l
+}
+
func (h *HasBytesWriter) Write(p []byte) (n int, err error) {
if h.done {
return len(p), nil
}
if len(h.buff) == 0 {
- h.buff = make([]byte, len(h.Pattern)*2)
+ h.buff = make([]byte, h.patternLen()*2)
}
for i := range p {
@@ -46,11 +57,23 @@ func (h *HasBytesWriter) Write(p []byte) (n int, err error) {
h.i = len(h.buff) / 2
}
- if bytes.Contains(h.buff, h.Pattern) {
- h.Match = true
- h.done = true
- return len(p), nil
+ for _, pp := range h.Patterns {
+ if bytes.Contains(h.buff, pp.Pattern) {
+ pp.Match = true
+ done := true
+ for _, ppp := range h.Patterns {
+ if !ppp.Match {
+ done = false
+ break
+ }
+ }
+ if done {
+ h.done = true
+ }
+ return len(p), nil
+ }
}
+
}
return len(p), nil
diff --git a/common/hugio/hasBytesWriter_test.go b/common/hugio/hasBytesWriter_test.go
index af53fa5dd..49487ab0b 100644
--- a/common/hugio/hasBytesWriter_test.go
+++ b/common/hugio/hasBytesWriter_test.go
@@ -34,8 +34,11 @@ func TestHasBytesWriter(t *testing.T) {
var b bytes.Buffer
h := &HasBytesWriter{
- Pattern: []byte("__foo"),
+ Patterns: []*HasBytesPattern{
+ {Pattern: []byte("__foo")},
+ },
}
+
return h, io.MultiWriter(&b, h)
}
@@ -46,19 +49,19 @@ func TestHasBytesWriter(t *testing.T) {
for i := 0; i < 22; i++ {
h, w := neww()
fmt.Fprintf(w, rndStr()+"abc __foobar"+rndStr())
- c.Assert(h.Match, qt.Equals, true)
+ c.Assert(h.Patterns[0].Match, qt.Equals, true)
h, w = neww()
fmt.Fprintf(w, rndStr()+"abc __f")
fmt.Fprintf(w, "oo bar"+rndStr())
- c.Assert(h.Match, qt.Equals, true)
+ c.Assert(h.Patterns[0].Match, qt.Equals, true)
h, w = neww()
fmt.Fprintf(w, rndStr()+"abc __moo bar")
- c.Assert(h.Match, qt.Equals, false)
+ c.Assert(h.Patterns[0].Match, qt.Equals, false)
}
h, w := neww()
fmt.Fprintf(w, "__foo")
- c.Assert(h.Match, qt.Equals, true)
+ c.Assert(h.Patterns[0].Match, qt.Equals, true)
}
diff --git a/common/maps/cache.go b/common/maps/cache.go
index 3723d318e..7cd7410c2 100644
--- a/common/maps/cache.go
+++ b/common/maps/cache.go
@@ -74,6 +74,26 @@ func (c *Cache[K, T]) ForEeach(f func(K, T)) {
}
}
+func (c *Cache[K, T]) Drain() map[K]T {
+ c.Lock()
+ m := c.m
+ c.m = make(map[K]T)
+ c.Unlock()
+ return m
+}
+
+func (c *Cache[K, T]) Len() int {
+ c.RLock()
+ defer c.RUnlock()
+ return len(c.m)
+}
+
+func (c *Cache[K, T]) Reset() {
+ c.Lock()
+ c.m = make(map[K]T)
+ c.Unlock()
+}
+
// SliceCache is a simple thread safe cache backed by a map.
type SliceCache[T any] struct {
m map[string][]T
diff --git a/common/paths/path.go b/common/paths/path.go
index 906270cae..de91d6a2f 100644
--- a/common/paths/path.go
+++ b/common/paths/path.go
@@ -237,12 +237,17 @@ func prettifyPath(in string, b filepathPathBridge) string {
return b.Join(b.Dir(in), name, "index"+ext)
}
-// CommonDir returns the common directory of the given paths.
-func CommonDir(path1, path2 string) string {
+// CommonDirPath returns the common directory of the given paths.
+func CommonDirPath(path1, path2 string) string {
if path1 == "" || path2 == "" {
return ""
}
+ hadLeadingSlash := strings.HasPrefix(path1, "/") || strings.HasPrefix(path2, "/")
+
+ path1 = TrimLeading(path1)
+ path2 = TrimLeading(path2)
+
p1 := strings.Split(path1, "/")
p2 := strings.Split(path2, "/")
@@ -256,7 +261,13 @@ func CommonDir(path1, path2 string) string {
}
}
- return strings.Join(common, "/")
+ s := strings.Join(common, "/")
+
+ if hadLeadingSlash && s != "" {
+ s = "/" + s
+ }
+
+ return s
}
// Sanitize sanitizes string to be used in Hugo's file paths and URLs, allowing only
@@ -384,12 +395,27 @@ func PathEscape(pth string) string {
// ToSlashTrimLeading is just a filepath.ToSlash with an added / prefix trimmer.
func ToSlashTrimLeading(s string) string {
- return strings.TrimPrefix(filepath.ToSlash(s), "/")
+ return TrimLeading(filepath.ToSlash(s))
+}
+
+// TrimLeading trims the leading slash from the given string.
+func TrimLeading(s string) string {
+ return strings.TrimPrefix(s, "/")
}
// ToSlashTrimTrailing is just a filepath.ToSlash with an added / suffix trimmer.
func ToSlashTrimTrailing(s string) string {
- return strings.TrimSuffix(filepath.ToSlash(s), "/")
+ return TrimTrailing(filepath.ToSlash(s))
+}
+
+// TrimTrailing trims the trailing slash from the given string.
+func TrimTrailing(s string) string {
+ return strings.TrimSuffix(s, "/")
+}
+
+// ToSlashTrim trims any leading and trailing slashes from the given string and converts it to a forward slash separated path.
+func ToSlashTrim(s string) string {
+ return strings.Trim(filepath.ToSlash(s), "/")
}
// ToSlashPreserveLeading converts the path given to a forward slash separated path
@@ -397,3 +423,8 @@ func ToSlashTrimTrailing(s string) string {
func ToSlashPreserveLeading(s string) string {
return "/" + strings.Trim(filepath.ToSlash(s), "/")
}
+
+// IsSameFilePath checks if s1 and s2 are the same file path.
+func IsSameFilePath(s1, s2 string) bool {
+ return path.Clean(ToSlashTrim(s1)) == path.Clean(ToSlashTrim(s2))
+}
diff --git a/common/paths/path_test.go b/common/paths/path_test.go
index 3605bfc43..bc27df6c6 100644
--- a/common/paths/path_test.go
+++ b/common/paths/path_test.go
@@ -262,3 +262,52 @@ func TestFieldsSlash(t *testing.T) {
c.Assert(FieldsSlash("/"), qt.DeepEquals, []string{})
c.Assert(FieldsSlash(""), qt.DeepEquals, []string{})
}
+
+func TestCommonDirPath(t *testing.T) {
+ c := qt.New(t)
+
+ for _, this := range []struct {
+ a, b, expected string
+ }{
+ {"/a/b/c", "/a/b/d", "/a/b"},
+ {"/a/b/c", "a/b/d", "/a/b"},
+ {"a/b/c", "/a/b/d", "/a/b"},
+ {"a/b/c", "a/b/d", "a/b"},
+ {"/a/b/c", "/a/b/c", "/a/b/c"},
+ {"/a/b/c", "/a/b/c/d", "/a/b/c"},
+ {"/a/b/c", "/a/b", "/a/b"},
+ {"/a/b/c", "/a", "/a"},
+ {"/a/b/c", "/d/e/f", ""},
+ } {
+ c.Assert(CommonDirPath(this.a, this.b), qt.Equals, this.expected, qt.Commentf("a: %s b: %s", this.a, this.b))
+ }
+}
+
+func TestIsSameFilePath(t *testing.T) {
+ c := qt.New(t)
+
+ for _, this := range []struct {
+ a, b string
+ expected bool
+ }{
+ {"/a/b/c", "/a/b/c", true},
+ {"/a/b/c", "/a/b/c/", true},
+ {"/a/b/c", "/a/b/d", false},
+ {"/a/b/c", "/a/b", false},
+ {"/a/b/c", "/a/b/c/d", false},
+ {"/a/b/c", "/a/b/cd", false},
+ {"/a/b/c", "/a/b/cc", false},
+ {"/a/b/c", "/a/b/c/", true},
+ {"/a/b/c", "/a/b/c//", true},
+ {"/a/b/c", "/a/b/c/.", true},
+ {"/a/b/c", "/a/b/c/./", true},
+ {"/a/b/c", "/a/b/c/./.", true},
+ {"/a/b/c", "/a/b/c/././", true},
+ {"/a/b/c", "/a/b/c/././.", true},
+ {"/a/b/c", "/a/b/c/./././", true},
+ {"/a/b/c", "/a/b/c/./././.", true},
+ {"/a/b/c", "/a/b/c/././././", true},
+ } {
+ c.Assert(IsSameFilePath(filepath.FromSlash(this.a), filepath.FromSlash(this.b)), qt.Equals, this.expected, qt.Commentf("a: %s b: %s", this.a, this.b))
+ }
+}