Merge branch 'master' of https://git.freesoftwareextremist.com/bloat
[bloat] / util / getopt.go
1 /*
2 Copyright 2019 Drew DeVault <sir@cmpwn.com>
3
4 Redistribution and use in source and binary forms, with or without
5 modification, are permitted provided that the following conditions are met:
6
7 1. Redistributions of source code must retain the above copyright notice, this
8 list of conditions and the following disclaimer.
9
10 2. Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13
14 3. Neither the name of the copyright holder nor the names of its contributors
15 may be used to endorse or promote products derived from this software without
16 specific prior written permission.
17
18 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 package util
31
32 import (
33         "fmt"
34         "os"
35 )
36
37 // In the case of "-o example", Option is 'o' and "example" is Value. For
38 // options which do not take an argument, Value is "".
39 type Option struct {
40         Option rune
41         Value  string
42 }
43
44 // This is returned when an unknown option is found in argv, but not in the
45 // option spec.
46 type UnknownOptionError rune
47
48 func (e UnknownOptionError) Error() string {
49         return fmt.Sprintf("%s: unknown option -%c", os.Args[0], rune(e))
50 }
51
52 // This is returned when an option with a mandatory argument is missing that
53 // argument.
54 type MissingOptionError rune
55
56 func (e MissingOptionError) Error() string {
57         return fmt.Sprintf("%s: expected argument for -%c", os.Args[0], rune(e))
58 }
59
60 // Getopts implements a POSIX-compatible options interface.
61 //
62 // Returns a slice of options and the index of the first non-option argument.
63 //
64 // If an error is returned, you must print it to stderr to be POSIX complaint.
65 func Getopts(argv []string, spec string) ([]Option, int, error) {
66         optmap := make(map[rune]bool)
67         runes := []rune(spec)
68         for i, rn := range spec {
69                 if rn == ':' {
70                         if i == 0 {
71                                 continue
72                         }
73                         optmap[runes[i-1]] = true
74                 } else {
75                         optmap[rn] = false
76                 }
77         }
78
79         var (
80                 i    int
81                 opts []Option
82         )
83         for i = 1; i < len(argv); i++ {
84                 arg := argv[i]
85                 runes = []rune(arg)
86                 if len(arg) == 0 || arg == "-" {
87                         break
88                 }
89                 if arg[0] != '-' {
90                         break
91                 }
92                 if arg == "--" {
93                         i++
94                         break
95                 }
96                 for j, opt := range runes[1:] {
97                         if optopt, ok := optmap[opt]; !ok {
98                                 opts = append(opts, Option{'?', ""})
99                                 return opts, i, UnknownOptionError(opt)
100                         } else if optopt {
101                                 if j+1 < len(runes)-1 {
102                                         opts = append(opts, Option{opt, string(runes[j+2:])})
103                                         break
104                                 } else {
105                                         if i+1 >= len(argv) {
106                                                 if len(spec) >= 1 && spec[0] == ':' {
107                                                         opts = append(opts, Option{':', string(opt)})
108                                                 } else {
109                                                         return opts, i, MissingOptionError(opt)
110                                                 }
111                                         } else {
112                                                 opts = append(opts, Option{opt, argv[i+1]})
113                                                 i++
114                                         }
115                                 }
116                         } else {
117                                 opts = append(opts, Option{opt, ""})
118                         }
119                 }
120         }
121         return opts, i, nil
122 }