opt.go (3578B)
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)) }