Skip to content

Commit ad597b3

Browse files
committed
helm: copy internal ignore and sympath modules
We require these to be able to mimic Helm's own directory loader, and surprisingly (for `ignore` at least), these are not public. Signed-off-by: Hidde Beydals <[email protected]>
1 parent 8593d58 commit ad597b3

File tree

18 files changed

+723
-0
lines changed

18 files changed

+723
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
Copyright The Helm Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
/*Package ignore provides tools for writing ignore files (a la .gitignore).
18+
19+
This provides both an ignore parser and a file-aware processor.
20+
21+
The format of ignore files closely follows, but does not exactly match, the
22+
format for .gitignore files (https://git-scm.com/docs/gitignore).
23+
24+
The formatting rules are as follows:
25+
26+
- Parsing is line-by-line
27+
- Empty lines are ignored
28+
- Lines the begin with # (comments) will be ignored
29+
- Leading and trailing spaces are always ignored
30+
- Inline comments are NOT supported ('foo* # Any foo' does not contain a comment)
31+
- There is no support for multi-line patterns
32+
- Shell glob patterns are supported. See Go's "path/filepath".Match
33+
- If a pattern begins with a leading !, the match will be negated.
34+
- If a pattern begins with a leading /, only paths relatively rooted will match.
35+
- If the pattern ends with a trailing /, only directories will match
36+
- If a pattern contains no slashes, file basenames are tested (not paths)
37+
- The pattern sequence "**", while legal in a glob, will cause an error here
38+
(to indicate incompatibility with .gitignore).
39+
40+
Example:
41+
42+
# Match any file named foo.txt
43+
foo.txt
44+
45+
# Match any text file
46+
*.txt
47+
48+
# Match only directories named mydir
49+
mydir/
50+
51+
# Match only text files in the top-level directory
52+
/*.txt
53+
54+
# Match only the file foo.txt in the top-level directory
55+
/foo.txt
56+
57+
# Match any file named ab.txt, ac.txt, or ad.txt
58+
a[b-d].txt
59+
60+
Notable differences from .gitignore:
61+
- The '**' syntax is not supported.
62+
- The globbing library is Go's 'filepath.Match', not fnmatch(3)
63+
- Trailing spaces are always ignored (there is no supported escape sequence)
64+
- The evaluation of escape sequences has not been tested for compatibility
65+
- There is no support for '\!' as a special leading sequence.
66+
*/
67+
package ignore
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
Copyright The Helm Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package ignore
18+
19+
import (
20+
"bufio"
21+
"bytes"
22+
"io"
23+
"log"
24+
"os"
25+
"path/filepath"
26+
"strings"
27+
28+
"github.com/pkg/errors"
29+
)
30+
31+
// HelmIgnore default name of an ignorefile.
32+
const HelmIgnore = ".helmignore"
33+
34+
// Rules is a collection of path matching rules.
35+
//
36+
// Parse() and ParseFile() will construct and populate new Rules.
37+
// Empty() will create an immutable empty ruleset.
38+
type Rules struct {
39+
patterns []*pattern
40+
}
41+
42+
// Empty builds an empty ruleset.
43+
func Empty() *Rules {
44+
return &Rules{patterns: []*pattern{}}
45+
}
46+
47+
// AddDefaults adds default ignore patterns.
48+
//
49+
// Ignore all dotfiles in "templates/"
50+
func (r *Rules) AddDefaults() {
51+
r.parseRule(`templates/.?*`)
52+
}
53+
54+
// ParseFile parses a helmignore file and returns the *Rules.
55+
func ParseFile(file string) (*Rules, error) {
56+
f, err := os.Open(file)
57+
if err != nil {
58+
return nil, err
59+
}
60+
defer f.Close()
61+
return Parse(f)
62+
}
63+
64+
// Parse parses a rules file
65+
func Parse(file io.Reader) (*Rules, error) {
66+
r := &Rules{patterns: []*pattern{}}
67+
68+
s := bufio.NewScanner(file)
69+
currentLine := 0
70+
utf8bom := []byte{0xEF, 0xBB, 0xBF}
71+
for s.Scan() {
72+
scannedBytes := s.Bytes()
73+
// We trim UTF8 BOM
74+
if currentLine == 0 {
75+
scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom)
76+
}
77+
line := string(scannedBytes)
78+
currentLine++
79+
80+
if err := r.parseRule(line); err != nil {
81+
return r, err
82+
}
83+
}
84+
return r, s.Err()
85+
}
86+
87+
// Ignore evaluates the file at the given path, and returns true if it should be ignored.
88+
//
89+
// Ignore evaluates path against the rules in order. Evaluation stops when a match
90+
// is found. Matching a negative rule will stop evaluation.
91+
func (r *Rules) Ignore(path string, fi os.FileInfo) bool {
92+
// Don't match on empty dirs.
93+
if path == "" {
94+
return false
95+
}
96+
97+
// Disallow ignoring the current working directory.
98+
// See issue:
99+
// 1776 (New York City) Hamilton: "Pardon me, are you Aaron Burr, sir?"
100+
if path == "." || path == "./" {
101+
return false
102+
}
103+
for _, p := range r.patterns {
104+
if p.match == nil {
105+
log.Printf("ignore: no matcher supplied for %q", p.raw)
106+
return false
107+
}
108+
109+
// For negative rules, we need to capture and return non-matches,
110+
// and continue for matches.
111+
if p.negate {
112+
if p.mustDir && !fi.IsDir() {
113+
return true
114+
}
115+
if !p.match(path, fi) {
116+
return true
117+
}
118+
continue
119+
}
120+
121+
// If the rule is looking for directories, and this is not a directory,
122+
// skip it.
123+
if p.mustDir && !fi.IsDir() {
124+
continue
125+
}
126+
if p.match(path, fi) {
127+
return true
128+
}
129+
}
130+
return false
131+
}
132+
133+
// parseRule parses a rule string and creates a pattern, which is then stored in the Rules object.
134+
func (r *Rules) parseRule(rule string) error {
135+
rule = strings.TrimSpace(rule)
136+
137+
// Ignore blank lines
138+
if rule == "" {
139+
return nil
140+
}
141+
// Comment
142+
if strings.HasPrefix(rule, "#") {
143+
return nil
144+
}
145+
146+
// Fail any rules that contain **
147+
if strings.Contains(rule, "**") {
148+
return errors.New("double-star (**) syntax is not supported")
149+
}
150+
151+
// Fail any patterns that can't compile. A non-empty string must be
152+
// given to Match() to avoid optimization that skips rule evaluation.
153+
if _, err := filepath.Match(rule, "abc"); err != nil {
154+
return err
155+
}
156+
157+
p := &pattern{raw: rule}
158+
159+
// Negation is handled at a higher level, so strip the leading ! from the
160+
// string.
161+
if strings.HasPrefix(rule, "!") {
162+
p.negate = true
163+
rule = rule[1:]
164+
}
165+
166+
// Directory verification is handled by a higher level, so the trailing /
167+
// is removed from the rule. That way, a directory named "foo" matches,
168+
// even if the supplied string does not contain a literal slash character.
169+
if strings.HasSuffix(rule, "/") {
170+
p.mustDir = true
171+
rule = strings.TrimSuffix(rule, "/")
172+
}
173+
174+
if strings.HasPrefix(rule, "/") {
175+
// Require path matches the root path.
176+
p.match = func(n string, fi os.FileInfo) bool {
177+
rule = strings.TrimPrefix(rule, "/")
178+
ok, err := filepath.Match(rule, n)
179+
if err != nil {
180+
log.Printf("Failed to compile %q: %s", rule, err)
181+
return false
182+
}
183+
return ok
184+
}
185+
} else if strings.Contains(rule, "/") {
186+
// require structural match.
187+
p.match = func(n string, fi os.FileInfo) bool {
188+
ok, err := filepath.Match(rule, n)
189+
if err != nil {
190+
log.Printf("Failed to compile %q: %s", rule, err)
191+
return false
192+
}
193+
return ok
194+
}
195+
} else {
196+
p.match = func(n string, fi os.FileInfo) bool {
197+
// When there is no slash in the pattern, we evaluate ONLY the
198+
// filename.
199+
n = filepath.Base(n)
200+
ok, err := filepath.Match(rule, n)
201+
if err != nil {
202+
log.Printf("Failed to compile %q: %s", rule, err)
203+
return false
204+
}
205+
return ok
206+
}
207+
}
208+
209+
r.patterns = append(r.patterns, p)
210+
return nil
211+
}
212+
213+
// matcher is a function capable of computing a match.
214+
//
215+
// It returns true if the rule matches.
216+
type matcher func(name string, fi os.FileInfo) bool
217+
218+
// pattern describes a pattern to be matched in a rule set.
219+
type pattern struct {
220+
// raw is the unparsed string, with nothing stripped.
221+
raw string
222+
// match is the matcher function.
223+
match matcher
224+
// negate indicates that the rule's outcome should be negated.
225+
negate bool
226+
// mustDir indicates that the matched file must be a directory.
227+
mustDir bool
228+
}

0 commit comments

Comments
 (0)