Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
348 lines
6.8 KiB
348 lines
6.8 KiB
// Copyright 2012 Jesse van den Kieboom. All rights reserved. |
|
// Use of this source code is governed by a BSD-style |
|
// license that can be found in the LICENSE file. |
|
|
|
package flags |
|
|
|
import ( |
|
"fmt" |
|
"reflect" |
|
"strconv" |
|
"strings" |
|
"time" |
|
) |
|
|
|
// Marshaler is the interface implemented by types that can marshal themselves |
|
// to a string representation of the flag. |
|
type Marshaler interface { |
|
// MarshalFlag marshals a flag value to its string representation. |
|
MarshalFlag() (string, error) |
|
} |
|
|
|
// Unmarshaler is the interface implemented by types that can unmarshal a flag |
|
// argument to themselves. The provided value is directly passed from the |
|
// command line. |
|
type Unmarshaler interface { |
|
// UnmarshalFlag unmarshals a string value representation to the flag |
|
// value (which therefore needs to be a pointer receiver). |
|
UnmarshalFlag(value string) error |
|
} |
|
|
|
func getBase(options multiTag, base int) (int, error) { |
|
sbase := options.Get("base") |
|
|
|
var err error |
|
var ivbase int64 |
|
|
|
if sbase != "" { |
|
ivbase, err = strconv.ParseInt(sbase, 10, 32) |
|
base = int(ivbase) |
|
} |
|
|
|
return base, err |
|
} |
|
|
|
func convertMarshal(val reflect.Value) (bool, string, error) { |
|
// Check first for the Marshaler interface |
|
if val.Type().NumMethod() > 0 && val.CanInterface() { |
|
if marshaler, ok := val.Interface().(Marshaler); ok { |
|
ret, err := marshaler.MarshalFlag() |
|
return true, ret, err |
|
} |
|
} |
|
|
|
return false, "", nil |
|
} |
|
|
|
func convertToString(val reflect.Value, options multiTag) (string, error) { |
|
if ok, ret, err := convertMarshal(val); ok { |
|
return ret, err |
|
} |
|
|
|
tp := val.Type() |
|
|
|
// Support for time.Duration |
|
if tp == reflect.TypeOf((*time.Duration)(nil)).Elem() { |
|
stringer := val.Interface().(fmt.Stringer) |
|
return stringer.String(), nil |
|
} |
|
|
|
switch tp.Kind() { |
|
case reflect.String: |
|
return val.String(), nil |
|
case reflect.Bool: |
|
if val.Bool() { |
|
return "true", nil |
|
} |
|
|
|
return "false", nil |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
base, err := getBase(options, 10) |
|
|
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
return strconv.FormatInt(val.Int(), base), nil |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
|
base, err := getBase(options, 10) |
|
|
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
return strconv.FormatUint(val.Uint(), base), nil |
|
case reflect.Float32, reflect.Float64: |
|
return strconv.FormatFloat(val.Float(), 'g', -1, tp.Bits()), nil |
|
case reflect.Slice: |
|
if val.Len() == 0 { |
|
return "", nil |
|
} |
|
|
|
ret := "[" |
|
|
|
for i := 0; i < val.Len(); i++ { |
|
if i != 0 { |
|
ret += ", " |
|
} |
|
|
|
item, err := convertToString(val.Index(i), options) |
|
|
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
ret += item |
|
} |
|
|
|
return ret + "]", nil |
|
case reflect.Map: |
|
ret := "{" |
|
|
|
for i, key := range val.MapKeys() { |
|
if i != 0 { |
|
ret += ", " |
|
} |
|
|
|
keyitem, err := convertToString(key, options) |
|
|
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
item, err := convertToString(val.MapIndex(key), options) |
|
|
|
if err != nil { |
|
return "", err |
|
} |
|
|
|
ret += keyitem + ":" + item |
|
} |
|
|
|
return ret + "}", nil |
|
case reflect.Ptr: |
|
return convertToString(reflect.Indirect(val), options) |
|
case reflect.Interface: |
|
if !val.IsNil() { |
|
return convertToString(val.Elem(), options) |
|
} |
|
} |
|
|
|
return "", nil |
|
} |
|
|
|
func convertUnmarshal(val string, retval reflect.Value) (bool, error) { |
|
if retval.Type().NumMethod() > 0 && retval.CanInterface() { |
|
if unmarshaler, ok := retval.Interface().(Unmarshaler); ok { |
|
if retval.IsNil() { |
|
retval.Set(reflect.New(retval.Type().Elem())) |
|
|
|
// Re-assign from the new value |
|
unmarshaler = retval.Interface().(Unmarshaler) |
|
} |
|
|
|
return true, unmarshaler.UnmarshalFlag(val) |
|
} |
|
} |
|
|
|
if retval.Type().Kind() != reflect.Ptr && retval.CanAddr() { |
|
return convertUnmarshal(val, retval.Addr()) |
|
} |
|
|
|
if retval.Type().Kind() == reflect.Interface && !retval.IsNil() { |
|
return convertUnmarshal(val, retval.Elem()) |
|
} |
|
|
|
return false, nil |
|
} |
|
|
|
func convert(val string, retval reflect.Value, options multiTag) error { |
|
if ok, err := convertUnmarshal(val, retval); ok { |
|
return err |
|
} |
|
|
|
tp := retval.Type() |
|
|
|
// Support for time.Duration |
|
if tp == reflect.TypeOf((*time.Duration)(nil)).Elem() { |
|
parsed, err := time.ParseDuration(val) |
|
|
|
if err != nil { |
|
return err |
|
} |
|
|
|
retval.SetInt(int64(parsed)) |
|
return nil |
|
} |
|
|
|
switch tp.Kind() { |
|
case reflect.String: |
|
retval.SetString(val) |
|
case reflect.Bool: |
|
if val == "" { |
|
retval.SetBool(true) |
|
} else { |
|
b, err := strconv.ParseBool(val) |
|
|
|
if err != nil { |
|
return err |
|
} |
|
|
|
retval.SetBool(b) |
|
} |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
base, err := getBase(options, 10) |
|
|
|
if err != nil { |
|
return err |
|
} |
|
|
|
parsed, err := strconv.ParseInt(val, base, tp.Bits()) |
|
|
|
if err != nil { |
|
return err |
|
} |
|
|
|
retval.SetInt(parsed) |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
|
base, err := getBase(options, 10) |
|
|
|
if err != nil { |
|
return err |
|
} |
|
|
|
parsed, err := strconv.ParseUint(val, base, tp.Bits()) |
|
|
|
if err != nil { |
|
return err |
|
} |
|
|
|
retval.SetUint(parsed) |
|
case reflect.Float32, reflect.Float64: |
|
parsed, err := strconv.ParseFloat(val, tp.Bits()) |
|
|
|
if err != nil { |
|
return err |
|
} |
|
|
|
retval.SetFloat(parsed) |
|
case reflect.Slice: |
|
elemtp := tp.Elem() |
|
|
|
elemvalptr := reflect.New(elemtp) |
|
elemval := reflect.Indirect(elemvalptr) |
|
|
|
if err := convert(val, elemval, options); err != nil { |
|
return err |
|
} |
|
|
|
retval.Set(reflect.Append(retval, elemval)) |
|
case reflect.Map: |
|
parts := strings.SplitN(val, ":", 2) |
|
|
|
key := parts[0] |
|
var value string |
|
|
|
if len(parts) == 2 { |
|
value = parts[1] |
|
} |
|
|
|
keytp := tp.Key() |
|
keyval := reflect.New(keytp) |
|
|
|
if err := convert(key, keyval, options); err != nil { |
|
return err |
|
} |
|
|
|
valuetp := tp.Elem() |
|
valueval := reflect.New(valuetp) |
|
|
|
if err := convert(value, valueval, options); err != nil { |
|
return err |
|
} |
|
|
|
if retval.IsNil() { |
|
retval.Set(reflect.MakeMap(tp)) |
|
} |
|
|
|
retval.SetMapIndex(reflect.Indirect(keyval), reflect.Indirect(valueval)) |
|
case reflect.Ptr: |
|
if retval.IsNil() { |
|
retval.Set(reflect.New(retval.Type().Elem())) |
|
} |
|
|
|
return convert(val, reflect.Indirect(retval), options) |
|
case reflect.Interface: |
|
if !retval.IsNil() { |
|
return convert(val, retval.Elem(), options) |
|
} |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func isPrint(s string) bool { |
|
for _, c := range s { |
|
if !strconv.IsPrint(c) { |
|
return false |
|
} |
|
} |
|
|
|
return true |
|
} |
|
|
|
func quoteIfNeeded(s string) string { |
|
if !isPrint(s) { |
|
return strconv.Quote(s) |
|
} |
|
|
|
return s |
|
} |
|
|
|
func quoteIfNeededV(s []string) []string { |
|
ret := make([]string, len(s)) |
|
|
|
for i, v := range s { |
|
ret[i] = quoteIfNeeded(v) |
|
} |
|
|
|
return ret |
|
} |
|
|
|
func quoteV(s []string) []string { |
|
ret := make([]string, len(s)) |
|
|
|
for i, v := range s { |
|
ret[i] = strconv.Quote(v) |
|
} |
|
|
|
return ret |
|
} |
|
|
|
func unquoteIfPossible(s string) (string, error) { |
|
if len(s) == 0 || s[0] != '"' { |
|
return s, nil |
|
} |
|
|
|
return strconv.Unquote(s) |
|
}
|
|
|