code.cestus.io/tools/fabricator@v0.4.3/pkg/ff/fftoml/fftoml.go (about) 1 // Package fftoml provides a TOML config file paser. 2 package fftoml 3 4 import ( 5 "fmt" 6 "io" 7 "strconv" 8 9 "code.cestus.io/tools/fabricator/pkg/ff" 10 "github.com/pelletier/go-toml" 11 ) 12 13 // Parser is a parser for TOML file format. Flags and their values are read 14 // from the key/value pairs defined in the config file. 15 func Parser(r io.Reader, set func(name, value string) error) error { 16 return New().Parse(r, set) 17 } 18 19 // ConfigFileParser is a parser for the TOML file format. Flags and their values 20 // are read from the key/value pairs defined in the config file. 21 // Nested tables and keys are concatenated with a delimiter to derive the 22 // relevant flag name. 23 type ConfigFileParser struct { 24 delimiter string 25 } 26 27 // New constructs and configures a ConfigFileParser using the provided options. 28 func New(opts ...Option) (c ConfigFileParser) { 29 c.delimiter = "." 30 for _, opt := range opts { 31 opt(&c) 32 } 33 return c 34 } 35 36 // Parse parses the provided io.Reader as a TOML file and uses the provided set function 37 // to set flag names derived from the tables names and their key/value pairs. 38 func (c ConfigFileParser) Parse(r io.Reader, set func(name, value string) error) error { 39 tree, err := toml.LoadReader(r) 40 if err != nil { 41 return ParseError{Inner: err} 42 } 43 44 return parseTree(tree, "", c.delimiter, set) 45 } 46 47 // Option is a function which changes the behavior of the TOML config file parser. 48 type Option func(*ConfigFileParser) 49 50 // WithTableDelimiter is an option which configures a delimiter 51 // used to prefix table names onto keys when constructing 52 // their associated flag name. 53 // The default delimiter is "." 54 // 55 // For example, given the following TOML 56 // 57 // [section.subsection] 58 // value = 10 59 // 60 // Parse will match to a flag with the name `-section.subsection.value` by default. 61 // If the delimiter is "-", Parse will match to `-section-subsection-value` instead. 62 func WithTableDelimiter(d string) Option { 63 return func(c *ConfigFileParser) { 64 c.delimiter = d 65 } 66 } 67 68 func parseTree(tree *toml.Tree, parent, delimiter string, set func(name, value string) error) error { 69 for _, key := range tree.Keys() { 70 name := key 71 if parent != "" { 72 name = parent + delimiter + key 73 } 74 switch t := tree.Get(key).(type) { 75 case *toml.Tree: 76 if err := parseTree(t, name, delimiter, set); err != nil { 77 return err 78 } 79 case interface{}: 80 values, err := valsToStrs(t) 81 if err != nil { 82 return ParseError{Inner: err} 83 } 84 for _, value := range values { 85 if err = set(name, value); err != nil { 86 return err 87 } 88 } 89 } 90 } 91 return nil 92 } 93 94 func valsToStrs(val interface{}) ([]string, error) { 95 if vals, ok := val.([]interface{}); ok { 96 ss := make([]string, len(vals)) 97 for i := range vals { 98 s, err := valToStr(vals[i]) 99 if err != nil { 100 return nil, err 101 } 102 ss[i] = s 103 } 104 return ss, nil 105 } 106 s, err := valToStr(val) 107 if err != nil { 108 return nil, err 109 } 110 return []string{s}, nil 111 112 } 113 114 func valToStr(val interface{}) (string, error) { 115 switch v := val.(type) { 116 case string: 117 return v, nil 118 case bool: 119 return strconv.FormatBool(v), nil 120 case uint64: 121 return strconv.FormatUint(v, 10), nil 122 case int64: 123 return strconv.FormatInt(v, 10), nil 124 case float64: 125 return strconv.FormatFloat(v, 'g', -1, 64), nil 126 default: 127 return "", ff.StringConversionError{Value: val} 128 } 129 } 130 131 // ParseError wraps all errors originating from the TOML parser. 132 type ParseError struct { 133 Inner error 134 } 135 136 // Error implenents the error interface. 137 func (e ParseError) Error() string { 138 return fmt.Sprintf("error parsing TOML config: %v", e.Inner) 139 } 140 141 // Unwrap implements the errors.Wrapper interface, allowing errors.Is and 142 // errors.As to work with ParseErrors. 143 func (e ParseError) Unwrap() error { 144 return e.Inner 145 }