Skip to content

Commit 378fe8d

Browse files
committed
resources: Refix resources.GetMatch/Match case-sensitivity bug
- add integration tests - fix not found error in `walk.go` Fix #7686 Fix #10112 - ref - #10305 - 281554e
1 parent d8aba18 commit 378fe8d

File tree

3 files changed

+272
-6
lines changed

3 files changed

+272
-6
lines changed

hugofs/walk.go

+48-5
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,13 @@ func (w *Walkway) Walk() error {
117117
fi = w.fi
118118
} else {
119119
info, _, err := lstatIfPossible(w.fs, w.root)
120-
if err != nil {
121-
if os.IsNotExist(err) {
122-
return nil
123-
}
124120

121+
if os.IsNotExist(err) {
122+
// recheck case-insensitive directory name
123+
info, err = w.findDirInsensitively()
124+
}
125+
126+
if err != nil {
125127
if w.checkErr(w.root, err) {
126128
return nil
127129
}
@@ -147,7 +149,48 @@ func lstatIfPossible(fs afero.Fs, path string) (os.FileInfo, bool, error) {
147149
return fi, false, err
148150
}
149151

150-
// checkErr returns true if the error is handled.
152+
// case-insenstively directory search
153+
func (w *Walkway) findDirInsensitively() (os.FileInfo, error) {
154+
parent := filepath.Dir(w.root)
155+
base := filepath.Base(w.root)
156+
157+
fi, err := w.fs.Stat(parent)
158+
159+
if err != nil {
160+
return nil, err
161+
}
162+
163+
if !fi.IsDir() {
164+
return nil, fmt.Errorf("%s is not directory", parent)
165+
}
166+
167+
f, err := w.fs.Open(parent)
168+
if err != nil {
169+
return nil, err
170+
}
171+
172+
names, err := f.Readdirnames(-1)
173+
174+
if err != nil {
175+
return nil, err
176+
}
177+
178+
baseLowered := strings.ToLower(base)
179+
for _, name := range names {
180+
if baseLowered == strings.ToLower(name) {
181+
p := filepath.Join(parent, name)
182+
fi, _, err := lstatIfPossible(w.fs, p)
183+
if err != nil {
184+
return nil, err
185+
}
186+
w.root = p
187+
return fi, err
188+
}
189+
}
190+
return nil, os.ErrNotExist
191+
}
192+
193+
// ceckErr returns true if the error is handled.
151194
func (w *Walkway) checkErr(filename string, err error) bool {
152195
if err == ErrPermissionSymlink {
153196
logUnsupportedSymlink(filename, w.logger)

resources/resource_factories/create/create.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ func (c *Client) GetMatch(pattern string) (resource.Resource, error) {
9393
}
9494

9595
func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource) bool, firstOnly bool) (resource.Resources, error) {
96+
patternNoLower := glob.NormalizePathNoLower(pattern)
9697
pattern = glob.NormalizePath(pattern)
9798
partitions := glob.FilterGlobParts(strings.Split(pattern, "/"))
9899
if len(partitions) == 0 {
@@ -127,7 +128,7 @@ func (c *Client) match(name, pattern string, matchFunc func(r resource.Resource)
127128
return firstOnly, nil
128129
}
129130

130-
if err := hugofs.Glob(c.rs.BaseFs.Assets.Fs, pattern, handle); err != nil {
131+
if err := hugofs.Glob(c.rs.BaseFs.Assets.Fs, patternNoLower, handle); err != nil {
131132
return nil, err
132133
}
133134

tpl/resources/integration_test.go

+222
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package resources_test
1515

1616
import (
17+
"runtime"
1718
"testing"
1819

1920
qt "github.com/frankban/quicktest"
@@ -98,3 +99,224 @@ func TestCopyPageShouldFail(t *testing.T) {
9899
b.Assert(err, qt.IsNotNil)
99100

100101
}
102+
103+
func TestMatchPatternCaseSensitivity(t *testing.T) {
104+
t.Parallel()
105+
106+
files := `
107+
-- config.toml --
108+
baseURL = "http://example.com/blog"
109+
-- assets/ABC/def/x.txt --
110+
content in x.txt
111+
-- assets/abc/DEF/y.txt --
112+
content in y.txt
113+
-- layouts/index.html --
114+
{{ with resources.Get "ABC/def/x.txt" }}
115+
Get "ABC/def/x.txt" | .Content => {{ .Content }}
116+
Get "ABC/def/x.txt" | .Name => {{ .Name }}
117+
{{ end }}
118+
119+
{{ with resources.Get "abc/DEF/y.txt" }}
120+
Get "abc/DEF/y.txt" | .Content => {{ .Content }}
121+
Get "abc/DEF/y.txt" | .Name => {{ .Name }}
122+
{{ end }}
123+
124+
{{ with resources.Get "abc/def/x.txt" }}
125+
Get "abc/def/x.txt" | .Content => {{ .Content }}
126+
Get "abc/def/x.txt" | .Name => {{ .Name }}
127+
{{ end }}
128+
129+
{{ with resources.Get "abc/def/y.txt" }}
130+
Get "abc/def/y.txt" | .Content => {{ .Content }}
131+
Get "abc/def/y.txt" | .Name => {{ .Name }}
132+
{{ end }}
133+
134+
{{ with resources.Get "ABC/DEF/X.TXT" }}
135+
Get "ABC/DEF/X.TXT" | .Content => {{ .Content }}
136+
Get "ABC/DEF/X.TXT" | .Name => {{ .Name }}
137+
{{ end }}
138+
139+
{{ with resources.Get "ABC/DEF/Y.TXT" }}
140+
Get "ABC/DEF/Y.TXT" | .Content => {{ .Content }}
141+
Get "ABC/DEF/Y.TXT" | .Name => {{ .Name }}
142+
{{ end }}
143+
144+
145+
{{ with resources.GetMatch "ABC/def/x.txt" }}
146+
GetMatch "ABC/def/x.txt" | .Content => {{ .Content }}
147+
GetMatch "ABC/def/x.txt" | .Name => {{ .Name }}
148+
{{ end }}
149+
150+
{{ with resources.GetMatch "abc/DEF/y.txt" }}
151+
GetMatch "abc/DEF/y.txt" | .Content => {{ .Content }}
152+
GetMatch "abc/DEF/y.txt" | .Name => {{ .Name }}
153+
{{ end }}
154+
155+
{{ with resources.GetMatch "abc/def/x.txt" }}
156+
GetMatch "abc/def/x.txt" | .Content => {{ .Content }}
157+
GetMatch "abc/def/x.txt" | .Name => {{ .Name }}
158+
{{ end }}
159+
160+
{{ with resources.GetMatch "abc/def/y.txt" }}
161+
GetMatch "abc/def/y.txt" | .Content => {{ .Content }}
162+
GetMatch "abc/def/y.txt" | .Name => {{ .Name }}
163+
{{ end }}
164+
165+
{{ with resources.GetMatch "ABC/DEF/X.TXT" }}
166+
GetMatch "ABC/DEF/X.TXT" | .Content => {{ .Content }}
167+
GetMatch "ABC/DEF/X.TXT" | .Name => {{ .Name }}
168+
{{ end }}
169+
170+
{{ with resources.GetMatch "ABC/DEF/Y.TXT" }}
171+
GetMatch "ABC/DEF/Y.TXT" | .Content => {{ .Content }}
172+
GetMatch "ABC/DEF/Y.TXT" | .Name => {{ .Name }}
173+
{{ end }}
174+
175+
{{ with resources.GetMatch "ABC/*/x.txt" }}
176+
GetMatch "ABC/*/x.txt" | .Content => {{ .Content }}
177+
GetMatch "ABC/*/x.txt" | .Name => {{ .Name }}
178+
{{ end }}
179+
180+
{{ with resources.GetMatch "abc/*/x.txt" }}
181+
GetMatch "abc/*/x.txt" | .Content => {{ .Content }}
182+
GetMatch "abc/*/x.txt" | .Name => {{ .Name }}
183+
{{ end }}
184+
185+
{{ with resources.GetMatch "ABC/**/x.txt" }}
186+
GetMatch "ABC/**/x.txt" | .Content => {{ .Content }}
187+
GetMatch "ABC/**/x.txt" | .Name => {{ .Name }}
188+
{{ end }}
189+
190+
{{ with resources.GetMatch "abc/**/x.txt" }}
191+
GetMatch "abc/**/x.txt" | .Content => {{ .Content }}
192+
GetMatch "abc/**/x.txt" | .Name => {{ .Name }}
193+
{{ end }}
194+
195+
196+
{{ with resources.Match "**/*.txt" }}
197+
Match "**/*.txt" | len => {{ len . }}
198+
{{ end }}
199+
200+
{{ with resources.Match "*/*/*.txt" }}
201+
Match "*/*/*.txt" | len => {{ len . }}
202+
{{ end }}
203+
204+
# windows
205+
206+
{{ with resources.GetMatch "abc/*/y.txt" }}
207+
GetMatch "abc/*/y.txt" | .Content => {{ .Content }}
208+
GetMatch "abc/*/y.txt" | .Name => {{ .Name }}
209+
{{ end }}
210+
211+
{{ with resources.GetMatch "ABC/*/Y.TXT" }}
212+
GetMatch "ABC/*/Y.TXT" | .Content => {{ .Content }}
213+
GetMatch "ABC/*/Y.TXT" | .Name => {{ .Name }}
214+
{{ end }}
215+
216+
{{ with resources.GetMatch "abc/**/y.txt" }}
217+
GetMatch "abc/**/y.txt" | .Content => {{ .Content }}
218+
GetMatch "abc/**/y.txt" | .Name => {{ .Name }}
219+
{{ end }}
220+
221+
{{ with resources.GetMatch "ABC/**/Y.TXT" }}
222+
GetMatch "ABC/**/Y.TXT" | .Content => {{ .Content }}
223+
GetMatch "ABC/**/Y.TXT" | .Name => {{ .Name }}
224+
{{ end }}
225+
`
226+
227+
b := hugolib.NewIntegrationTestBuilder(
228+
hugolib.IntegrationTestConfig{
229+
T: t,
230+
TxtarString: files,
231+
NeedsOsFS: true,
232+
}).Build()
233+
234+
want := `
235+
236+
Get "ABC/def/x.txt" | .Content => content in x.txt
237+
Get "ABC/def/x.txt" | .Name => ABC/def/x.txt
238+
239+
Get "abc/DEF/y.txt" | .Content => content in y.txt
240+
Get "abc/DEF/y.txt" | .Name => abc/DEF/y.txt
241+
242+
Get "abc/def/x.txt" | .Content => content in x.txt
243+
Get "abc/def/x.txt" | .Name => ABC/def/x.txt
244+
245+
Get "abc/def/y.txt" | .Content => content in y.txt
246+
Get "abc/def/y.txt" | .Name => abc/DEF/y.txt
247+
248+
Get "ABC/DEF/X.TXT" | .Content => content in x.txt
249+
Get "ABC/DEF/X.TXT" | .Name => ABC/def/x.txt
250+
251+
Get "ABC/DEF/Y.TXT" | .Content => content in y.txt
252+
Get "ABC/DEF/Y.TXT" | .Name => abc/DEF/y.txt
253+
254+
255+
GetMatch "ABC/def/x.txt" | .Content => content in x.txt
256+
GetMatch "ABC/def/x.txt" | .Name => ABC/def/x.txt
257+
258+
GetMatch "abc/DEF/y.txt" | .Content => content in y.txt
259+
GetMatch "abc/DEF/y.txt" | .Name => abc/DEF/y.txt
260+
261+
GetMatch "abc/def/x.txt" | .Content => content in x.txt
262+
GetMatch "abc/def/x.txt" | .Name => ABC/def/x.txt
263+
264+
GetMatch "abc/def/y.txt" | .Content => content in y.txt
265+
GetMatch "abc/def/y.txt" | .Name => abc/DEF/y.txt
266+
267+
GetMatch "ABC/DEF/X.TXT" | .Content => content in x.txt
268+
GetMatch "ABC/DEF/X.TXT" | .Name => ABC/def/x.txt
269+
270+
GetMatch "ABC/DEF/Y.TXT" | .Content => content in y.txt
271+
GetMatch "ABC/DEF/Y.TXT" | .Name => abc/DEF/y.txt
272+
273+
GetMatch "ABC/*/x.txt" | .Content => content in x.txt
274+
GetMatch "ABC/*/x.txt" | .Name => ABC/def/x.txt
275+
276+
GetMatch "abc/*/x.txt" | .Content => content in x.txt
277+
GetMatch "abc/*/x.txt" | .Name => ABC/def/x.txt
278+
279+
GetMatch "ABC/**/x.txt" | .Content => content in x.txt
280+
GetMatch "ABC/**/x.txt" | .Name => ABC/def/x.txt
281+
282+
GetMatch "abc/**/x.txt" | .Content => content in x.txt
283+
GetMatch "abc/**/x.txt" | .Name => ABC/def/x.txt
284+
285+
286+
Match "*/*/*.txt" | len => 2
287+
Match "**/*.txt" | len => 2
288+
`
289+
if runtime.GOOS != "windows" && runtime.GOOS != "darwin" {
290+
want += `
291+
GetMatch "abc/*/y.txt" | .Content => content in y.txt
292+
GetMatch "abc/*/y.txt" | .Name => abc/DEF/y.txt
293+
294+
GetMatch "ABC/*/Y.TXT" | .Content => content in y.txt
295+
GetMatch "ABC/*/Y.TXT" | .Name => abc/DEF/y.txt
296+
297+
GetMatch "abc/**/y.txt" | .Content => content in y.txt
298+
GetMatch "abc/**/y.txt" | .Name => abc/DEF/y.txt
299+
300+
GetMatch "ABC/**/Y.TXT" | .Content => content in y.txt
301+
GetMatch "ABC/**/Y.TXT" | .Name => abc/DEF/y.txt
302+
`
303+
} else {
304+
// on Windows or mcsOS, glob stars are replaced with lowercase name inevitably
305+
want += `
306+
GetMatch "abc/*/y.txt" | .Content => content in y.txt
307+
GetMatch "abc/*/y.txt" | .Name => abc/def/y.txt
308+
309+
GetMatch "ABC/*/Y.TXT" | .Content => content in y.txt
310+
GetMatch "ABC/*/Y.TXT" | .Name => abc/def/y.txt
311+
312+
GetMatch "abc/**/y.txt" | .Content => content in y.txt
313+
GetMatch "abc/**/y.txt" | .Name => abc/def/y.txt
314+
315+
GetMatch "ABC/**/Y.TXT" | .Content => content in y.txt
316+
GetMatch "ABC/**/Y.TXT" | .Name => abc/def/y.txt
317+
`
318+
}
319+
320+
b.AssertFileContent("public/index.html", want)
321+
322+
}

0 commit comments

Comments
 (0)