1package doublestar
2
3import "strings"
4
5// glob is an internal type to store options during globbing.
6type glob struct {
7 failOnIOErrors bool
8 failOnPatternNotExist bool
9 filesOnly bool
10 noFollow bool
11}
12
13// GlobOption represents a setting that can be passed to Glob, GlobWalk, and
14// FilepathGlob.
15type GlobOption func(*glob)
16
17// Construct a new glob object with the given options
18func newGlob(opts ...GlobOption) *glob {
19 g := &glob{}
20 for _, opt := range opts {
21 opt(g)
22 }
23 return g
24}
25
26// WithFailOnIOErrors is an option that can be passed to Glob, GlobWalk, or
27// FilepathGlob. If passed, doublestar will abort and return IO errors when
28// encountered. Note that if the glob pattern references a path that does not
29// exist (such as `nonexistent/path/*`), this is _not_ considered an IO error:
30// it is considered a pattern with no matches.
31//
32func WithFailOnIOErrors() GlobOption {
33 return func(g *glob) {
34 g.failOnIOErrors = true
35 }
36}
37
38// WithFailOnPatternNotExist is an option that can be passed to Glob, GlobWalk,
39// or FilepathGlob. If passed, doublestar will abort and return
40// ErrPatternNotExist if the pattern references a path that does not exist
41// before any meta charcters such as `nonexistent/path/*`. Note that alts (ie,
42// `{...}`) are expanded before this check. In other words, a pattern such as
43// `{a,b}/*` may fail if either `a` or `b` do not exist but `*/{a,b}` will
44// never fail because the star may match nothing.
45//
46func WithFailOnPatternNotExist() GlobOption {
47 return func(g *glob) {
48 g.failOnPatternNotExist = true
49 }
50}
51
52// WithFilesOnly is an option that can be passed to Glob, GlobWalk, or
53// FilepathGlob. If passed, doublestar will only return files that match the
54// pattern, not directories.
55//
56// Note: if combined with the WithNoFollow option, symlinks to directories
57// _will_ be included in the result since no attempt is made to follow the
58// symlink.
59//
60func WithFilesOnly() GlobOption {
61 return func(g *glob) {
62 g.filesOnly = true
63 }
64}
65
66// WithNoFollow is an option that can be passed to Glob, GlobWalk, or
67// FilepathGlob. If passed, doublestar will not follow symlinks while
68// traversing the filesystem. However, due to io/fs's _very_ poor support for
69// querying the filesystem about symlinks, there's a caveat here: if part of
70// the pattern before any meta characters contains a reference to a symlink, it
71// will be followed. For example, a pattern such as `path/to/symlink/*` will be
72// followed assuming it is a valid symlink to a directory. However, from this
73// same example, a pattern such as `path/to/**` will not traverse the
74// `symlink`, nor would `path/*/symlink/*`
75//
76// Note: if combined with the WithFilesOnly option, symlinks to directories
77// _will_ be included in the result since no attempt is made to follow the
78// symlink.
79//
80func WithNoFollow() GlobOption {
81 return func(g *glob) {
82 g.noFollow = true
83 }
84}
85
86// forwardErrIfFailOnIOErrors is used to wrap the return values of I/O
87// functions. When failOnIOErrors is enabled, it will return err; otherwise, it
88// always returns nil.
89//
90func (g *glob) forwardErrIfFailOnIOErrors(err error) error {
91 if g.failOnIOErrors {
92 return err
93 }
94 return nil
95}
96
97// handleErrNotExist handles fs.ErrNotExist errors. If
98// WithFailOnPatternNotExist has been enabled and canFail is true, this will
99// return ErrPatternNotExist. Otherwise, it will return nil.
100//
101func (g *glob) handlePatternNotExist(canFail bool) error {
102 if canFail && g.failOnPatternNotExist {
103 return ErrPatternNotExist
104 }
105 return nil
106}
107
108// Format options for debugging/testing purposes
109func (g *glob) GoString() string {
110 var b strings.Builder
111 b.WriteString("opts: ")
112
113 hasOpts := false
114 if g.failOnIOErrors {
115 b.WriteString("WithFailOnIOErrors")
116 hasOpts = true
117 }
118 if g.failOnPatternNotExist {
119 if hasOpts {
120 b.WriteString(", ")
121 }
122 b.WriteString("WithFailOnPatternNotExist")
123 hasOpts = true
124 }
125 if g.filesOnly {
126 if hasOpts {
127 b.WriteString(", ")
128 }
129 b.WriteString("WithFilesOnly")
130 hasOpts = true
131 }
132 if g.noFollow {
133 if hasOpts {
134 b.WriteString(", ")
135 }
136 b.WriteString("WithNoFollow")
137 hasOpts = true
138 }
139
140 if !hasOpts {
141 b.WriteString("nil")
142 }
143 return b.String()
144}