Skip to content

Commit

Permalink
fix: file paths on windows conflict with the ast escape rune so we ne…
Browse files Browse the repository at this point in the history
…ed to keep paths in the linux style (#12)
  • Loading branch information
djgilcrease committed Dec 8, 2020
1 parent b27e0ce commit f130ce4
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 11 deletions.
20 changes: 17 additions & 3 deletions glob.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import (
"github.com/spf13/afero"
)

const (
runeSeparator = '/'
stringSeparator = string(runeSeparator)
)

// FileSystem is meant to be used with WithFs.
type FileSystem afero.Fs

Expand Down Expand Up @@ -48,10 +53,16 @@ func QuoteMeta(pattern string) string {
return glob.QuoteMeta(pattern)
}

// toNixPath converts the path to the nix style path
// Windows style path separators are escape characters so cause issues with the compiled glob.
func toNixPath(path string) string {
return filepath.ToSlash(filepath.Clean(path))
}

// Glob returns all files that match the given pattern in the current directory.
func Glob(pattern string, opts ...OptFunc) ([]string, error) {
return doGlob(
strings.TrimPrefix(pattern, "./"),
strings.TrimPrefix(pattern, "."+stringSeparator),
compileOptions(opts),
)
}
Expand All @@ -60,7 +71,7 @@ func doGlob(pattern string, options *globOptions) ([]string, error) { // nolint:
var fs = options.fs
var matches []string

matcher, err := glob.Compile(pattern, filepath.Separator)
matcher, err := glob.Compile(pattern, runeSeparator)
if err != nil {
return matches, fmt.Errorf("compile glob pattern: %w", err)
}
Expand All @@ -76,7 +87,7 @@ func doGlob(pattern string, options *globOptions) ([]string, error) { // nolint:
// glob contains no dynamic matchers so prefix is the file name that
// the glob references directly. When the glob explicitly references
// a single non-existing file, return an error for the user to check.
return []string{}, fmt.Errorf("matching %q: %w", prefix, os.ErrNotExist)
return []string{}, fmt.Errorf(`matching "%s": %w`, prefix, os.ErrNotExist)
}

return []string{}, nil
Expand All @@ -100,6 +111,8 @@ func doGlob(pattern string, options *globOptions) ([]string, error) { // nolint:
return err
}

// The glob ast from github.com/gobwas/glob only works properly with linux paths
path = toNixPath(path)
if !matcher.Match(path) {
return nil
}
Expand Down Expand Up @@ -149,6 +162,7 @@ func filesInDirectory(fs afero.Fs, dir string) ([]string, error) {
if info.IsDir() {
return nil
}
path = toNixPath(path)
files = append(files, path)
return nil
})
Expand Down
25 changes: 25 additions & 0 deletions glob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (
)

func TestGlob(t *testing.T) { // nolint:funlen
t.Parallel()
t.Run("real", func(t *testing.T) {
t.Parallel()
matches, err := Glob("*_test.go")
require.NoError(t, err)
require.Equal(t, []string{
Expand All @@ -21,6 +23,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("simple", func(t *testing.T) {
t.Parallel()
matches, err := Glob("./a/*/*", WithFs(testFs(t, []string{
"./c/file1.txt",
"./a/nope/file1.txt",
Expand All @@ -39,6 +42,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("single file", func(t *testing.T) {
t.Parallel()
matches, err := Glob("a/b/*", WithFs(testFs(t, []string{
"./c/file1.txt",
"./a/nope/file1.txt",
Expand All @@ -49,6 +53,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("super asterisk", func(t *testing.T) {
t.Parallel()
matches, err := Glob("a/**/*", WithFs(testFs(t, []string{
"./a/nope.txt",
"./a/d/file1.txt",
Expand All @@ -64,6 +69,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("alternative matchers", func(t *testing.T) {
t.Parallel()
matches, err := Glob("a/{b,d}/file.txt", WithFs(testFs(t, []string{
"a/b/file.txt",
"a/c/file.txt",
Expand All @@ -77,6 +83,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("character list and range matchers", func(t *testing.T) {
t.Parallel()
matches, err := Glob("[!bc]/[a-z]/file[01].txt", WithFs(testFs(t, []string{
"a/b/file0.txt",
"a/c/file1.txt",
Expand All @@ -93,6 +100,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("nested matchers", func(t *testing.T) {
t.Parallel()
matches, err := Glob("{a,[0-9]b}.txt", WithFs(testFs(t, []string{
"a.txt",
"b.txt",
Expand All @@ -107,6 +115,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("single symbol wildcards", func(t *testing.T) {
t.Parallel()
matches, err := Glob("a?.txt", WithFs(testFs(t, []string{
"a.txt",
"a1.txt",
Expand All @@ -120,6 +129,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("direct match", func(t *testing.T) {
t.Parallel()
matches, err := Glob("a/b/c", WithFs(testFs(t, []string{
"./a/nope.txt",
"./a/b/c",
Expand All @@ -129,6 +139,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("direct match wildcard", func(t *testing.T) {
t.Parallel()
matches, err := Glob(QuoteMeta("a/b/c{a"), WithFs(testFs(t, []string{
"./a/nope.txt",
"a/b/c{a",
Expand All @@ -138,6 +149,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("direct no match", func(t *testing.T) {
t.Parallel()
matches, err := Glob("a/b/d", WithFs(testFs(t, []string{
"./a/nope.txt",
"./a/b/dc",
Expand All @@ -148,13 +160,15 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("escaped direct no match", func(t *testing.T) {
t.Parallel()
matches, err := Glob("a/\\{b\\}", WithFs(testFs(t, nil, nil)))
require.EqualError(t, err, "matching \"a/{b}\": file does not exist")
require.True(t, errors.Is(err, os.ErrNotExist))
require.Empty(t, matches)
})

t.Run("direct no match escaped wildcards", func(t *testing.T) {
t.Parallel()
matches, err := Glob(QuoteMeta("a/b/c{a"), WithFs(testFs(t, []string{
"./a/nope.txt",
"./a/b/dc",
Expand All @@ -164,6 +178,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("no matches", func(t *testing.T) {
t.Parallel()
matches, err := Glob("z/*", WithFs(testFs(t, []string{
"./a/nope.txt",
}, nil)))
Expand All @@ -172,12 +187,14 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("empty folder", func(t *testing.T) {
t.Parallel()
matches, err := Glob("a*", WithFs(testFs(t, nil, nil)))
require.NoError(t, err)
require.Empty(t, matches)
})

t.Run("escaped asterisk", func(t *testing.T) {
t.Parallel()
matches, err := Glob("a/\\*/b", WithFs(testFs(t, []string{
"a/a/b",
"a/*/b",
Expand All @@ -190,6 +207,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("escaped curly braces", func(t *testing.T) {
t.Parallel()
matches, err := Glob("\\{a,b\\}/c", WithFs(testFs(t, []string{
"a/c",
"b/c",
Expand All @@ -202,12 +220,14 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("invalid pattern", func(t *testing.T) {
t.Parallel()
matches, err := Glob("[*", WithFs(testFs(t, nil, nil)))
require.EqualError(t, err, "compile glob pattern: unexpected end of input")
require.Empty(t, matches)
})

t.Run("prefix is a file", func(t *testing.T) {
t.Parallel()
matches, err := Glob("ab/c/*", WithFs(testFs(t, []string{
"ab/c",
"ab/d",
Expand All @@ -218,6 +238,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("match files in directories", func(t *testing.T) {
t.Parallel()
matches, err := Glob("/a/{b,c}", WithFs(testFs(t, []string{
"/a/b/d",
"/a/b/e/f",
Expand All @@ -232,6 +253,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("match directories directly", func(t *testing.T) {
t.Parallel()
matches, err := Glob("/a/{b,c}", MatchDirectories(true), WithFs(testFs(t, []string{
"/a/b/d",
"/a/b/e/f",
Expand All @@ -245,6 +267,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("match empty directory", func(t *testing.T) {
t.Parallel()
matches, err := Glob("/a/{b,c}", MatchDirectories(true), WithFs(testFs(t, []string{
"/a/b",
}, []string{
Expand All @@ -258,6 +281,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
})

t.Run("pattern ending with star and subdir", func(t *testing.T) {
t.Parallel()
matches, err := Glob("a/*", WithFs(testFs(t, []string{
"./a/1.txt",
"./a/2.txt",
Expand All @@ -278,6 +302,7 @@ func TestGlob(t *testing.T) { // nolint:funlen
}

func TestQuoteMeta(t *testing.T) {
t.Parallel()
matches, err := Glob(QuoteMeta("{a,b}/c"), WithFs(testFs(t, []string{
"a/c",
"b/c",
Expand Down
16 changes: 8 additions & 8 deletions prefix.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package fileglob

import (
"fmt"
"path/filepath"
"strings"

"github.com/gobwas/glob/syntax/ast"
Expand Down Expand Up @@ -59,13 +58,10 @@ func staticText(node *ast.Node) (text string, ok bool) {
// staticPrefix returns the file path inside the pattern up
// to the first path element that contains a wildcard.
func staticPrefix(pattern string) (string, error) {
parts := strings.Split(pattern, string(filepath.Separator))

prefix := ""
if len(pattern) > 0 && rune(pattern[0]) == filepath.Separator {
prefix = string(filepath.Separator)
}
parts := strings.Split(pattern, stringSeparator)

// nolint:prealloc
var prefixPath []string
for _, part := range parts {
if part == "" {
continue
Expand All @@ -81,7 +77,11 @@ func staticPrefix(pattern string) (string, error) {
break
}

prefix = filepath.Join(prefix, staticPart)
prefixPath = append(prefixPath, staticPart)
}
prefix := strings.Join(prefixPath, stringSeparator)
if len(pattern) > 0 && rune(pattern[0]) == runeSeparator && !strings.HasPrefix(prefix, stringSeparator) {
prefix = stringSeparator + prefix
}

if prefix == "" {
Expand Down
4 changes: 4 additions & 0 deletions prefix_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
)

func TestStaticPrefix(t *testing.T) {
t.Parallel()
var testCases = []struct {
pattern string
prefix string
Expand All @@ -22,6 +23,7 @@ func TestStaticPrefix(t *testing.T) {
{"./", "."},
{"fo\\*o/bar/b*z", "fo*o/bar"},
{"/\\{foo\\}/bar", "/{foo}/bar"},
{"C:/Path/To/Some/File", "C:/Path/To/Some/File"},
}

for _, testCase := range testCases {
Expand All @@ -32,6 +34,7 @@ func TestStaticPrefix(t *testing.T) {
}

func TestContainsMatchers(t *testing.T) {
t.Parallel()
var testCases = []struct {
pattern string
containsMatchers bool
Expand All @@ -57,6 +60,7 @@ func TestContainsMatchers(t *testing.T) {
}

func TestValidPattern(t *testing.T) {
t.Parallel()
var testCases = []struct {
pattern string
valid bool
Expand Down

0 comments on commit f130ce4

Please sign in to comment.