-
Notifications
You must be signed in to change notification settings - Fork 11
/
flag.go
282 lines (253 loc) · 7.3 KB
/
flag.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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
// Copyright 2021 Google LLC
//
// Licensed under the Apache Livense, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package flag
import (
"encoding/json"
"flag"
"fmt"
"os"
"runtime"
"sort"
"strings"
"time"
"github.com/divVerent/aaaaxy/internal/log"
)
var (
flagSet = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
v = Int("v", 0, "verbose logging level") // Must be declared here to prevent cycle.
batch = Bool("batch", false, "if set, show no alert boxes") // Must be declared here to prevent cycle.
loadConfig = Bool("load_config", true, "enable processing of the configuration file")
)
// SystemDefault performs a GOOS/GOARCH dependent value lookup to be used in flag defaults.
// Map keys shall be */*, GOOS/*, */GOARCH or GOOS/GOARCH.
func SystemDefault(m map[string]interface{}) interface{} {
k := fmt.Sprintf("%v/%v", runtime.GOOS, runtime.GOARCH)
if val, found := m[k]; found {
return val
}
k = fmt.Sprintf("%v/*", runtime.GOOS)
if val, found := m[k]; found {
return val
}
k = fmt.Sprintf("*/%v", runtime.GOARCH)
if val, found := m[k]; found {
return val
}
return m["*/*"]
}
// Bool creates a bool in our FlagSet.
func Bool(name string, value bool, usage string) *bool {
return flagSet.Bool(name, value, usage)
}
// Float64 creates a float64 in our FlagSet.
func Float64(name string, value float64, usage string) *float64 {
return flagSet.Float64(name, value, usage)
}
// Int creates an int in our FlagSet.
func Int(name string, value int, usage string) *int {
return flagSet.Int(name, value, usage)
}
// String creates a string in our FlagSet.
func String(name string, value string, usage string) *string {
return flagSet.String(name, value, usage)
}
// Duration creates a Duration in our FlagSet.
func Duration(name string, value time.Duration, usage string) *time.Duration {
return flagSet.Duration(name, value, usage)
}
// Set overrides a flag value. May be used by the menu.
func Set(name string, value interface{}) error {
return flagSet.Set(name, fmt.Sprint(value))
}
// Get loads a flag by name.
func Get(name string) interface{} {
f := flagSet.Lookup(name)
if f == nil {
log.Errorf("queried non-existing flag: %v", name)
return ""
}
return f.Value.(flag.Getter).Get()
}
// Config is a JSON serializable type containing the flags.
type Config struct {
flags map[string]string
}
// MarshalJSON returns the JSON representation of the config.
func (c *Config) MarshalJSON() ([]byte, error) {
return json.Marshal(c.flags)
}
// UnmarshalJSON loads the config from a JSON object string.
func (c *Config) UnmarshalJSON(data []byte) error {
return json.Unmarshal(data, &c.flags)
}
// Marshal returns a config object for the currently set flags (both those from the config and command line).
// We only write non-default flag values.
func Marshal() *Config {
c := &Config{flags: map[string]string{}}
// Note: VisitAll also sees flags that have been modified by code writing to *flag pointers.
// Visit would only see flags changed using flag.Set or the command line.
flagSet.VisitAll(func(f *flag.Flag) {
// Don't save debug or dump flags.
if strings.HasPrefix(f.Name, "cheat_") {
return
}
if strings.HasPrefix(f.Name, "debug_") {
return
}
if strings.HasPrefix(f.Name, "dump_") {
return
}
if strings.HasPrefix(f.Name, "demo_") {
return
}
if f.Value.String() == f.DefValue {
return
}
c.flags[f.Name] = f.Value.String()
})
return c
}
// Cheating returns if any cheats are enabled, and what they are.
func Cheating() (bool, string) {
cheating := false
cheats := []string{}
flagSet.Visit(func(f *flag.Flag) {
if strings.HasPrefix(f.Name, "cheat_") {
cheating = true
cheats = append(cheats, fmt.Sprintf("--%s=%s", f.Name, f.Value.String()))
}
})
return cheating, strings.Join(cheats, " ")
}
// ResetToDefaults returns all flags to their default value.
func ResetToDefaults() {
flagSet.Visit(func(f *flag.Flag) {
f.Value.Set(f.DefValue)
})
}
var getConfig func() (*Config, error)
func applyConfig() {
// Provide verbose level ASAP.
log.V = v
log.Batch = batch
// Skip config loading if so desired.
// This ability is why flag loading is hard;
// we need to parse the command line to detect whether we want to load the config,
// but then we want the command line to have precedence over the config.
if !*loadConfig {
log.Infof("config loading was disabled by the command line")
return
}
// Remember which flags have already been set. These will NOT come from the config.
set := map[string]struct{}{}
flagSet.Visit(func(f *flag.Flag) {
set[f.Name] = struct{}{}
})
config, err := getConfig()
if err != nil {
log.Errorf("could not load config: %v", err)
return
}
if config == nil {
// Nothing to do.
return
}
for name, value := range config.flags {
// Don't take from config what's already been overridden.
if _, found := set[name]; found {
continue
}
err = flagSet.Set(name, value)
if err != nil {
log.Errorf("could not apply config value %q=%q: %v", name, value, err)
continue
}
}
}
func showUsage() {
applyConfig()
flagSet.PrintDefaults()
}
// Parse parses the command-line flags, then loads the config object using the provided function.
// Should be called initially, before loading config.
func Parse(getSystemDefaults func() (*Config, error)) {
getConfig = getSystemDefaults
flagSet.Usage = showUsage
flagSet.Parse(os.Args[1:])
applyConfig()
}
// NoConfig can be passed to Parse if the binary wants to do no config file processing.
func NoConfig() (*Config, error) {
return nil, nil
}
// StringBoolMap is a custom flag type to contain maps from string to bool.
func StringBoolMap(name string, value map[string]bool, usage string) *map[string]bool {
m := stringBoolMap{m: value}
flagSet.Var(&m, name, usage)
return &m.m
}
type stringBoolMap struct {
m map[string]bool
}
func (m *stringBoolMap) String() string {
a := make([]string, 0, len(m.m))
for k := range m.m {
a = append(a, k)
}
sort.Strings(a)
s := ""
for _, k := range a {
if s != "" {
s += ","
}
s += k
s += "="
s += fmt.Sprint(m.m[k])
}
return s
}
func (m *stringBoolMap) Set(s string) error {
m.m = map[string]bool{}
if s == "" {
return nil
}
for _, word := range strings.Split(s, ",") {
kv := strings.SplitN(word, "=", 2)
switch len(kv) {
case 2:
if kv[1] == "" {
delete(m.m, kv[0])
} else {
var v bool
_, err := fmt.Sscanf(kv[1], "%v", &v)
if err != nil {
return fmt.Errorf("invalid StringBoolMap flag value, got %q, want something of the form key1=true/false,key2=true/false,...", s)
}
m.m[kv[0]] = v
}
case 1:
if strings.HasPrefix(kv[0], "no") {
m.m[kv[0][2:]] = false
} else {
m.m[kv[0]] = true
}
default:
return fmt.Errorf("invalid StringMap flag value, got %q, want something of the form key1=value1,key2=value2,...", s)
}
}
return nil
}
func (m *stringBoolMap) Get() interface{} {
return m.m
}