Skip to content

Commit

Permalink
Add support for globs in FROM clause.
Browse files Browse the repository at this point in the history
For #7.

Currently only supports `?` and `*`, will need to slightly refactor
tokenizer to add support for `[]` as well.
  • Loading branch information
kashav committed Jun 6, 2017
1 parent 65350f6 commit c1f275b
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 35 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Each group features a set of equivalent clauses.

Each source should be a relative or absolute path to a directory on your machine.

Source paths may include environment variables (e.g. `$GOPATH`) or tildes (`~`). Use a hyphen (`-`) to exclude a directory.
Source paths may include environment variables (e.g. `$GOPATH`) or tildes (`~`). Use a hyphen (`-`) to exclude a directory. Source paths also support usage of [glob patterns](https://en.wikipedia.org/wiki/Glob_(programming)).

In the case that a directory begins with a hypgen (e.g. `-foo`), use the following to include it as a source:

Expand All @@ -114,11 +114,11 @@ In the case that a directory begins with a hypgen (e.g. `-foo`), use the followi
```

```console
>>> ... FROM ., ~/Desktop ...
>>> ... FROM ~/Desktop, ./*/**.go ...
```

```console
>>> ... FROM ~/Desktop, $GOPATH, -.git/ ...
>>> ... FROM $GOPATH, -.git/ ...
```

### Condition
Expand Down
10 changes: 5 additions & 5 deletions query/excluder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ import (

// Excluder allows us to support different methods of excluding in the future.
type Excluder interface {
ShouldExclude(path string) bool
shouldExclude(path string) bool
}

// RegexpExclude uses regular expressions to tell if a file/path should be
// regexpExclude uses regular expressions to tell if a file/path should be
// excluded.
type RegexpExclude struct {
type regexpExclude struct {
exclusions []string
regex *regexp.Regexp
}

// ShouldExclude will return a boolean denoting whether or not the path should
// be excluded based on the given slice of exclusions.
func (r *RegexpExclude) ShouldExclude(path string) bool {
func (r *regexpExclude) shouldExclude(path string) bool {
if r.regex == nil {
r.buildRegex()
}
Expand All @@ -31,7 +31,7 @@ func (r *RegexpExclude) ShouldExclude(path string) bool {
}

// buildRegex builds the regular expression for this RegexpExclude.
func (r *RegexpExclude) buildRegex() {
func (r *regexpExclude) buildRegex() {
exclusions := make([]string, len(r.exclusions))
for i, exclusion := range r.exclusions {
// Wrap exclusion in ^ and (/.*)?$ AFTER trimming trailing slashes and
Expand Down
8 changes: 4 additions & 4 deletions query/excluder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type ExcluderCase struct {

func TestShouldExclude_ExpectAllExcluded(t *testing.T) {
exclusions := []string{".git", ".gitignore"}
excluder := RegexpExclude{exclusions: exclusions}
excluder := regexpExclude{exclusions: exclusions}
cases := []ExcluderCase{
{".git", true},
{".git/", true},
Expand All @@ -18,7 +18,7 @@ func TestShouldExclude_ExpectAllExcluded(t *testing.T) {
}

for _, c := range cases {
actual := excluder.ShouldExclude(c.input)
actual := excluder.shouldExclude(c.input)
if actual != c.expected {
t.Fatalf("\nExpected %v\n Got %v", c.expected, actual)
}
Expand All @@ -27,7 +27,7 @@ func TestShouldExclude_ExpectAllExcluded(t *testing.T) {

func TestShouldExclude_ExpectNotExcluded(t *testing.T) {
exclusions := []string{".git"}
excluder := RegexpExclude{exclusions: exclusions}
excluder := regexpExclude{exclusions: exclusions}
cases := []ExcluderCase{
{".git", true},
{".git/", true},
Expand All @@ -36,7 +36,7 @@ func TestShouldExclude_ExpectNotExcluded(t *testing.T) {
}

for _, c := range cases {
actual := excluder.ShouldExclude(c.input)
actual := excluder.shouldExclude(c.input)
if actual != c.expected {
t.Fatalf("\nExpected %v\n Got %v", c.expected, actual)
}
Expand Down
73 changes: 50 additions & 23 deletions query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package query
import (
"os"
"path/filepath"
"strings"
)

// Query represents an input query.
Expand Down Expand Up @@ -44,42 +45,68 @@ func (q *Query) HasAttribute(attributes ...string) bool {
// evaluating the condition tree for each file. This method calls workFunc on
// each "successful" file.
func (q *Query) Execute(workFunc interface{}) error {
seen := make(map[string]bool)
excluder := &RegexpExclude{exclusions: q.Sources["exclude"]}
seen := map[string]bool{}
excluder := &regexpExclude{exclusions: q.Sources["exclude"]}

for _, src := range q.Sources["include"] {
err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
// TODO: Improve our method of detecting if src is a glob pattern. This
// currently doesn't support usage of square brackets, since the tokenizer
// doesn't recognize these as part of a directory.
//
// Pattern reference: https://golang.org/pkg/path/filepath/#Match.
if strings.ContainsAny(src, "*?") {
// If src does _resemble_ a glob pattern, we find all matches and
// evaluate the condition tree against each.
matches, err := filepath.Glob(src)
if err != nil {
return err
}

if path == "." {
return nil
for _, match := range matches {
if err = filepath.Walk(match, q.walkFunc(seen, excluder, workFunc)); err != nil {
return err
}
}
continue
}

// Avoid walking a single directory more than once.
if _, ok := seen[path]; ok {
return nil
}
seen[path] = true
if err := filepath.Walk(src, q.walkFunc(seen, excluder, workFunc)); err != nil {
return err
}
}

if excluder.ShouldExclude(path) {
return nil
}
return nil
}

if !q.ConditionTree.evaluateTree(path, info) {
return nil
}
// walkFunc returns a filepath.WalkFunc which evaluates the condition tree
// against the given file.
func (q *Query) walkFunc(seen map[string]bool, excluder Excluder,
workFunc interface{}) filepath.WalkFunc {
return func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}

results := q.applyModifiers(path, info)
workFunc.(func(string, os.FileInfo, map[string]interface{}))(path, info, results)
if path == "." {
return nil
})
}

if err != nil {
return err
// Avoid walking a single directory more than once.
if _, ok := seen[path]; ok {
return nil
}
}
seen[path] = true

return nil
if excluder.shouldExclude(path) {
return nil
}

if !q.ConditionTree.evaluateTree(path, info) {
return nil
}

results := q.applyModifiers(path, info)
workFunc.(func(string, os.FileInfo, map[string]interface{}))(path, info, results)
return nil
}
}

0 comments on commit c1f275b

Please sign in to comment.