Skip to content

Commit 25a2922

Browse files
author
Arnold Trakhtenberg
authored
support glob patterns for filePattern alias matching (#113)
1 parent ce2d13c commit 25a2922

6 files changed

Lines changed: 101 additions & 48 deletions

File tree

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,17 @@ aliases:
243243

244244
#### Search a file for a specific pattern
245245

246-
Specify a file to search, with a regular expression containing a capture group to match aliases. The pattern must contain the templated literal `FLAG_KEY` to be replaced with flag keys.
246+
Specify a number of files (`paths`) using [glob patterns](https://en.wikipedia.org/wiki/Glob_(programming)) to search. Be as specific as possible with your path globs to minimize the number of files searched for aliases.
247247

248-
Example matching all variable names storing flag keys of the form `MyFlagKey := "my-flag"` in the file `test.go`:
248+
You must also specify a regular expression (`pattern`) containing a capture group to match aliases. The pattern must contain the the text `FLAG_KEY`, which will be subsituted with flag keys.
249+
250+
Example matching all variable names storing flag keys of the form `MyFlagKey := "my-flag"` in .go files do not end with `_test`:
249251

250252
```yaml
251253
aliases:
252254
- type: filePattern
253-
path: test.go
255+
paths:
256+
- '*[!_test].go'
254257
pattern: (\w+Key) := "FLAG_KEY"
255258
```
256259

internal/helpers/helpers.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package helpers
2+
3+
func Dedupe(s []string) []string {
4+
keys := make(map[string]struct{}, len(s))
5+
ret := make([]string, 0, len(s))
6+
for _, entry := range s {
7+
if _, value := keys[entry]; !value {
8+
keys[entry] = struct{}{}
9+
ret = append(ret, entry)
10+
}
11+
}
12+
return ret
13+
}

internal/options/alias.go

Lines changed: 76 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,15 @@ import (
99
"os/exec"
1010
"path/filepath"
1111
"regexp"
12+
"strconv"
1213
"strings"
1314
"time"
1415

1516
"github.com/iancoleman/strcase"
16-
"github.com/launchdarkly/ld-find-code-refs/internal/validation"
1717
"gopkg.in/yaml.v2"
18+
19+
"github.com/launchdarkly/ld-find-code-refs/internal/helpers"
20+
"github.com/launchdarkly/ld-find-code-refs/internal/validation"
1821
)
1922

2023
// We're using multiple packages for configuration.
@@ -53,7 +56,7 @@ func (a Alias) Generate(flag string) ([]string, error) {
5356
ret = []string{strcase.ToDelimited(flag, '.')}
5457
case FilePattern:
5558
pattern := regexp.MustCompile(strings.ReplaceAll(*a.Pattern, "FLAG_KEY", flag))
56-
results := pattern.FindAllStringSubmatch(string(a.FileContents), -1)
59+
results := pattern.FindAllStringSubmatch(string(a.AllFileContents), -1)
5760
for _, res := range results {
5861
if len(res) > 1 {
5962
ret = append(ret, res[1:]...)
@@ -108,14 +111,18 @@ const (
108111
// TODO: can we use OOP?
109112
type Alias struct {
110113
Type AliasType `yaml:"type"`
114+
Name string `yaml:"name"`
111115

112116
// Literal
113117
Flags map[string][]string `yaml:"flags,omitempty"`
114118

119+
// TODO: Remove this field - it has been superceded by Paths and should be removed
120+
Path *string `yaml:"path"`
121+
115122
// FilePattern
116-
Path *string `yaml:"path,omitempty"`
117-
Pattern *string `yaml:"pattern,omitempty"`
118-
FileContents []byte `yaml:"-"` // data for pattern matching
123+
Paths []string `yaml:"paths,omitempty"`
124+
Pattern *string `yaml:"pattern,omitempty"`
125+
AllFileContents []byte `yaml:"-"` // data for pattern matching
119126

120127
// Command
121128
Command *string `yaml:"command,omitempty"`
@@ -127,16 +134,15 @@ func (a *Alias) IsValid() error {
127134
if err != nil {
128135
return err
129136
}
130-
131137
// Validate expected fields
132138
switch a.Type {
133139
case Literal:
134140
if a.Flags == nil {
135141
return errors.New("literal aliases must provide an 'flags'")
136142
}
137143
case FilePattern:
138-
if a.Path == nil {
139-
return errors.New("filePattern aliases must provide a 'path'")
144+
if len(a.Paths) == 0 && a.Path == nil {
145+
return errors.New("filePattern aliases must provide at least one path in 'paths'")
140146
}
141147
if a.Pattern == nil {
142148
return errors.New("filePattern aliases must provide a 'pattern'")
@@ -165,8 +171,8 @@ func (a *Alias) IsValid() error {
165171
unexpectedField = "flags"
166172
}
167173
case a.Type != FilePattern:
168-
if a.Path != nil {
169-
unexpectedField = "path"
174+
if len(a.Paths) > 0 {
175+
unexpectedField = "paths"
170176
}
171177
if a.Pattern != nil {
172178
unexpectedField = "pattern"
@@ -186,6 +192,62 @@ func (a *Alias) IsValid() error {
186192
return nil
187193
}
188194

195+
func (a *Alias) ProcessFileContent(idx int) error {
196+
if a.Type != FilePattern {
197+
return nil
198+
}
199+
200+
// TODO: Remove this block with release as the Path field is being removed
201+
if a.Path != nil {
202+
path := filepath.Join(Dir.Value(), filepath.Dir(*a.Path))
203+
absPath, err := validation.NormalizeAndValidatePath(path)
204+
if err != nil {
205+
return err
206+
}
207+
208+
absFile := filepath.Join(absPath, filepath.Base(*a.Path))
209+
if !validation.FileExists(absFile) {
210+
return fmt.Errorf("could not find file at path '%s'", absFile)
211+
}
212+
/* #nosec */
213+
data, err := ioutil.ReadFile(absFile)
214+
if err != nil {
215+
return fmt.Errorf("could not process file at path '%s': %v", absFile, err)
216+
}
217+
a.AllFileContents = data
218+
}
219+
220+
aliasId := strconv.Itoa(idx)
221+
if a.Name != "" {
222+
aliasId = a.Name
223+
}
224+
225+
paths := []string{}
226+
for _, glob := range a.Paths {
227+
absGlob := filepath.Join(Dir.Value(), glob)
228+
matches, err := filepath.Glob(absGlob)
229+
if err != nil {
230+
return fmt.Errorf("filePattern '%s': could not process path glob '%s'", aliasId, absGlob)
231+
}
232+
paths = append(paths, matches...)
233+
}
234+
paths = helpers.Dedupe(paths)
235+
a.AllFileContents = []byte{}
236+
237+
for _, path := range paths {
238+
if !validation.FileExists(path) {
239+
return fmt.Errorf("filePattern '%s': could not find file at path '%s'", aliasId, path)
240+
}
241+
/* #nosec */
242+
data, err := ioutil.ReadFile(path)
243+
if err != nil {
244+
return fmt.Errorf("filePattern '%s': could not process file at path '%s': %v", aliasId, path, err)
245+
}
246+
a.AllFileContents = append(a.AllFileContents, data...)
247+
}
248+
return nil
249+
}
250+
189251
type YamlOptions struct {
190252
Aliases []Alias `yaml:"aliases"`
191253
}
@@ -225,25 +287,11 @@ func Yaml() (*YamlOptions, error) {
225287
return nil, err
226288
}
227289
for i, a := range o.Aliases {
228-
if a.Path != nil {
229-
path := filepath.Join(Dir.Value(), filepath.Dir(*a.Path))
230-
absPath, err := validation.NormalizeAndValidatePath(path)
231-
if err != nil {
232-
return nil, err
233-
}
234-
235-
absFile := filepath.Join(absPath, filepath.Base(*a.Path))
236-
if !validation.FileExists(absFile) {
237-
return nil, fmt.Errorf("could not find file at path '%s'", absFile)
238-
}
239-
/* #nosec */
240-
data, err := ioutil.ReadFile(absFile)
241-
if err != nil {
242-
return nil, fmt.Errorf("could not process file at path '%s': %v", absFile, err)
243-
}
244-
a.FileContents = data
245-
o.Aliases[i] = a
290+
err = a.ProcessFileContent(i)
291+
if err != nil {
292+
return nil, err
246293
}
294+
o.Aliases[i] = a
247295
}
248296
return &o, nil
249297
}

pkg/coderefs/alias.go

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package coderefs
22

33
import (
4+
"github.com/launchdarkly/ld-find-code-refs/internal/helpers"
45
"github.com/launchdarkly/ld-find-code-refs/internal/options"
56
)
67

@@ -14,19 +15,7 @@ func generateAliases(flags []string, aliases []options.Alias) (map[string][]stri
1415
}
1516
ret[flag] = append(ret[flag], flagAliases...)
1617
}
17-
ret[flag] = dedupe(ret[flag])
18+
ret[flag] = helpers.Dedupe(ret[flag])
1819
}
1920
return ret, nil
2021
}
21-
22-
func dedupe(s []string) []string {
23-
keys := make(map[string]struct{}, len(s))
24-
ret := make([]string, 0, len(s))
25-
for _, entry := range s {
26-
if _, value := keys[entry]; !value {
27-
keys[entry] = struct{}{}
28-
ret = append(ret, entry)
29-
}
30-
}
31-
return ret
32-
}

pkg/coderefs/alias_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ func filePattern(flag string) o.Alias {
113113
a := alias(o.FilePattern)
114114
pattern := "(\\w+)\\s= 'FLAG_KEY'"
115115
a.Pattern = &pattern
116-
a.FileContents = []byte(fmt.Sprintf("SOME_FLAG = '%s'", flag))
116+
a.AllFileContents = []byte(fmt.Sprintf("SOME_FLAG = '%s'", flag))
117117
return a
118118
}
119119

pkg/coderefs/coderefs.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import (
1111
"time"
1212

1313
"github.com/launchdarkly/ld-find-code-refs/internal/command"
14+
"github.com/launchdarkly/ld-find-code-refs/internal/helpers"
1415
"github.com/launchdarkly/ld-find-code-refs/internal/ld"
1516
"github.com/launchdarkly/ld-find-code-refs/internal/log"
16-
"github.com/launchdarkly/ld-find-code-refs/internal/options"
1717
o "github.com/launchdarkly/ld-find-code-refs/internal/options"
1818
"github.com/launchdarkly/ld-find-code-refs/internal/validation"
1919
"github.com/launchdarkly/ld-find-code-refs/internal/version"
@@ -121,7 +121,7 @@ func Scan() {
121121
log.Warning.Printf("omitting %d flags with keys less than minimum (%d)", len(omittedFlags), minFlagKeyLen)
122122
}
123123

124-
aliases, err := generateAliases(filteredFlags, options.Aliases)
124+
aliases, err := generateAliases(filteredFlags, o.Aliases)
125125
if err != nil {
126126
log.Error.Fatalf("failed to create flag key aliases: %v", err)
127127
}
@@ -497,7 +497,7 @@ func buildHunksForFlag(projKey, flag, path string, flagReferences []*list.Elemen
497497
appendToPreviousHunk = false
498498
} else {
499499
currentHunk.Lines = hunkStringBuilder.String()
500-
currentHunk.Aliases = dedupe(currentHunk.Aliases)
500+
currentHunk.Aliases = helpers.Dedupe(currentHunk.Aliases)
501501
hunks = append(hunks, currentHunk)
502502
previousHunk = &hunks[len(hunks)-1]
503503
}

0 commit comments

Comments
 (0)