Rewrite pathtools.Glob to track partially-matched directories
The directory structure: a/ a b/ b With the glob pattern */a would previously return []string{"a"} for dirs, but it needs to return []string{"a", "b"} in order to re-run the generator if a file called "a" is created inside b/. Rewrite Glob to manually recurse through path elements, only calling filepath.Glob for a pattern with wilds in the last element of the path, and add the globbed directory to the dirs list each time. Also add tests and test data for pathtools.Glob. Change-Id: Ibbdb2f99809ea0826d4fa82066cf84103005ef57
This commit is contained in:
parent
63d5d4d9e4
commit
4b793e5798
11 changed files with 186 additions and 23 deletions
|
@ -15,6 +15,7 @@
|
|||
package pathtools
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
@ -22,38 +23,44 @@ import (
|
|||
// Glob returns the list of files that match the given pattern along with the
|
||||
// list of directories that were searched to construct the file list.
|
||||
func Glob(pattern string) (matches, dirs []string, err error) {
|
||||
matches, err = filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
if !isWild(pattern) {
|
||||
// If there are no wilds in the pattern, just return whether the file at the pattern
|
||||
// exists or not. Uses filepath.Glob instead of manually statting to get consistent
|
||||
// results.
|
||||
matches, err = filepath.Glob(filepath.Clean(pattern))
|
||||
return matches, dirs, err
|
||||
}
|
||||
|
||||
wildIndices := wildElements(pattern)
|
||||
|
||||
if len(wildIndices) > 0 {
|
||||
for _, match := range matches {
|
||||
dir := filepath.Dir(match)
|
||||
dirElems := strings.Split(dir, string(filepath.Separator))
|
||||
|
||||
for _, index := range wildIndices {
|
||||
dirs = append(dirs, strings.Join(dirElems[:index],
|
||||
string(filepath.Separator)))
|
||||
dir, file := saneSplit(pattern)
|
||||
dirMatches, dirs, err := Glob(dir)
|
||||
for _, m := range dirMatches {
|
||||
if info, _ := os.Stat(m); info.IsDir() {
|
||||
dirs = append(dirs, m)
|
||||
newMatches, err := filepath.Glob(filepath.Join(m, file))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
matches = append(matches, newMatches...)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return matches, dirs, nil
|
||||
}
|
||||
|
||||
func wildElements(pattern string) []int {
|
||||
elems := strings.Split(pattern, string(filepath.Separator))
|
||||
|
||||
var result []int
|
||||
for i, elem := range elems {
|
||||
if isWild(elem) {
|
||||
result = append(result, i)
|
||||
}
|
||||
// Faster version of dir, file := filepath.Dir(path), filepath.File(path)
|
||||
// Similar to filepath.Split, but returns "." if dir is empty and trims trailing slash if dir is
|
||||
// not "/"
|
||||
func saneSplit(path string) (dir, file string) {
|
||||
dir, file = filepath.Split(path)
|
||||
switch dir {
|
||||
case "":
|
||||
dir = "."
|
||||
case "/":
|
||||
// Nothing
|
||||
default:
|
||||
dir = dir[:len(dir)-1]
|
||||
}
|
||||
return result
|
||||
return dir, file
|
||||
}
|
||||
|
||||
func isWild(pattern string) bool {
|
||||
|
|
156
pathtools/glob_test.go
Normal file
156
pathtools/glob_test.go
Normal file
|
@ -0,0 +1,156 @@
|
|||
// Copyright 2014 Google Inc. 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 pathtools
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var pwd, _ = os.Getwd()
|
||||
|
||||
var globTestCases = []struct {
|
||||
pattern string
|
||||
matches []string
|
||||
dirs []string
|
||||
}{
|
||||
// Current directory tests
|
||||
{
|
||||
pattern: "*",
|
||||
matches: []string{"a", "b", "c", "d.ext", "e.ext"},
|
||||
dirs: []string{"."},
|
||||
},
|
||||
{
|
||||
pattern: "*.ext",
|
||||
matches: []string{"d.ext", "e.ext"},
|
||||
dirs: []string{"."},
|
||||
},
|
||||
{
|
||||
pattern: "*/a",
|
||||
matches: []string{"a/a", "b/a"},
|
||||
dirs: []string{".", "a", "b", "c"},
|
||||
},
|
||||
{
|
||||
pattern: "*/*/a",
|
||||
matches: []string{"a/a/a"},
|
||||
dirs: []string{".", "a", "b", "c", "a/a", "a/b", "c/f", "c/g", "c/h"},
|
||||
},
|
||||
{
|
||||
pattern: "*/a/a",
|
||||
matches: []string{"a/a/a"},
|
||||
dirs: []string{".", "a", "b", "c", "a/a"},
|
||||
},
|
||||
|
||||
// ./ directory tests
|
||||
{
|
||||
pattern: "./*",
|
||||
matches: []string{"a", "b", "c", "d.ext", "e.ext"},
|
||||
dirs: []string{"."},
|
||||
},
|
||||
{
|
||||
pattern: "./*.ext",
|
||||
matches: []string{"d.ext", "e.ext"},
|
||||
dirs: []string{"."},
|
||||
},
|
||||
{
|
||||
pattern: "./*/a",
|
||||
matches: []string{"a/a", "b/a"},
|
||||
dirs: []string{".", "a", "b", "c"},
|
||||
},
|
||||
{
|
||||
pattern: "./[ac]/a",
|
||||
matches: []string{"a/a"},
|
||||
dirs: []string{".", "a", "c"},
|
||||
},
|
||||
|
||||
// subdirectory tests
|
||||
{
|
||||
pattern: "c/*/*.ext",
|
||||
matches: []string{"c/f/f.ext", "c/g/g.ext"},
|
||||
dirs: []string{"c", "c/f", "c/g", "c/h"},
|
||||
},
|
||||
{
|
||||
pattern: "a/*/a",
|
||||
matches: []string{"a/a/a"},
|
||||
dirs: []string{"a", "a/a", "a/b"},
|
||||
},
|
||||
|
||||
// absolute tests
|
||||
{
|
||||
pattern: filepath.Join(pwd, "testdata/c/*/*.ext"),
|
||||
matches: []string{
|
||||
filepath.Join(pwd, "testdata/c/f/f.ext"),
|
||||
filepath.Join(pwd, "testdata/c/g/g.ext"),
|
||||
},
|
||||
dirs: []string{
|
||||
filepath.Join(pwd, "testdata/c"),
|
||||
filepath.Join(pwd, "testdata/c/f"),
|
||||
filepath.Join(pwd, "testdata/c/g"),
|
||||
filepath.Join(pwd, "testdata/c/h"),
|
||||
},
|
||||
},
|
||||
|
||||
// no-wild tests
|
||||
{
|
||||
pattern: "a",
|
||||
matches: []string{"a"},
|
||||
dirs: nil,
|
||||
},
|
||||
{
|
||||
pattern: "a/a",
|
||||
matches: []string{"a/a"},
|
||||
dirs: nil,
|
||||
},
|
||||
|
||||
// clean tests
|
||||
{
|
||||
pattern: "./c/*/*.ext",
|
||||
matches: []string{"c/f/f.ext", "c/g/g.ext"},
|
||||
dirs: []string{"c", "c/f", "c/g", "c/h"},
|
||||
},
|
||||
{
|
||||
pattern: "c/../c/*/*.ext",
|
||||
matches: []string{"c/f/f.ext", "c/g/g.ext"},
|
||||
dirs: []string{"c", "c/f", "c/g", "c/h"},
|
||||
},
|
||||
}
|
||||
|
||||
func TestGlob(t *testing.T) {
|
||||
os.Chdir("testdata")
|
||||
defer os.Chdir("..")
|
||||
for _, testCase := range globTestCases {
|
||||
matches, dirs, err := Glob(testCase.pattern)
|
||||
if err != nil {
|
||||
t.Errorf(" pattern: %q", testCase.pattern)
|
||||
t.Errorf(" error: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(matches, testCase.matches) {
|
||||
t.Errorf("incorrect matches list:")
|
||||
t.Errorf(" pattern: %q", testCase.pattern)
|
||||
t.Errorf(" got: %#v", matches)
|
||||
t.Errorf("expected: %#v", testCase.matches)
|
||||
}
|
||||
if !reflect.DeepEqual(dirs, testCase.dirs) {
|
||||
t.Errorf("incorrect dirs list:")
|
||||
t.Errorf(" pattern: %q", testCase.pattern)
|
||||
t.Errorf(" got: %#v", dirs)
|
||||
t.Errorf("expected: %#v", testCase.dirs)
|
||||
}
|
||||
}
|
||||
}
|
0
pathtools/testdata/a/a/a
vendored
Normal file
0
pathtools/testdata/a/a/a
vendored
Normal file
0
pathtools/testdata/a/b/b
vendored
Normal file
0
pathtools/testdata/a/b/b
vendored
Normal file
0
pathtools/testdata/b/a
vendored
Normal file
0
pathtools/testdata/b/a
vendored
Normal file
0
pathtools/testdata/c/c
vendored
Normal file
0
pathtools/testdata/c/c
vendored
Normal file
0
pathtools/testdata/c/f/f.ext
vendored
Normal file
0
pathtools/testdata/c/f/f.ext
vendored
Normal file
0
pathtools/testdata/c/g/g.ext
vendored
Normal file
0
pathtools/testdata/c/g/g.ext
vendored
Normal file
0
pathtools/testdata/c/h/h
vendored
Normal file
0
pathtools/testdata/c/h/h
vendored
Normal file
0
pathtools/testdata/d.ext
vendored
Normal file
0
pathtools/testdata/d.ext
vendored
Normal file
0
pathtools/testdata/e.ext
vendored
Normal file
0
pathtools/testdata/e.ext
vendored
Normal file
Loading…
Reference in a new issue