1
2
3
4
5 package objabi
6
7 import (
8 "flag"
9 "fmt"
10 "internal/bisect"
11 "internal/buildcfg"
12 "io"
13 "log"
14 "os"
15 "reflect"
16 "sort"
17 "strconv"
18 "strings"
19 )
20
21 func Flagcount(name, usage string, val *int) {
22 flag.Var((*count)(val), name, usage)
23 }
24
25 func Flagfn1(name, usage string, f func(string)) {
26 flag.Var(fn1(f), name, usage)
27 }
28
29 func Flagprint(w io.Writer) {
30 flag.CommandLine.SetOutput(w)
31 flag.PrintDefaults()
32 }
33
34 func Flagparse(usage func()) {
35 flag.Usage = usage
36 os.Args = expandArgs(os.Args)
37 flag.Parse()
38 }
39
40
41
42
43
44
45
46
47
48
49
50
51
52 func expandArgs(in []string) (out []string) {
53
54 for i, s := range in {
55 if strings.HasPrefix(s, "@") {
56 if out == nil {
57 out = make([]string, 0, len(in)*2)
58 out = append(out, in[:i]...)
59 }
60 slurp, err := os.ReadFile(s[1:])
61 if err != nil {
62 log.Fatal(err)
63 }
64 args := strings.Split(strings.TrimSpace(strings.ReplaceAll(string(slurp), "\r", "")), "\n")
65 for i, arg := range args {
66 args[i] = DecodeArg(arg)
67 }
68 out = append(out, expandArgs(args)...)
69 } else if out != nil {
70 out = append(out, s)
71 }
72 }
73 if out == nil {
74 return in
75 }
76 return
77 }
78
79 func AddVersionFlag() {
80 flag.Var(versionFlag{}, "V", "print version and exit")
81 }
82
83 var buildID string
84
85 type versionFlag struct{}
86
87 func (versionFlag) IsBoolFlag() bool { return true }
88 func (versionFlag) Get() any { return nil }
89 func (versionFlag) String() string { return "" }
90 func (versionFlag) Set(s string) error {
91 name := os.Args[0]
92 name = name[strings.LastIndex(name, `/`)+1:]
93 name = name[strings.LastIndex(name, `\`)+1:]
94 name = strings.TrimSuffix(name, ".exe")
95
96 p := ""
97
98
99
100 if goexperiment := buildcfg.Experiment.String(); goexperiment != "" {
101 p = " X:" + goexperiment
102 }
103
104
105
106
107
108
109 if s == "full" {
110 if strings.Contains(buildcfg.Version, "devel") {
111 p += " buildID=" + buildID
112 }
113 }
114
115 fmt.Printf("%s version %s%s\n", name, buildcfg.Version, p)
116 os.Exit(0)
117 return nil
118 }
119
120
121
122
123 type count int
124
125 func (c *count) String() string {
126 return fmt.Sprint(int(*c))
127 }
128
129 func (c *count) Set(s string) error {
130 switch s {
131 case "true":
132 *c++
133 case "false":
134 *c = 0
135 default:
136 n, err := strconv.Atoi(s)
137 if err != nil {
138 return fmt.Errorf("invalid count %q", s)
139 }
140 *c = count(n)
141 }
142 return nil
143 }
144
145 func (c *count) Get() any {
146 return int(*c)
147 }
148
149 func (c *count) IsBoolFlag() bool {
150 return true
151 }
152
153 func (c *count) IsCountFlag() bool {
154 return true
155 }
156
157 type fn1 func(string)
158
159 func (f fn1) Set(s string) error {
160 f(s)
161 return nil
162 }
163
164 func (f fn1) String() string { return "" }
165
166
167
168
169 func DecodeArg(arg string) string {
170
171 if !strings.ContainsAny(arg, "\\\n") {
172 return arg
173 }
174
175 var b strings.Builder
176 var wasBS bool
177 for _, r := range arg {
178 if wasBS {
179 switch r {
180 case '\\':
181 b.WriteByte('\\')
182 case 'n':
183 b.WriteByte('\n')
184 default:
185
186
187 panic("badly formatted input")
188 }
189 } else if r == '\\' {
190 wasBS = true
191 continue
192 } else {
193 b.WriteRune(r)
194 }
195 wasBS = false
196 }
197 return b.String()
198 }
199
200 type debugField struct {
201 name string
202 help string
203 concurrentOk bool
204 val any
205 }
206
207 type DebugFlag struct {
208 tab map[string]debugField
209 concurrentOk *bool
210 debugSSA DebugSSA
211 }
212
213
214
215
216
217 type DebugSSA func(phase, flag string, val int, valString string) string
218
219
220
221
222
223
224
225
226
227
228
229
230
231 func NewDebugFlag(debug any, debugSSA DebugSSA) *DebugFlag {
232 flag := &DebugFlag{
233 tab: make(map[string]debugField),
234 debugSSA: debugSSA,
235 }
236
237 v := reflect.ValueOf(debug).Elem()
238 t := v.Type()
239 for i := 0; i < t.NumField(); i++ {
240 f := t.Field(i)
241 ptr := v.Field(i).Addr().Interface()
242 if f.Name == "ConcurrentOk" {
243 switch ptr := ptr.(type) {
244 default:
245 panic("debug.ConcurrentOk must have type bool")
246 case *bool:
247 flag.concurrentOk = ptr
248 }
249 continue
250 }
251 name := strings.ToLower(f.Name)
252 help := f.Tag.Get("help")
253 if help == "" {
254 panic(fmt.Sprintf("debug.%s is missing help text", f.Name))
255 }
256 concurrent := f.Tag.Get("concurrent")
257
258 switch ptr.(type) {
259 default:
260 panic(fmt.Sprintf("debug.%s has invalid type %v (must be int, string, or *bisect.Matcher)", f.Name, f.Type))
261 case *int, *string, **bisect.Matcher:
262
263 }
264 flag.tab[name] = debugField{name, help, concurrent == "ok", ptr}
265 }
266
267 return flag
268 }
269
270 func (f *DebugFlag) Set(debugstr string) error {
271 if debugstr == "" {
272 return nil
273 }
274 for name := range strings.SplitSeq(debugstr, ",") {
275 if name == "" {
276 continue
277 }
278
279 if name == "help" {
280 fmt.Print(debugHelpHeader)
281 maxLen, names := 0, []string{}
282 if f.debugSSA != nil {
283 maxLen = len("ssa/help")
284 }
285 for name := range f.tab {
286 if len(name) > maxLen {
287 maxLen = len(name)
288 }
289 names = append(names, name)
290 }
291 sort.Strings(names)
292
293 nl := fmt.Sprintf("\n\t%-*s\t", maxLen, "")
294 for _, name := range names {
295 help := f.tab[name].help
296 fmt.Printf("\t%-*s\t%s\n", maxLen, name, strings.ReplaceAll(help, "\n", nl))
297 }
298 if f.debugSSA != nil {
299
300 fmt.Printf("\t%-*s\t%s\n", maxLen, "ssa/help", "print help about SSA debugging")
301 }
302 os.Exit(0)
303 }
304
305 val, valstring, haveInt := 1, "", true
306 if i := strings.IndexAny(name, "=:"); i >= 0 {
307 var err error
308 name, valstring = name[:i], name[i+1:]
309 val, err = strconv.Atoi(valstring)
310 if err != nil {
311 val, haveInt = 1, false
312 }
313 }
314
315 if t, ok := f.tab[name]; ok {
316 switch vp := t.val.(type) {
317 case nil:
318
319 case *string:
320 *vp = valstring
321 case *int:
322 if !haveInt {
323 log.Fatalf("invalid debug value %v", name)
324 }
325 *vp = val
326 case **bisect.Matcher:
327 var err error
328 *vp, err = bisect.New(valstring)
329 if err != nil {
330 log.Fatalf("debug flag %v: %v", name, err)
331 }
332 default:
333 panic("bad debugtab type")
334 }
335
336 if !t.concurrentOk && f.concurrentOk != nil {
337 *f.concurrentOk = false
338 }
339 } else if f.debugSSA != nil && strings.HasPrefix(name, "ssa/") {
340
341
342
343 phase := name[4:]
344 flag := "debug"
345 if i := strings.Index(phase, "/"); i >= 0 {
346 flag = phase[i+1:]
347 phase = phase[:i]
348 }
349 err := f.debugSSA(phase, flag, val, valstring)
350 if err != "" {
351 log.Fatal(err)
352 }
353
354
355
356 *f.concurrentOk = false
357
358 } else {
359 return fmt.Errorf("unknown debug key %s\n", name)
360 }
361 }
362
363 return nil
364 }
365
366 const debugHelpHeader = `usage: -d arg[,arg]* and arg is <key>[=<value>]
367
368 <key> is one of:
369
370 `
371
372 func (f *DebugFlag) String() string {
373 return ""
374 }
375
View as plain text