blob: b27f698dc4b1d04731f47908ca8efe2aaec0f20d [file] [log] [blame]
Naveen Sampath04696f72022-06-13 15:19:14 +05301// Copyright 2012 Jesse van den Kieboom. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package flags
6
7import (
8 "fmt"
9 "reflect"
10 "strconv"
11 "strings"
12 "time"
13)
14
15// Marshaler is the interface implemented by types that can marshal themselves
16// to a string representation of the flag.
17type Marshaler interface {
18 // MarshalFlag marshals a flag value to its string representation.
19 MarshalFlag() (string, error)
20}
21
22// Unmarshaler is the interface implemented by types that can unmarshal a flag
23// argument to themselves. The provided value is directly passed from the
24// command line.
25type Unmarshaler interface {
26 // UnmarshalFlag unmarshals a string value representation to the flag
27 // value (which therefore needs to be a pointer receiver).
28 UnmarshalFlag(value string) error
29}
30
31// ValueValidator is the interface implemented by types that can validate a
32// flag argument themselves. The provided value is directly passed from the
33// command line.
34type ValueValidator interface {
35 // IsValidValue returns an error if the provided string value is valid for
36 // the flag.
37 IsValidValue(value string) error
38}
39
40func getBase(options multiTag, base int) (int, error) {
41 sbase := options.Get("base")
42
43 var err error
44 var ivbase int64
45
46 if sbase != "" {
47 ivbase, err = strconv.ParseInt(sbase, 10, 32)
48 base = int(ivbase)
49 }
50
51 return base, err
52}
53
54func convertMarshal(val reflect.Value) (bool, string, error) {
55 // Check first for the Marshaler interface
Abhay Kumarfe505f22025-11-10 14:16:31 +000056 if val.IsValid() && val.Type().NumMethod() > 0 && val.CanInterface() {
Naveen Sampath04696f72022-06-13 15:19:14 +053057 if marshaler, ok := val.Interface().(Marshaler); ok {
58 ret, err := marshaler.MarshalFlag()
59 return true, ret, err
60 }
61 }
62
63 return false, "", nil
64}
65
66func convertToString(val reflect.Value, options multiTag) (string, error) {
67 if ok, ret, err := convertMarshal(val); ok {
68 return ret, err
69 }
70
Abhay Kumarfe505f22025-11-10 14:16:31 +000071 if !val.IsValid() {
72 return "", nil
73 }
74
Naveen Sampath04696f72022-06-13 15:19:14 +053075 tp := val.Type()
76
77 // Support for time.Duration
78 if tp == reflect.TypeOf((*time.Duration)(nil)).Elem() {
79 stringer := val.Interface().(fmt.Stringer)
80 return stringer.String(), nil
81 }
82
83 switch tp.Kind() {
84 case reflect.String:
85 return val.String(), nil
86 case reflect.Bool:
87 if val.Bool() {
88 return "true", nil
89 }
90
91 return "false", nil
92 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
93 base, err := getBase(options, 10)
94
95 if err != nil {
96 return "", err
97 }
98
99 return strconv.FormatInt(val.Int(), base), nil
100 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
101 base, err := getBase(options, 10)
102
103 if err != nil {
104 return "", err
105 }
106
107 return strconv.FormatUint(val.Uint(), base), nil
108 case reflect.Float32, reflect.Float64:
109 return strconv.FormatFloat(val.Float(), 'g', -1, tp.Bits()), nil
110 case reflect.Slice:
111 if val.Len() == 0 {
112 return "", nil
113 }
114
115 ret := "["
116
117 for i := 0; i < val.Len(); i++ {
118 if i != 0 {
119 ret += ", "
120 }
121
122 item, err := convertToString(val.Index(i), options)
123
124 if err != nil {
125 return "", err
126 }
127
128 ret += item
129 }
130
131 return ret + "]", nil
132 case reflect.Map:
133 ret := "{"
134
135 for i, key := range val.MapKeys() {
136 if i != 0 {
137 ret += ", "
138 }
139
140 keyitem, err := convertToString(key, options)
141
142 if err != nil {
143 return "", err
144 }
145
146 item, err := convertToString(val.MapIndex(key), options)
147
148 if err != nil {
149 return "", err
150 }
151
152 ret += keyitem + ":" + item
153 }
154
155 return ret + "}", nil
156 case reflect.Ptr:
157 return convertToString(reflect.Indirect(val), options)
158 case reflect.Interface:
159 if !val.IsNil() {
160 return convertToString(val.Elem(), options)
161 }
162 }
163
164 return "", nil
165}
166
167func convertUnmarshal(val string, retval reflect.Value) (bool, error) {
168 if retval.Type().NumMethod() > 0 && retval.CanInterface() {
169 if unmarshaler, ok := retval.Interface().(Unmarshaler); ok {
170 if retval.IsNil() {
171 retval.Set(reflect.New(retval.Type().Elem()))
172
173 // Re-assign from the new value
174 unmarshaler = retval.Interface().(Unmarshaler)
175 }
176
177 return true, unmarshaler.UnmarshalFlag(val)
178 }
179 }
180
181 if retval.Type().Kind() != reflect.Ptr && retval.CanAddr() {
182 return convertUnmarshal(val, retval.Addr())
183 }
184
185 if retval.Type().Kind() == reflect.Interface && !retval.IsNil() {
186 return convertUnmarshal(val, retval.Elem())
187 }
188
189 return false, nil
190}
191
192func convert(val string, retval reflect.Value, options multiTag) error {
193 if ok, err := convertUnmarshal(val, retval); ok {
194 return err
195 }
196
197 tp := retval.Type()
198
199 // Support for time.Duration
200 if tp == reflect.TypeOf((*time.Duration)(nil)).Elem() {
201 parsed, err := time.ParseDuration(val)
202
203 if err != nil {
204 return err
205 }
206
207 retval.SetInt(int64(parsed))
208 return nil
209 }
210
211 switch tp.Kind() {
212 case reflect.String:
213 retval.SetString(val)
214 case reflect.Bool:
215 if val == "" {
216 retval.SetBool(true)
217 } else {
218 b, err := strconv.ParseBool(val)
219
220 if err != nil {
221 return err
222 }
223
224 retval.SetBool(b)
225 }
226 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
Abhay Kumarfe505f22025-11-10 14:16:31 +0000227 base, err := getBase(options, 0)
Naveen Sampath04696f72022-06-13 15:19:14 +0530228
229 if err != nil {
230 return err
231 }
232
233 parsed, err := strconv.ParseInt(val, base, tp.Bits())
234
235 if err != nil {
236 return err
237 }
238
239 retval.SetInt(parsed)
240 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
Abhay Kumarfe505f22025-11-10 14:16:31 +0000241 base, err := getBase(options, 0)
Naveen Sampath04696f72022-06-13 15:19:14 +0530242
243 if err != nil {
244 return err
245 }
246
247 parsed, err := strconv.ParseUint(val, base, tp.Bits())
248
249 if err != nil {
250 return err
251 }
252
253 retval.SetUint(parsed)
254 case reflect.Float32, reflect.Float64:
255 parsed, err := strconv.ParseFloat(val, tp.Bits())
256
257 if err != nil {
258 return err
259 }
260
261 retval.SetFloat(parsed)
262 case reflect.Slice:
263 elemtp := tp.Elem()
264
265 elemvalptr := reflect.New(elemtp)
266 elemval := reflect.Indirect(elemvalptr)
267
268 if err := convert(val, elemval, options); err != nil {
269 return err
270 }
271
272 retval.Set(reflect.Append(retval, elemval))
273 case reflect.Map:
Abhay Kumarfe505f22025-11-10 14:16:31 +0000274 keyValueDelimiter := options.Get("key-value-delimiter")
275 if keyValueDelimiter == "" {
276 keyValueDelimiter = ":"
277 }
278
279 parts := strings.SplitN(val, keyValueDelimiter, 2)
Naveen Sampath04696f72022-06-13 15:19:14 +0530280
281 key := parts[0]
282 var value string
283
284 if len(parts) == 2 {
285 value = parts[1]
286 }
287
288 keytp := tp.Key()
289 keyval := reflect.New(keytp)
290
291 if err := convert(key, keyval, options); err != nil {
292 return err
293 }
294
295 valuetp := tp.Elem()
296 valueval := reflect.New(valuetp)
297
298 if err := convert(value, valueval, options); err != nil {
299 return err
300 }
301
302 if retval.IsNil() {
303 retval.Set(reflect.MakeMap(tp))
304 }
305
306 retval.SetMapIndex(reflect.Indirect(keyval), reflect.Indirect(valueval))
307 case reflect.Ptr:
308 if retval.IsNil() {
309 retval.Set(reflect.New(retval.Type().Elem()))
310 }
311
312 return convert(val, reflect.Indirect(retval), options)
313 case reflect.Interface:
314 if !retval.IsNil() {
315 return convert(val, retval.Elem(), options)
316 }
317 }
318
319 return nil
320}
321
322func isPrint(s string) bool {
323 for _, c := range s {
324 if !strconv.IsPrint(c) {
325 return false
326 }
327 }
328
329 return true
330}
331
332func quoteIfNeeded(s string) string {
333 if !isPrint(s) {
334 return strconv.Quote(s)
335 }
336
337 return s
338}
339
340func quoteIfNeededV(s []string) []string {
341 ret := make([]string, len(s))
342
343 for i, v := range s {
344 ret[i] = quoteIfNeeded(v)
345 }
346
347 return ret
348}
349
350func quoteV(s []string) []string {
351 ret := make([]string, len(s))
352
353 for i, v := range s {
354 ret[i] = strconv.Quote(v)
355 }
356
357 return ret
358}
359
360func unquoteIfPossible(s string) (string, error) {
361 if len(s) == 0 || s[0] != '"' {
362 return s, nil
363 }
364
365 return strconv.Unquote(s)
366}