From 4b793e5798ddfd797bfd8c8534e1e5262c44c7d6 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Thu, 23 Apr 2015 13:29:28 -0700 Subject: [PATCH] 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 --- pathtools/glob.go | 53 ++++++------ pathtools/glob_test.go | 156 +++++++++++++++++++++++++++++++++++ pathtools/testdata/a/a/a | 0 pathtools/testdata/a/b/b | 0 pathtools/testdata/b/a | 0 pathtools/testdata/c/c | 0 pathtools/testdata/c/f/f.ext | 0 pathtools/testdata/c/g/g.ext | 0 pathtools/testdata/c/h/h | 0 pathtools/testdata/d.ext | 0 pathtools/testdata/e.ext | 0 11 files changed, 186 insertions(+), 23 deletions(-) create mode 100644 pathtools/glob_test.go create mode 100644 pathtools/testdata/a/a/a create mode 100644 pathtools/testdata/a/b/b create mode 100644 pathtools/testdata/b/a create mode 100644 pathtools/testdata/c/c create mode 100644 pathtools/testdata/c/f/f.ext create mode 100644 pathtools/testdata/c/g/g.ext create mode 100644 pathtools/testdata/c/h/h create mode 100644 pathtools/testdata/d.ext create mode 100644 pathtools/testdata/e.ext diff --git a/pathtools/glob.go b/pathtools/glob.go index 0d96691..34e43b9 100644 --- a/pathtools/glob.go +++ b/pathtools/glob.go @@ -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 { diff --git a/pathtools/glob_test.go b/pathtools/glob_test.go new file mode 100644 index 0000000..6186c7f --- /dev/null +++ b/pathtools/glob_test.go @@ -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) + } + } +} diff --git a/pathtools/testdata/a/a/a b/pathtools/testdata/a/a/a new file mode 100644 index 0000000..e69de29 diff --git a/pathtools/testdata/a/b/b b/pathtools/testdata/a/b/b new file mode 100644 index 0000000..e69de29 diff --git a/pathtools/testdata/b/a b/pathtools/testdata/b/a new file mode 100644 index 0000000..e69de29 diff --git a/pathtools/testdata/c/c b/pathtools/testdata/c/c new file mode 100644 index 0000000..e69de29 diff --git a/pathtools/testdata/c/f/f.ext b/pathtools/testdata/c/f/f.ext new file mode 100644 index 0000000..e69de29 diff --git a/pathtools/testdata/c/g/g.ext b/pathtools/testdata/c/g/g.ext new file mode 100644 index 0000000..e69de29 diff --git a/pathtools/testdata/c/h/h b/pathtools/testdata/c/h/h new file mode 100644 index 0000000..e69de29 diff --git a/pathtools/testdata/d.ext b/pathtools/testdata/d.ext new file mode 100644 index 0000000..e69de29 diff --git a/pathtools/testdata/e.ext b/pathtools/testdata/e.ext new file mode 100644 index 0000000..e69de29