1 | // Package opt implements command-line flag parsing.
|
---|
2 | package opt // import "modernc.org/opt"
|
---|
3 |
|
---|
4 | import (
|
---|
5 | "fmt"
|
---|
6 | "strings"
|
---|
7 | )
|
---|
8 |
|
---|
9 | type opt struct {
|
---|
10 | handler func(opt, arg string) error
|
---|
11 | name string
|
---|
12 |
|
---|
13 | arg bool // Enable argument, e.g. `-I foo` or `-I=foo`
|
---|
14 | }
|
---|
15 |
|
---|
16 | // A Set represents a set of defined options.
|
---|
17 | type Set struct {
|
---|
18 | cfg map[string]*opt
|
---|
19 | imm []*opt
|
---|
20 | }
|
---|
21 |
|
---|
22 | // NewSet returns a new, empty option set.
|
---|
23 | func NewSet() *Set { return &Set{cfg: map[string]*opt{}} }
|
---|
24 |
|
---|
25 | // Opt defines a simple option, e.g. `-f`. When the option is found during
|
---|
26 | // Parse, the handler is called with the value of the option, e.g. "-f".
|
---|
27 | func (p *Set) Opt(name string, handler func(opt string) error) {
|
---|
28 | p.cfg[name] = &opt{
|
---|
29 | handler: func(opt, arg string) error { return handler(opt) },
|
---|
30 | }
|
---|
31 | }
|
---|
32 |
|
---|
33 | // Arg defines a simple option with an argument, e.g. `-I foo` or `-I=foo`.
|
---|
34 | // Setting imm argument enables additionally `-Ifoo`. When the option is found
|
---|
35 | // during Parse, the handler is called with the values of the option and the
|
---|
36 | // argument, e.g. "-I" and "foo" for all of the variants.
|
---|
37 | func (p *Set) Arg(name string, imm bool, handler func(opt, arg string) error) {
|
---|
38 | switch {
|
---|
39 | case imm:
|
---|
40 | p.imm = append(p.imm, &opt{
|
---|
41 | handler: handler,
|
---|
42 | name: name,
|
---|
43 | })
|
---|
44 | default:
|
---|
45 | p.cfg[name] = &opt{
|
---|
46 | arg: true,
|
---|
47 | handler: handler,
|
---|
48 | name: name,
|
---|
49 | }
|
---|
50 | }
|
---|
51 | }
|
---|
52 |
|
---|
53 | // Parse parses opts. Must be called after all options are defined. The handler
|
---|
54 | // is called for all items in opts that were not defined before using Opt or
|
---|
55 | // Arg.
|
---|
56 | //
|
---|
57 | // If any handler returns a non-nil error, Parse will stop. If the error is of
|
---|
58 | // type Skip, the error returned by Parse will contain all the unprocessed
|
---|
59 | // items of opts.
|
---|
60 | //
|
---|
61 | // The opts slice must not be modified by any handler while Parser is
|
---|
62 | // executing.
|
---|
63 | func (p *Set) Parse(opts []string, handler func(string) error) (err error) {
|
---|
64 | defer func() {
|
---|
65 | switch err.(type) {
|
---|
66 | case Skip:
|
---|
67 | err = Skip(opts)
|
---|
68 | }
|
---|
69 | }()
|
---|
70 |
|
---|
71 | for len(opts) != 0 {
|
---|
72 | opt := opts[0]
|
---|
73 | opt0 := opt
|
---|
74 | opts = opts[1:]
|
---|
75 | var arg string
|
---|
76 | out:
|
---|
77 | switch {
|
---|
78 | case strings.HasPrefix(opt, "-"):
|
---|
79 | name := opt[1:]
|
---|
80 | for _, cfg := range p.imm {
|
---|
81 | if strings.HasPrefix(name, cfg.name) {
|
---|
82 | switch {
|
---|
83 | case name == cfg.name:
|
---|
84 | if len(opts) == 0 {
|
---|
85 | return fmt.Errorf("missing argument of %s", opt)
|
---|
86 | }
|
---|
87 |
|
---|
88 | if err = cfg.handler(opt, opts[0]); err != nil {
|
---|
89 | return err
|
---|
90 | }
|
---|
91 |
|
---|
92 | opts = opts[1:]
|
---|
93 | default:
|
---|
94 | opt = opt[:len(cfg.name)+1]
|
---|
95 | val := strings.TrimPrefix(name[len(cfg.name):], "=")
|
---|
96 | if err = cfg.handler(opt, val); err != nil {
|
---|
97 | return err
|
---|
98 | }
|
---|
99 | }
|
---|
100 | break out
|
---|
101 | }
|
---|
102 | }
|
---|
103 |
|
---|
104 | if n := strings.IndexByte(opt, '='); n > 0 {
|
---|
105 | arg = opt[n+1:]
|
---|
106 | name = opt[1:n]
|
---|
107 | opt = opt[:n]
|
---|
108 | }
|
---|
109 | switch cfg := p.cfg[name]; {
|
---|
110 | case cfg == nil:
|
---|
111 | if err = handler(opt0); err != nil {
|
---|
112 | return err
|
---|
113 | }
|
---|
114 | default:
|
---|
115 | switch {
|
---|
116 | case cfg.arg:
|
---|
117 | switch {
|
---|
118 | case arg != "":
|
---|
119 | if err = cfg.handler(opt, arg); err != nil {
|
---|
120 | return err
|
---|
121 | }
|
---|
122 | default:
|
---|
123 | if len(opts) == 0 {
|
---|
124 | return fmt.Errorf("missing argument of %s", opt)
|
---|
125 | }
|
---|
126 |
|
---|
127 | if err = cfg.handler(opt, opts[0]); err != nil {
|
---|
128 | return err
|
---|
129 | }
|
---|
130 |
|
---|
131 | opts = opts[1:]
|
---|
132 | }
|
---|
133 | default:
|
---|
134 | if err = cfg.handler(opt, ""); err != nil {
|
---|
135 | return err
|
---|
136 | }
|
---|
137 | }
|
---|
138 | }
|
---|
139 | default:
|
---|
140 | if opt == "" {
|
---|
141 | break
|
---|
142 | }
|
---|
143 |
|
---|
144 | if err = handler(opt); err != nil {
|
---|
145 | return err
|
---|
146 | }
|
---|
147 | }
|
---|
148 | }
|
---|
149 | return nil
|
---|
150 | }
|
---|
151 |
|
---|
152 | // Skip is an error that contains all unprocessed items passed to Parse.
|
---|
153 | type Skip []string
|
---|
154 |
|
---|
155 | func (s Skip) Error() string { return fmt.Sprint([]string(s)) }
|
---|