From b64617fe4f90da030bcf4a9c5a4913393ce96b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Mon, 12 Aug 2019 16:43:37 +0200 Subject: Add resources.Match and resources.GetMatch Fix #6190 --- hugofs/glob.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++++ hugofs/glob/glob.go | 81 +++++++++++++++++++++++++++++++++++++++++++++ hugofs/glob/glob_test.go | 63 +++++++++++++++++++++++++++++++++++ hugofs/glob_test.go | 61 ++++++++++++++++++++++++++++++++++ 4 files changed, 290 insertions(+) create mode 100644 hugofs/glob.go create mode 100644 hugofs/glob/glob.go create mode 100644 hugofs/glob/glob_test.go create mode 100644 hugofs/glob_test.go (limited to 'hugofs') diff --git a/hugofs/glob.go b/hugofs/glob.go new file mode 100644 index 000000000..e4115ea7c --- /dev/null +++ b/hugofs/glob.go @@ -0,0 +1,85 @@ +// Copyright 2019 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 hugofs + +import ( + "errors" + "path/filepath" + "strings" + + "github.com/gohugoio/hugo/hugofs/glob" + + "github.com/spf13/afero" +) + +// Glob walks the fs and passes all matches to the handle func. +// The handle func can return true to signal a stop. +func Glob(fs afero.Fs, pattern string, handle func(fi FileMetaInfo) (bool, error)) error { + pattern = glob.NormalizePath(pattern) + if pattern == "" { + return nil + } + + g, err := glob.GetGlob(pattern) + if err != nil { + return nil + } + + hasSuperAsterisk := strings.Contains(pattern, "**") + levels := strings.Count(pattern, "/") + root := glob.ResolveRootDir(pattern) + + // Signals that we're done. + done := errors.New("done") + + wfn := func(p string, info FileMetaInfo, err error) error { + p = glob.NormalizePath(p) + if info.IsDir() { + if !hasSuperAsterisk { + // Avoid walking to the bottom if we can avoid it. + if p != "" && strings.Count(p, "/") >= levels { + return filepath.SkipDir + } + } + return nil + } + + if g.Match(p) { + d, err := handle(info) + if err != nil { + return err + } + if d { + return done + } + } + + return nil + } + + w := NewWalkway(WalkwayConfig{ + Root: root, + Fs: fs, + WalkFn: wfn, + }) + + err = w.Walk() + + if err != done { + return err + } + + return nil + +} diff --git a/hugofs/glob/glob.go b/hugofs/glob/glob.go new file mode 100644 index 000000000..18d8d44cb --- /dev/null +++ b/hugofs/glob/glob.go @@ -0,0 +1,81 @@ +// Copyright 2019 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 glob + +import ( + "path" + "path/filepath" + "strings" + "sync" + + "github.com/gobwas/glob" + "github.com/gobwas/glob/syntax" +) + +var ( + globCache = make(map[string]glob.Glob) + globMu sync.RWMutex +) + +func GetGlob(pattern string) (glob.Glob, error) { + var g glob.Glob + + globMu.RLock() + g, found := globCache[pattern] + globMu.RUnlock() + if !found { + var err error + g, err = glob.Compile(strings.ToLower(pattern), '/') + if err != nil { + return nil, err + } + + globMu.Lock() + globCache[pattern] = g + globMu.Unlock() + } + + return g, nil + +} + +func NormalizePath(p string) string { + return strings.Trim(filepath.ToSlash(strings.ToLower(p)), "/.") +} + +// ResolveRootDir takes a normalized path on the form "assets/**.json" and +// determines any root dir, i.e. any start path without any wildcards. +func ResolveRootDir(p string) string { + parts := strings.Split(path.Dir(p), "/") + var roots []string + for _, part := range parts { + isSpecial := false + for i := 0; i < len(part); i++ { + if syntax.Special(part[i]) { + isSpecial = true + break + } + } + if isSpecial { + break + } + roots = append(roots, part) + } + + if len(roots) == 0 { + return "" + } + + return strings.Join(roots, "/") +} diff --git a/hugofs/glob/glob_test.go b/hugofs/glob/glob_test.go new file mode 100644 index 000000000..2b1c74107 --- /dev/null +++ b/hugofs/glob/glob_test.go @@ -0,0 +1,63 @@ +// Copyright 2019 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 glob + +import ( + "path/filepath" + "testing" + + qt "github.com/frankban/quicktest" +) + +func TestResolveRootDir(t *testing.T) { + c := qt.New(t) + + for _, test := range []struct { + in string + expect string + }{ + {"data/foo.json", "data"}, + {"a/b/**/foo.json", "a/b"}, + {"dat?a/foo.json", ""}, + {"a/b[a-c]/foo.json", "a"}, + } { + + c.Assert(ResolveRootDir(test.in), qt.Equals, test.expect) + } +} + +func TestNormalizePath(t *testing.T) { + c := qt.New(t) + + for _, test := range []struct { + in string + expect string + }{ + {filepath.FromSlash("data/FOO.json"), "data/foo.json"}, + {filepath.FromSlash("/data/FOO.json"), "data/foo.json"}, + {filepath.FromSlash("./FOO.json"), "foo.json"}, + {"//", ""}, + } { + + c.Assert(NormalizePath(test.in), qt.Equals, test.expect) + } +} + +func TestGetGlob(t *testing.T) { + c := qt.New(t) + g, err := GetGlob("**.JSON") + c.Assert(err, qt.IsNil) + c.Assert(g.Match("data/my.json"), qt.Equals, true) + +} diff --git a/hugofs/glob_test.go b/hugofs/glob_test.go new file mode 100644 index 000000000..3c7780685 --- /dev/null +++ b/hugofs/glob_test.go @@ -0,0 +1,61 @@ +// Copyright 2019 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 hugofs + +import ( + "path/filepath" + "testing" + + "github.com/spf13/afero" + + qt "github.com/frankban/quicktest" +) + +func TestGlob(t *testing.T) { + c := qt.New(t) + + fs := NewBaseFileDecorator(afero.NewMemMapFs()) + + create := func(filename string) { + err := afero.WriteFile(fs, filepath.FromSlash(filename), []byte("content "+filename), 0777) + c.Assert(err, qt.IsNil) + } + + collect := func(pattern string) []string { + var paths []string + h := func(fi FileMetaInfo) (bool, error) { + paths = append(paths, fi.Meta().Path()) + return false, nil + } + err := Glob(fs, pattern, h) + c.Assert(err, qt.IsNil) + return paths + } + + create("root.json") + create("jsonfiles/d1.json") + create("jsonfiles/d2.json") + create("jsonfiles/sub/d3.json") + create("jsonfiles/d1.xml") + create("a/b/c/e/f.json") + + c.Assert(collect("**.json"), qt.HasLen, 5) + c.Assert(collect("**"), qt.HasLen, 6) + c.Assert(collect(""), qt.HasLen, 0) + c.Assert(collect("jsonfiles/*.json"), qt.HasLen, 2) + c.Assert(collect("*.json"), qt.HasLen, 1) + c.Assert(collect("**.xml"), qt.HasLen, 1) + c.Assert(collect(filepath.FromSlash("/jsonfiles/*.json")), qt.HasLen, 2) + +} -- cgit v1.2.3