/
bflag.go
215 lines (184 loc) · 4.93 KB
/
bflag.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package instructions
import (
"strings"
"github.com/moby/buildkit/util/suggest"
"github.com/pkg/errors"
)
// FlagType is the type of the build flag
type FlagType int
const (
boolType FlagType = iota
stringType
stringsType
)
// BFlags contains all flags information for the builder
type BFlags struct {
Args []string // actual flags/args from cmd line
flags map[string]*Flag
used map[string]*Flag
Err error
}
// Flag contains all information for a flag
type Flag struct {
bf *BFlags
name string
flagType FlagType
Value string
StringValues []string
}
// NewBFlags returns the new BFlags struct
func NewBFlags() *BFlags {
return &BFlags{
flags: make(map[string]*Flag),
used: make(map[string]*Flag),
}
}
// NewBFlagsWithArgs returns the new BFlags struct with Args set to args
func NewBFlagsWithArgs(args []string) *BFlags {
flags := NewBFlags()
flags.Args = args
return flags
}
// AddBool adds a bool flag to BFlags
// Note, any error will be generated when Parse() is called (see Parse).
func (bf *BFlags) AddBool(name string, def bool) *Flag {
flag := bf.addFlag(name, boolType)
if flag == nil {
return nil
}
if def {
flag.Value = "true"
} else {
flag.Value = "false"
}
return flag
}
// AddString adds a string flag to BFlags
// Note, any error will be generated when Parse() is called (see Parse).
func (bf *BFlags) AddString(name string, def string) *Flag {
flag := bf.addFlag(name, stringType)
if flag == nil {
return nil
}
flag.Value = def
return flag
}
// AddStrings adds a string flag to BFlags that can match multiple values
func (bf *BFlags) AddStrings(name string) *Flag {
flag := bf.addFlag(name, stringsType)
if flag == nil {
return nil
}
return flag
}
// addFlag is a generic func used by the other AddXXX() func
// to add a new flag to the BFlags struct.
// Note, any error will be generated when Parse() is called (see Parse).
func (bf *BFlags) addFlag(name string, flagType FlagType) *Flag {
if _, ok := bf.flags[name]; ok {
bf.Err = errors.Errorf("Duplicate flag defined: %s", name)
return nil
}
newFlag := &Flag{
bf: bf,
name: name,
flagType: flagType,
}
bf.flags[name] = newFlag
return newFlag
}
// IsUsed checks if the flag is used
func (fl *Flag) IsUsed() bool {
if _, ok := fl.bf.used[fl.name]; ok {
return true
}
return false
}
// Used returns a slice of flag names that are set
func (bf *BFlags) Used() []string {
used := make([]string, 0, len(bf.used))
for f := range bf.used {
used = append(used, f)
}
return used
}
// IsTrue checks if a bool flag is true
func (fl *Flag) IsTrue() bool {
if fl.flagType != boolType {
// Should never get here
err := errors.Errorf("Trying to use IsTrue on a non-boolean: %s", fl.name)
panic(err)
}
return fl.Value == "true"
}
// Parse parses and checks if the BFlags is valid.
// Any error noticed during the AddXXX() funcs will be generated/returned
// here. We do this because an error during AddXXX() is more like a
// compile time error so it doesn't matter too much when we stop our
// processing as long as we do stop it, so this allows the code
// around AddXXX() to be just:
//
// defFlag := AddString("description", "")
//
// w/o needing to add an if-statement around each one.
func (bf *BFlags) Parse() error {
// If there was an error while defining the possible flags
// go ahead and bubble it back up here since we didn't do it
// earlier in the processing
if bf.Err != nil {
return errors.Wrap(bf.Err, "error setting up flags")
}
for _, arg := range bf.Args {
if !strings.HasPrefix(arg, "--") {
return errors.Errorf("arg should start with -- : %s", arg)
}
if arg == "--" {
return nil
}
arg, value, hasValue := strings.Cut(arg[2:], "=")
flag, ok := bf.flags[arg]
if !ok {
err := errors.Errorf("unknown flag: %s", arg)
return suggest.WrapError(err, arg, allFlags(bf.flags), true)
}
if _, ok = bf.used[arg]; ok && flag.flagType != stringsType {
return errors.Errorf("duplicate flag specified: %s", arg)
}
bf.used[arg] = flag
switch flag.flagType {
case boolType:
// value == "" is only ok if no "=" was specified
if hasValue && value == "" {
return errors.Errorf("missing a value on flag: %s", arg)
}
switch strings.ToLower(value) {
case "true", "":
flag.Value = "true"
case "false":
flag.Value = "false"
default:
return errors.Errorf("expecting boolean value for flag %s, not: %s", arg, value)
}
case stringType:
if !hasValue {
return errors.Errorf("missing a value on flag: %s", arg)
}
flag.Value = value
case stringsType:
if !hasValue {
return errors.Errorf("missing a value on flag: %s", arg)
}
flag.StringValues = append(flag.StringValues, value)
default:
panic("No idea what kind of flag we have! Should never get here!")
}
}
return nil
}
func allFlags(flags map[string]*Flag) []string {
var names []string
for name := range flags {
names = append(names, name)
}
return names
}