Eric Bower
·
2026-05-31
1package rsyncopts
2
3import (
4 "fmt"
5 "math"
6 "strconv"
7 "strings"
8)
9
10type poptOption struct {
11 longName string
12 shortName string
13 argInfo int
14 arg any // depends on argInfo
15 val int // 0 means don't return, just update arg
16 // descrip string
17 // argDescrip string
18}
19
20func (o *poptOption) name() string {
21 if o.longName == "" {
22 return "-" + o.shortName
23 }
24 return "--" + o.longName
25}
26
27// see popt(3).
28const (
29 POPT_ARG_NONE = iota // int; No argument expected
30 POPT_ARG_STRING // char*; No type checking to be performed
31 POPT_ARG_INT // int; An integer argument is expected
32 POPT_ARG_LONG // long; A long integer is expected
33 POPT_ARG_INCLUDE_TABLE // nest another option table
34 POPT_ARG_CALLBACK // call a function
35 POPT_ARG_INTL_DOMAIN // <not documented in popt(3)>
36 POPT_ARG_VAL // int; Integer value taken from val
37 POPT_ARG_FLOAT // float; A float argument is expected
38 POPT_ARG_DOUBLE // double; A double argument is expected
39 POPT_ARG_LONGLONG // long long; A long long integer is expected
40 POPT_ARG_MAINCALL = 16 + 11
41 POPT_ARG_ARGV = 12
42 POPT_ARG_SHORT = 13
43 POPT_ARG_BITSET = 16 + 14
44)
45
46const POPT_ARG_MASK = 0x000000FF
47
48const (
49 POPT_ARGFLAG_OR = 0x08000000
50)
51
52const (
53 POPT_BIT_SET = POPT_ARG_VAL | POPT_ARGFLAG_OR
54)
55
56type PoptError struct {
57 Errno int32
58 Err error
59}
60
61func (pe *PoptError) Unwrap() error { return pe.Err }
62
63func (pe *PoptError) Error() string { return pe.Err.Error() }
64
65// TODO(later): turn these into sentinel error values.
66// which stringify like poptStrerror().
67const (
68 POPT_ERROR_NOARG = -10 // missing argument
69 POPT_ERROR_BADOPT = -11 // unknown option
70 POPT_ERROR_UNWANTEDARG = -12 // option does not take an argument
71 POPT_ERROR_OPTSTOODEEP = -13 // aliases nested too deeply
72 POPT_ERROR_BADQUOTE = -15 // error in parameter quoting
73 POPT_ERROR_ERRNO = -16 // errno set, use strerror(errno)
74 POPT_ERROR_BADNUMBER = -17 // invalid numeric value
75 POPT_ERROR_OVERFLOW = -18 // number too large or too small
76 POPT_ERROR_BADOPERATION = -19 // mutually exclusive logical operations requested
77 POPT_ERROR_NULLARG = -20 // opt->arg should not be NULL
78 POPT_ERROR_MALLOC = -21 // memory allocation failed
79 POPT_ERROR_BADCONFIG = -22 // config file failed sanity test
80)
81
82type Context struct {
83 // state
84 table []poptOption
85 args []string
86 nextCharArg string
87 nextArg string
88
89 // output
90 Options *Options
91 RemainingArgs []string
92}
93
94func (pc *Context) findOption(longName, shortName string) *poptOption {
95 for idx, opt := range pc.table {
96 if longName != "" && opt.longName == longName {
97 return &pc.table[idx]
98 }
99 if shortName != "" && opt.shortName == shortName {
100 return &pc.table[idx]
101 }
102 }
103 return nil
104}
105
106func (pc *Context) poptSaveInt(opt *poptOption, val int) bool {
107 intPtr := opt.arg.(*int)
108 if intPtr == nil {
109 return false
110 }
111 if opt.argInfo&POPT_ARGFLAG_OR != 0 {
112 *intPtr |= val
113 } else {
114 *intPtr = val
115 }
116 return true
117}
118
119func (pc *Context) poptSaveArg(opt *poptOption, nextArg string) int32 {
120 argType := opt.argInfo & POPT_ARG_MASK
121 switch argType {
122 case POPT_ARG_INT:
123 i, err := strconv.ParseInt(nextArg, 0, 64)
124 if err != nil {
125 return POPT_ERROR_BADNUMBER
126 }
127 if i < math.MinInt32 || i > math.MaxInt32 {
128 return POPT_ERROR_OVERFLOW
129 }
130 pc.poptSaveInt(opt, int(i))
131 return 0
132
133 case POPT_ARG_STRING:
134 stringPtr := opt.arg.(*string)
135 if stringPtr == nil {
136 return 0
137 }
138 *stringPtr = nextArg
139 return 0
140 }
141
142 return POPT_ERROR_BADOPERATION
143}
144
145func (pc *Context) poptGetNextOpt() (int32, error) {
146 var opt *poptOption
147 for {
148 var longArg string
149 if pc.nextCharArg == "" && len(pc.args) == 0 {
150 return -1, nil // done
151 }
152 if pc.nextCharArg == "" {
153 // process next long option
154 origOptString := pc.args[0]
155 pc.args = pc.args[1:]
156 if origOptString == "" {
157 return -1, &PoptError{
158 Errno: POPT_ERROR_BADOPT,
159 Err: fmt.Errorf("unknown option: origOptString empty"),
160 }
161 }
162 if origOptString[0] != '-' || origOptString == "-" {
163 pc.RemainingArgs = append(pc.RemainingArgs, origOptString)
164 continue
165 }
166 before, after, found := strings.Cut(origOptString, "=")
167 if found {
168 longArg = after
169 }
170 // remove the one dash we ensured is present
171 before = strings.TrimPrefix(before, "-")
172 // a second dash is permitted
173 before = strings.TrimPrefix(before, "-")
174 opt = pc.findOption(before, "")
175 if opt == nil {
176 // try and parse it as a short option
177 pc.nextCharArg = origOptString[1:]
178 longArg = ""
179 }
180 }
181 if pc.nextCharArg != "" {
182 // process next short option
183 opt = pc.findOption("", pc.nextCharArg[:1])
184 if opt == nil {
185 return -1, &PoptError{
186 Errno: POPT_ERROR_BADOPT,
187 Err: fmt.Errorf("option %q not found", pc.nextCharArg[:1]),
188 }
189 }
190 pc.nextCharArg = pc.nextCharArg[1:]
191 }
192 if opt == nil {
193 // neither long nor short? how can we end up here?
194 return -1, &PoptError{
195 Errno: POPT_ERROR_BADOPT,
196 Err: fmt.Errorf("neither long nor short option found"),
197 }
198 }
199 argType := opt.argInfo & POPT_ARG_MASK
200 if argType == POPT_ARG_NONE || argType == POPT_ARG_VAL {
201 if longArg != "" || strings.HasPrefix(pc.nextCharArg, "=") {
202 return -1, &PoptError{
203 Errno: POPT_ERROR_UNWANTEDARG,
204 Err: fmt.Errorf("option %s does not take an argument", opt.name()),
205 }
206 }
207 if opt.arg != nil {
208 val := 1
209 if argType == POPT_ARG_VAL {
210 val = opt.val
211 }
212 if !pc.poptSaveInt(opt, val) {
213 return -1, &PoptError{
214 Errno: POPT_ERROR_BADOPERATION,
215 Err: fmt.Errorf("poptSaveInt"),
216 }
217 }
218 }
219 } else {
220 nextArg := longArg
221 if longArg != "" {
222 } else if pc.nextCharArg != "" {
223 nextArg = strings.TrimPrefix(pc.nextCharArg, "=")
224 pc.nextCharArg = ""
225 } else {
226 if len(pc.args) == 0 {
227 return -1, &PoptError{
228 Errno: POPT_ERROR_NOARG,
229 Err: fmt.Errorf("missing argument for option %s", opt.name()),
230 }
231 }
232 nextArg = pc.args[0]
233 pc.args = pc.args[1:]
234 }
235 pc.nextArg = nextArg
236 if opt.arg != nil {
237 if errno := pc.poptSaveArg(opt, nextArg); errno != 0 {
238 return -1, &PoptError{
239 Errno: errno,
240 Err: fmt.Errorf("poptSaveArg"),
241 }
242 }
243 }
244 }
245 if opt.val != 0 && argType != POPT_ARG_VAL {
246 return int32(opt.val), nil
247 }
248 }
249}
250
251func (pc *Context) poptGetOptArg() string {
252 ret := pc.nextArg
253 pc.nextArg = ""
254 return ret
255}