Skip to content

Commit f70523b

Browse files
committed
Add js.Batch
Fixes gohugoio#12626 Closes gohugoio#7499 Closes gohugoio#9978 Closes gohugoio#12879 Closes gohugoio#13113 Fixes gohugoio#13116
1 parent 989b299 commit f70523b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+4594
-971
lines changed

commands/hugobuilder.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,11 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
920920

921921
changed := c.changeDetector.changed()
922922
if c.changeDetector != nil {
923-
lrl.Logf("build changed %d files", len(changed))
923+
if len(changed) >= 10 {
924+
lrl.Logf("build changed %d files", len(changed))
925+
} else {
926+
lrl.Logf("build changed %d files: %q", len(changed), changed)
927+
}
924928
if len(changed) == 0 {
925929
// Nothing has changed.
926930
return

commands/server.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"path"
3333
"path/filepath"
3434
"regexp"
35+
"sort"
3536
"strconv"
3637
"strings"
3738
"sync"
@@ -210,16 +211,17 @@ func (f *fileChangeDetector) changed() []string {
210211
}
211212
}
212213

213-
return f.filterIrrelevant(c)
214+
return f.filterIrrelevantAndSort(c)
214215
}
215216

216-
func (f *fileChangeDetector) filterIrrelevant(in []string) []string {
217+
func (f *fileChangeDetector) filterIrrelevantAndSort(in []string) []string {
217218
var filtered []string
218219
for _, v := range in {
219220
if !f.irrelevantRe.MatchString(v) {
220221
filtered = append(filtered, v)
221222
}
222223
}
224+
sort.Strings(filtered)
223225
return filtered
224226
}
225227

common/herrors/errors.go

+15
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,21 @@ func IsNotExist(err error) bool {
133133
return false
134134
}
135135

136+
// IsExist returns true if the error is a file exists error.
137+
// Unlike os.IsExist, this also considers wrapped errors.
138+
func IsExist(err error) bool {
139+
if os.IsExist(err) {
140+
return true
141+
}
142+
143+
// os.IsExist does not consider wrapped errors.
144+
if os.IsExist(errors.Unwrap(err)) {
145+
return true
146+
}
147+
148+
return false
149+
}
150+
136151
var nilPointerErrRe = regexp.MustCompile(`at <(.*)>: error calling (.*?): runtime error: invalid memory address or nil pointer dereference`)
137152

138153
const deferredPrefix = "__hdeferred/"

common/herrors/file_error.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ func extractPosition(e error) (pos text.Position) {
384384
case godartsass.SassError:
385385
span := v.Span
386386
start := span.Start
387-
filename, _ := paths.UrlToFilename(span.Url)
387+
filename, _ := paths.UrlStringToFilename(span.Url)
388388
pos.Filename = filename
389389
pos.Offset = start.Offset
390390
pos.ColumnNumber = start.Column

common/hreflect/helpers.go

+21
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,27 @@ func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) {
223223
return time.Time{}, false
224224
}
225225

226+
// ToSliceAny converts the given value to a slice of any if possible.
227+
func ToSliceAny(v any) ([]any, bool) {
228+
if v == nil {
229+
return nil, false
230+
}
231+
switch vv := v.(type) {
232+
case []any:
233+
return vv, true
234+
default:
235+
vvv := reflect.ValueOf(v)
236+
if vvv.Kind() == reflect.Slice {
237+
out := make([]any, vvv.Len())
238+
for i := 0; i < vvv.Len(); i++ {
239+
out[i] = vvv.Index(i).Interface()
240+
}
241+
return out, true
242+
}
243+
}
244+
return nil, false
245+
}
246+
226247
func CallMethodByName(cxt context.Context, name string, v reflect.Value) []reflect.Value {
227248
fn := v.MethodByName(name)
228249
var args []reflect.Value

common/hreflect/helpers_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ func TestIsContextType(t *testing.T) {
5050
c.Assert(IsContextType(reflect.TypeOf(valueCtx)), qt.IsTrue)
5151
}
5252

53+
func TestToSliceAny(t *testing.T) {
54+
c := qt.New(t)
55+
56+
checkOK := func(in any, expected []any) {
57+
out, ok := ToSliceAny(in)
58+
c.Assert(ok, qt.Equals, true)
59+
c.Assert(out, qt.DeepEquals, expected)
60+
}
61+
62+
checkOK([]any{1, 2, 3}, []any{1, 2, 3})
63+
checkOK([]int{1, 2, 3}, []any{1, 2, 3})
64+
}
65+
5366
func BenchmarkIsContextType(b *testing.B) {
5467
type k string
5568
b.Run("value", func(b *testing.B) {

common/maps/cache.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,14 @@ func (c *Cache[K, T]) set(key K, value T) {
113113
}
114114

115115
// ForEeach calls the given function for each key/value pair in the cache.
116-
func (c *Cache[K, T]) ForEeach(f func(K, T)) {
116+
// If the function returns false, the iteration stops.
117+
func (c *Cache[K, T]) ForEeach(f func(K, T) bool) {
117118
c.RLock()
118119
defer c.RUnlock()
119120
for k, v := range c.m {
120-
f(k, v)
121+
if !f(k, v) {
122+
return
123+
}
121124
}
122125
}
123126

common/paths/url.go

+108-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2021 The Hugo Authors. All rights reserved.
1+
// Copyright 2024 The Hugo Authors. All rights reserved.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -159,9 +159,92 @@ func Uglify(in string) string {
159159
return path.Clean(in)
160160
}
161161

162+
// URLEscape escapes unicode letters.
163+
func URLEscape(uri string) string {
164+
// escape unicode letters
165+
u, err := url.Parse(uri)
166+
if err != nil {
167+
panic(err)
168+
}
169+
return u.String()
170+
}
171+
172+
// TrimExt trims the extension from a path..
173+
func TrimExt(in string) string {
174+
return strings.TrimSuffix(in, path.Ext(in))
175+
}
176+
177+
// From https://github.com/golang/go/blob/e0c76d95abfc1621259864adb3d101cf6f1f90fc/src/cmd/go/internal/web/url.go#L45
178+
func UrlFromFilename(filename string) (*url.URL, error) {
179+
if !filepath.IsAbs(filename) {
180+
return nil, fmt.Errorf("filepath must be absolute")
181+
}
182+
183+
// If filename has a Windows volume name, convert the volume to a host and prefix
184+
// per https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/.
185+
if vol := filepath.VolumeName(filename); vol != "" {
186+
if strings.HasPrefix(vol, `\\`) {
187+
filename = filepath.ToSlash(filename[2:])
188+
i := strings.IndexByte(filename, '/')
189+
190+
if i < 0 {
191+
// A degenerate case.
192+
// \\host.example.com (without a share name)
193+
// becomes
194+
// file://host.example.com/
195+
return &url.URL{
196+
Scheme: "file",
197+
Host: filename,
198+
Path: "/",
199+
}, nil
200+
}
201+
202+
// \\host.example.com\Share\path\to\file
203+
// becomes
204+
// file://host.example.com/Share/path/to/file
205+
return &url.URL{
206+
Scheme: "file",
207+
Host: filename[:i],
208+
Path: filepath.ToSlash(filename[i:]),
209+
}, nil
210+
}
211+
212+
// C:\path\to\file
213+
// becomes
214+
// file:///C:/path/to/file
215+
return &url.URL{
216+
Scheme: "file",
217+
Path: "/" + filepath.ToSlash(filename),
218+
}, nil
219+
}
220+
221+
// /path/to/file
222+
// becomes
223+
// file:///path/to/file
224+
return &url.URL{
225+
Scheme: "file",
226+
Path: filepath.ToSlash(filename),
227+
}, nil
228+
}
229+
230+
// UrlStringToFilename converts a URL string to a filename.
231+
// Returns the filename and a boolean indicating success.
232+
// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
233+
func UrlStringToFilename(s string) (string, bool) {
234+
u, err := url.ParseRequestURI(s)
235+
if err != nil {
236+
return filepath.FromSlash(s), false
237+
}
238+
u.Scheme = fileURLSchemeReplacer.Replace(u.Scheme)
239+
return UrlToFilename(u)
240+
}
241+
242+
// hugostdin is used in the Dart Sass integration.
243+
var fileURLSchemeReplacer = strings.NewReplacer("hugostdin", "file")
244+
162245
// UrlToFilename converts the URL s to a filename.
163246
// If ParseRequestURI fails, the input is just converted to OS specific slashes and returned.
164-
func UrlToFilename(s string) (string, bool) {
247+
func doUrlStringToFilename(s string) (string, bool) {
165248
u, err := url.ParseRequestURI(s)
166249
if err != nil {
167250
return filepath.FromSlash(s), false
@@ -184,12 +267,29 @@ func UrlToFilename(s string) (string, bool) {
184267
return p, true
185268
}
186269

187-
// URLEscape escapes unicode letters.
188-
func URLEscape(uri string) string {
189-
// escape unicode letters
190-
u, err := url.Parse(uri)
270+
// Based on https://github.com/golang/go/blob/e0c76d95abfc1621259864adb3d101cf6f1f90fc/src/cmd/go/internal/web/url.go#L45
271+
func UrlToFilename(u *url.URL) (string, bool) {
272+
if u.Scheme != "file" {
273+
return "", false
274+
}
275+
276+
checkAbs := func(path string) (string, bool) {
277+
if !filepath.IsAbs(path) {
278+
return "", false
279+
}
280+
return path, true
281+
}
282+
283+
if u.Path == "" {
284+
if u.Host != "" || u.Opaque == "" {
285+
return "", false
286+
}
287+
return checkAbs(filepath.FromSlash(u.Opaque))
288+
}
289+
290+
path, err := convertFileURLPath(u.Host, u.Path)
191291
if err != nil {
192-
panic(err)
292+
return path, false
193293
}
194-
return u.String()
294+
return checkAbs(path)
195295
}

common/paths/url_other.go

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2024 The Hugo Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
//go:build !windows
15+
16+
package paths
17+
18+
import (
19+
"errors"
20+
"path/filepath"
21+
)
22+
23+
// From https://github.com/golang/go/blob/6c25cf1c5fc063cc9ea27aa850ef0c4345f3a5b4/src/cmd/go/internal/web/url_other.go#L14
24+
func convertFileURLPath(host, path string) (string, error) {
25+
switch host {
26+
case "", "localhost":
27+
default:
28+
return "", errors.New("file URL specifies non-local host")
29+
}
30+
return filepath.FromSlash(path), nil
31+
}

common/paths/url_windows.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright 2024 The Hugo Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package paths
15+
16+
import (
17+
"errors"
18+
"path/filepath"
19+
"strings"
20+
)
21+
22+
// From https://github.com/golang/go/blob/6c25cf1c5fc063cc9ea27aa850ef0c4345f3a5b4/src/cmd/go/internal/web/url_windows.go#L13
23+
func convertFileURLPath(host, path string) (string, error) {
24+
if len(path) == 0 || path[0] != '/' {
25+
return "", errors.New("file URL path must start with /")
26+
}
27+
28+
path = filepath.FromSlash(path)
29+
30+
// We interpret Windows file URLs per the description in
31+
// https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/.
32+
33+
// The host part of a file URL (if any) is the UNC volume name,
34+
// but RFC 8089 reserves the authority "localhost" for the local machine.
35+
if host != "" && host != "localhost" {
36+
// A common "legacy" format omits the leading slash before a drive letter,
37+
// encoding the drive letter as the host instead of part of the path.
38+
// (See https://blogs.msdn.microsoft.com/freeassociations/2005/05/19/the-bizarre-and-unhappy-story-of-file-urls/.)
39+
// We do not support that format, but we should at least emit a more
40+
// helpful error message for it.
41+
if filepath.VolumeName(host) != "" {
42+
return "", errors.New("file URL encodes volume in host field: too few slashes?")
43+
}
44+
return `\\` + host + path, nil
45+
}
46+
47+
// If host is empty, path must contain an initial slash followed by a
48+
// drive letter and path. Remove the slash and verify that the path is valid.
49+
if vol := filepath.VolumeName(path[1:]); vol == "" || strings.HasPrefix(vol, `\\`) {
50+
return "", errors.New("file URL missing drive letter")
51+
}
52+
return path[1:], nil
53+
}

common/types/closer.go

+7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ type Closer interface {
1919
Close() error
2020
}
2121

22+
// CloserFunc is a convenience type to create a Closer from a function.
23+
type CloserFunc func() error
24+
25+
func (f CloserFunc) Close() error {
26+
return f()
27+
}
28+
2229
type CloseAdder interface {
2330
Add(Closer)
2431
}

config/allconfig/configlanguage.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,11 @@ func (c ConfigLanguage) Watching() bool {
137137
return c.m.Base.Internal.Watch
138138
}
139139

140-
func (c ConfigLanguage) NewIdentityManager(name string) identity.Manager {
140+
func (c ConfigLanguage) NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager {
141141
if !c.Watching() {
142142
return identity.NopManager
143143
}
144-
return identity.NewManager(name)
144+
return identity.NewManager(name, opts...)
145145
}
146146

147147
func (c ConfigLanguage) ContentTypes() config.ContentTypesProvider {

config/configProvider.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ type AllProvider interface {
5858
BuildDrafts() bool
5959
Running() bool
6060
Watching() bool
61-
NewIdentityManager(name string) identity.Manager
61+
NewIdentityManager(name string, opts ...identity.ManagerOption) identity.Manager
6262
FastRenderMode() bool
6363
PrintUnusedTemplates() bool
6464
EnableMissingTranslationPlaceholders() bool

0 commit comments

Comments
 (0)