mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-27 22:09:53 +02:00
85
hugofs/glob.go
Normal file
85
hugofs/glob.go
Normal file
@@ -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
|
||||
|
||||
}
|
81
hugofs/glob/glob.go
Normal file
81
hugofs/glob/glob.go
Normal file
@@ -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, "/")
|
||||
}
|
63
hugofs/glob/glob_test.go
Normal file
63
hugofs/glob/glob_test.go
Normal file
@@ -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)
|
||||
|
||||
}
|
61
hugofs/glob_test.go
Normal file
61
hugofs/glob_test.go
Normal file
@@ -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)
|
||||
|
||||
}
|
Reference in New Issue
Block a user