Skip to content

Commit 6ba772a

Browse files
committed
Extract glob patters to internal package
1 parent 76aa940 commit 6ba772a

File tree

3 files changed

+155
-59
lines changed

3 files changed

+155
-59
lines changed

fsutil/glob.go

Lines changed: 7 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import (
44
"net/http"
55
"os"
66
"path/filepath"
7-
"strings"
87

9-
"github.com/pkg/errors"
8+
globutil "github.com/posener/gitfs/internal/glob"
109
)
1110

1211
// Glob return a filesystem that contain only files that match any of the provided
@@ -16,10 +15,11 @@ func Glob(fs http.FileSystem, patterns ...string) (http.FileSystem, error) {
1615
if len(patterns) == 0 {
1716
return fs, nil
1817
}
19-
if err := checkPatterns(patterns...); err != nil {
18+
p, err := globutil.New(patterns...)
19+
if err != nil {
2020
return nil, err
2121
}
22-
return &glob{FileSystem: fs, patterns: patterns}, nil
22+
return &glob{FileSystem: fs, patterns: p}, nil
2323
}
2424

2525
// glob is an object that play the role of an http.FileSystem and an http.File.
@@ -29,7 +29,7 @@ type glob struct {
2929
http.FileSystem
3030
http.File
3131
root string
32-
patterns []string
32+
patterns globutil.Patterns
3333
}
3434

3535
// Open a file, relative to root. If the file exists in the filesystem
@@ -48,7 +48,7 @@ func (g *glob) Open(name string) (http.File, error) {
4848
}
4949

5050
// Regular file, match name.
51-
if !g.match(path, info.IsDir()) {
51+
if !g.patterns.Match(path, info.IsDir()) {
5252
return nil, os.ErrNotExist
5353
}
5454
return &glob{
@@ -68,61 +68,9 @@ func (g *glob) Readdir(count int) ([]os.FileInfo, error) {
6868
ret := make([]os.FileInfo, 0, len(files))
6969
for _, file := range files {
7070
path := filepath.Join(g.root, file.Name())
71-
if g.match(path, file.IsDir()) {
71+
if g.patterns.Match(path, file.IsDir()) {
7272
ret = append(ret, file)
7373
}
7474
}
7575
return ret, nil
7676
}
77-
78-
// match a path to the defined patterns. If it is a file a full match
79-
// is required. If it is a directory, only matching a prefix of any of
80-
// the patterns is required.
81-
func (g *glob) match(path string, isDir bool) bool {
82-
return (isDir && g.matchPrefix(path)) || (!isDir && g.matchFull(path))
83-
}
84-
85-
// matchFull finds a matching of the whole name to any of the patterns.
86-
func (g *glob) matchFull(name string) bool {
87-
for _, pattern := range g.patterns {
88-
if ok, _ := filepath.Match(pattern, name); ok {
89-
return true
90-
}
91-
}
92-
return false
93-
}
94-
95-
// matchPrefix finds a matching of prefix to a prefix of any of the patterns.
96-
func (g *glob) matchPrefix(prefix string) bool {
97-
parts := strings.Split(prefix, string(filepath.Separator))
98-
nextPattern:
99-
for _, pattern := range g.patterns {
100-
patternParts := strings.Split(pattern, string(filepath.Separator))
101-
if len(patternParts) < len(parts) {
102-
continue
103-
}
104-
for i := 0; i < len(parts); i++ {
105-
if ok, _ := filepath.Match(patternParts[i], parts[i]); !ok {
106-
continue nextPattern
107-
}
108-
}
109-
return true
110-
}
111-
return false
112-
}
113-
114-
// checkPattens checks the validity of the patterns.
115-
func checkPatterns(patterns ...string) error {
116-
var badPatterns []string
117-
for _, pattern := range patterns {
118-
_, err := filepath.Match(pattern, "x")
119-
if err != nil {
120-
badPatterns = append(badPatterns, pattern)
121-
return errors.Wrap(err, pattern)
122-
}
123-
}
124-
if len(badPatterns) > 0 {
125-
return errors.Wrap(filepath.ErrBadPattern, strings.Join(badPatterns, ", "))
126-
}
127-
return nil
128-
}

internal/glob/glob.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package glob
2+
3+
import (
4+
"path/filepath"
5+
"strings"
6+
7+
"github.com/pkg/errors"
8+
)
9+
10+
// Patterns can glob-match files or directories.
11+
type Patterns []string
12+
13+
// New returns a new glob pattern. It returns an error if any of the
14+
// patterns is invalid.
15+
func New(patterns ...string) (Patterns, error) {
16+
if err := checkPatterns(patterns); err != nil {
17+
return nil, err
18+
}
19+
return Patterns(patterns), nil
20+
}
21+
22+
// Match a path to the defined patterns. If it is a file a full match
23+
// is required. If it is a directory, only matching a prefix of any of
24+
// the patterns is required.
25+
func (p Patterns) Match(path string, isDir bool) bool {
26+
path = filepath.Clean(path)
27+
return (isDir && p.matchPrefix(path)) || (!isDir && p.matchFull(path))
28+
}
29+
30+
// matchFull finds a matching of the whole name to any of the patterns.
31+
func (p Patterns) matchFull(name string) bool {
32+
for _, pattern := range p {
33+
if ok, _ := filepath.Match(pattern, name); ok {
34+
return true
35+
}
36+
}
37+
return false
38+
}
39+
40+
// matchPrefix finds a matching of prefix to a prefix of any of the patterns.
41+
func (p Patterns) matchPrefix(prefix string) bool {
42+
parts := strings.Split(prefix, string(filepath.Separator))
43+
nextPattern:
44+
for _, pattern := range p {
45+
patternParts := strings.Split(pattern, string(filepath.Separator))
46+
if len(patternParts) < len(parts) {
47+
continue
48+
}
49+
for i := 0; i < len(parts); i++ {
50+
if ok, _ := filepath.Match(patternParts[i], parts[i]); !ok {
51+
continue nextPattern
52+
}
53+
}
54+
return true
55+
}
56+
return false
57+
}
58+
59+
// checkPattens checks the validity of the patterns.
60+
func checkPatterns(patterns []string) error {
61+
var badPatterns []string
62+
for _, pattern := range patterns {
63+
_, err := filepath.Match(pattern, "x")
64+
if err != nil {
65+
badPatterns = append(badPatterns, pattern)
66+
return errors.Wrap(err, pattern)
67+
}
68+
}
69+
if len(badPatterns) > 0 {
70+
return errors.Wrap(filepath.ErrBadPattern, strings.Join(badPatterns, ", "))
71+
}
72+
return nil
73+
}

internal/glob/glob_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package glob
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestMatch(t *testing.T) {
11+
t.Parallel()
12+
tests := []struct {
13+
pattern []string
14+
name string
15+
isDir bool
16+
}{
17+
{pattern: []string{"foo"}, name: "foo"},
18+
{pattern: []string{"*"}, name: "foo"},
19+
{pattern: []string{"foo"}, name: "./foo"},
20+
{pattern: []string{"foo"}, name: "foo/"},
21+
{pattern: []string{"foo"}, name: "./foo/"},
22+
{pattern: []string{"foo", "bar"}, name: "foo"},
23+
{pattern: []string{"bar", "foo"}, name: "foo"},
24+
{pattern: []string{"*/*"}, name: "foo/bar"},
25+
{pattern: []string{"*/*"}, name: "./foo/bar"},
26+
{pattern: []string{"*/*"}, name: "foo/bar/"},
27+
{pattern: []string{"*/*"}, name: "./foo/bar/"},
28+
{pattern: []string{"*/*"}, name: "foo", isDir: true},
29+
{pattern: []string{"*"}, name: "foo", isDir: true},
30+
{pattern: []string{"foo"}, name: "foo", isDir: true},
31+
}
32+
33+
for _, tt := range tests {
34+
p, err := New(tt.pattern...)
35+
require.NoError(t, err)
36+
assert.True(t, p.Match(tt.name, tt.isDir))
37+
}
38+
}
39+
40+
func TestMatch_noMatch(t *testing.T) {
41+
t.Parallel()
42+
tests := []struct {
43+
pattern []string
44+
name string
45+
isDir bool
46+
}{
47+
{pattern: []string{"f"}, name: "foo"},
48+
{pattern: []string{"f", "bar"}, name: "foo"},
49+
{pattern: []string{"bar", "f"}, name: "foo"},
50+
{pattern: []string{"*/*"}, name: "foo"},
51+
{pattern: []string{"*/*"}, name: "./foo"},
52+
{pattern: []string{"*/*"}, name: "foo/"},
53+
{pattern: []string{"*/*"}, name: "./foo/"},
54+
{pattern: []string{"*"}, name: "foo/bar"},
55+
{pattern: []string{"*"}, name: "./foo/bar"},
56+
{pattern: []string{"*"}, name: "foo/bar/"},
57+
{pattern: []string{"*"}, name: "./foo/bar/"},
58+
{pattern: []string{"*"}, name: "foo/bar", isDir: true},
59+
{pattern: []string{"*"}, name: "./foo/bar", isDir: true},
60+
{pattern: []string{"*"}, name: "foo/bar/", isDir: true},
61+
{pattern: []string{"*"}, name: "./foo/bar/", isDir: true},
62+
}
63+
64+
for _, tt := range tests {
65+
p, err := New(tt.pattern...)
66+
require.NoError(t, err)
67+
assert.False(t, p.Match(tt.name, tt.isDir))
68+
}
69+
}
70+
71+
func TestNew_badPattern(t *testing.T) {
72+
t.Parallel()
73+
_, err := New("[") // Missing closing bracket.
74+
assert.Error(t, err)
75+
}

0 commit comments

Comments
 (0)