mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-18 21:11:19 +02:00
@@ -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
|
||||
}
|
||||
|
@@ -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) {
|
||||
outErr = inErr
|
||||
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)
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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))
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user