-
Notifications
You must be signed in to change notification settings - Fork 0
/
getopt.go
158 lines (147 loc) · 3.47 KB
/
getopt.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
package gogetopt
import (
"fmt"
"strconv"
"unicode"
)
// Option represents an option identified on the command line.
// For options which take an argument, the original text is stored
// in Value. For numeric options, the parsed numeric value is also
// stored in the corresponding member. For boolean options (the
// default), the struct will have all zero values; the indicator
// of the option's presence is that it's present in the Options
// map.
type Option struct {
Value string
Int int
Float float64
}
// Options is a map of the options found by GetOpt. An option
// is present only if it was specified in the arguments parsed.
type Options map[string]*Option
// Seen is a convenience function to allow a query for whether
// an option has been set without using the ", ok" format, thus
// allowing a check inline in an expression.
func (o Options) Seen(s string) bool {
_, ok := o[s]
return ok
}
type optType int
type opt struct {
typ optType
}
func (o *opt) setType(typ optType) error {
if o == nil {
return fmt.Errorf("option type specifier without option")
}
o.typ = typ
return nil
}
const (
optBool optType = iota
optString
optInt
optFloat
optCount
)
type optMap map[string]*opt
// parseOpt converts an option string to a map of options
func parseOpt(optstring string) (optMap, error) {
o := make(optMap)
var prev *opt
for _, r := range optstring {
c := string(r)
if o[c] != nil {
return nil, fmt.Errorf("duplicate option specifiers for '%s'", c)
}
if unicode.IsLetter(r) {
o[c] = &opt{optBool}
prev = o[c]
continue
}
var err error
switch r {
case ':':
err = prev.setType(optString)
case '#':
err = prev.setType(optInt)
case '.':
err = prev.setType(optFloat)
case '+':
err = prev.setType(optCount)
default:
return nil, fmt.Errorf("invalid option specifier '%s'", c)
}
if err != nil {
return nil, err
}
// you can only specify a type once per option
prev = nil
}
return o, nil
}
// GetOpt interprets the provided arguments according to optstring,
// which lists individual flags. All flags are single characters
// which are letters or numbers.
func GetOpt(args []string, optstring string) (opts Options, remaining []string, err error) {
known, err := parseOpt(optstring)
if err != nil {
return nil, args, err
}
opts = make(Options)
next := 0
ArgLoop:
for next < len(args) {
if args[next] == "--" {
// skip this, return the rest without looking at them
next++
break
}
if args[next][0] != '-' {
break
}
// we're now looking at an argument which started with a hyphen
flags := args[next][1:]
next++
for _, f := range flags {
flag := string(f)
if known[flag] == nil {
err = fmt.Errorf("unknown option '%s'", flag)
break ArgLoop
}
opt, ok := opts[flag]
if !ok {
opt = &Option{}
opts[flag] = opt
} else {
if known[flag].typ != optCount {
err = fmt.Errorf("duplicate option '%s'", flag)
break ArgLoop
}
}
if known[flag].typ == optBool {
continue
}
if known[flag].typ == optCount {
opt.Int++
continue
}
if next >= len(args) {
err = fmt.Errorf("option '%s' requires an argument", flag)
break ArgLoop
}
opts[flag].Value = args[next]
next++
switch known[flag].typ {
case optInt:
opts[flag].Int, err = strconv.Atoi(opts[flag].Value)
case optFloat:
opts[flag].Float, err = strconv.ParseFloat(opts[flag].Value, 64)
}
if err != nil {
break ArgLoop
}
}
}
return opts, args[next:], err
}