github.phpd.cn/hashicorp/consul@v1.4.5/command/flags/config.go (about) 1 package flags 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "reflect" 8 "sort" 9 "strconv" 10 "time" 11 12 "github.com/mitchellh/mapstructure" 13 ) 14 15 // TODO (slackpad) - Trying out a different pattern here for config handling. 16 // These classes support the flag.Value interface but work in a manner where 17 // we can tell if they have been set. This lets us work with an all-pointer 18 // config structure and merge it in a clean-ish way. If this ends up being a 19 // good pattern we should pull this out into a reusable library. 20 21 // ConfigDecodeHook should be passed to mapstructure in order to decode into 22 // the *Value objects here. 23 var ConfigDecodeHook = mapstructure.ComposeDecodeHookFunc( 24 BoolToBoolValueFunc(), 25 StringToDurationValueFunc(), 26 StringToStringValueFunc(), 27 Float64ToUintValueFunc(), 28 ) 29 30 // BoolValue provides a flag value that's aware if it has been set. 31 type BoolValue struct { 32 v *bool 33 } 34 35 // IsBoolFlag is an optional method of the flag.Value 36 // interface which marks this value as boolean when 37 // the return value is true. See flag.Value for details. 38 func (b *BoolValue) IsBoolFlag() bool { 39 return true 40 } 41 42 // Merge will overlay this value if it has been set. 43 func (b *BoolValue) Merge(onto *bool) { 44 if b.v != nil { 45 *onto = *(b.v) 46 } 47 } 48 49 // Set implements the flag.Value interface. 50 func (b *BoolValue) Set(v string) error { 51 if b.v == nil { 52 b.v = new(bool) 53 } 54 var err error 55 *(b.v), err = strconv.ParseBool(v) 56 return err 57 } 58 59 // String implements the flag.Value interface. 60 func (b *BoolValue) String() string { 61 var current bool 62 if b.v != nil { 63 current = *(b.v) 64 } 65 return fmt.Sprintf("%v", current) 66 } 67 68 // BoolToBoolValueFunc is a mapstructure hook that looks for an incoming bool 69 // mapped to a BoolValue and does the translation. 70 func BoolToBoolValueFunc() mapstructure.DecodeHookFunc { 71 return func( 72 f reflect.Type, 73 t reflect.Type, 74 data interface{}) (interface{}, error) { 75 if f.Kind() != reflect.Bool { 76 return data, nil 77 } 78 79 val := BoolValue{} 80 if t != reflect.TypeOf(val) { 81 return data, nil 82 } 83 84 val.v = new(bool) 85 *(val.v) = data.(bool) 86 return val, nil 87 } 88 } 89 90 // DurationValue provides a flag value that's aware if it has been set. 91 type DurationValue struct { 92 v *time.Duration 93 } 94 95 // Merge will overlay this value if it has been set. 96 func (d *DurationValue) Merge(onto *time.Duration) { 97 if d.v != nil { 98 *onto = *(d.v) 99 } 100 } 101 102 // Set implements the flag.Value interface. 103 func (d *DurationValue) Set(v string) error { 104 if d.v == nil { 105 d.v = new(time.Duration) 106 } 107 var err error 108 *(d.v), err = time.ParseDuration(v) 109 return err 110 } 111 112 // String implements the flag.Value interface. 113 func (d *DurationValue) String() string { 114 var current time.Duration 115 if d.v != nil { 116 current = *(d.v) 117 } 118 return current.String() 119 } 120 121 // StringToDurationValueFunc is a mapstructure hook that looks for an incoming 122 // string mapped to a DurationValue and does the translation. 123 func StringToDurationValueFunc() mapstructure.DecodeHookFunc { 124 return func( 125 f reflect.Type, 126 t reflect.Type, 127 data interface{}) (interface{}, error) { 128 if f.Kind() != reflect.String { 129 return data, nil 130 } 131 132 val := DurationValue{} 133 if t != reflect.TypeOf(val) { 134 return data, nil 135 } 136 if err := val.Set(data.(string)); err != nil { 137 return nil, err 138 } 139 return val, nil 140 } 141 } 142 143 // StringValue provides a flag value that's aware if it has been set. 144 type StringValue struct { 145 v *string 146 } 147 148 // Merge will overlay this value if it has been set. 149 func (s *StringValue) Merge(onto *string) { 150 if s.v != nil { 151 *onto = *(s.v) 152 } 153 } 154 155 // Set implements the flag.Value interface. 156 func (s *StringValue) Set(v string) error { 157 if s.v == nil { 158 s.v = new(string) 159 } 160 *(s.v) = v 161 return nil 162 } 163 164 // String implements the flag.Value interface. 165 func (s *StringValue) String() string { 166 var current string 167 if s.v != nil { 168 current = *(s.v) 169 } 170 return current 171 } 172 173 // StringToStringValueFunc is a mapstructure hook that looks for an incoming 174 // string mapped to a StringValue and does the translation. 175 func StringToStringValueFunc() mapstructure.DecodeHookFunc { 176 return func( 177 f reflect.Type, 178 t reflect.Type, 179 data interface{}) (interface{}, error) { 180 if f.Kind() != reflect.String { 181 return data, nil 182 } 183 184 val := StringValue{} 185 if t != reflect.TypeOf(val) { 186 return data, nil 187 } 188 val.v = new(string) 189 *(val.v) = data.(string) 190 return val, nil 191 } 192 } 193 194 // UintValue provides a flag value that's aware if it has been set. 195 type UintValue struct { 196 v *uint 197 } 198 199 // Merge will overlay this value if it has been set. 200 func (u *UintValue) Merge(onto *uint) { 201 if u.v != nil { 202 *onto = *(u.v) 203 } 204 } 205 206 // Set implements the flag.Value interface. 207 func (u *UintValue) Set(v string) error { 208 if u.v == nil { 209 u.v = new(uint) 210 } 211 parsed, err := strconv.ParseUint(v, 0, 64) 212 *(u.v) = (uint)(parsed) 213 return err 214 } 215 216 // String implements the flag.Value interface. 217 func (u *UintValue) String() string { 218 var current uint 219 if u.v != nil { 220 current = *(u.v) 221 } 222 return fmt.Sprintf("%v", current) 223 } 224 225 // Float64ToUintValueFunc is a mapstructure hook that looks for an incoming 226 // float64 mapped to a UintValue and does the translation. 227 func Float64ToUintValueFunc() mapstructure.DecodeHookFunc { 228 return func( 229 f reflect.Type, 230 t reflect.Type, 231 data interface{}) (interface{}, error) { 232 if f.Kind() != reflect.Float64 { 233 return data, nil 234 } 235 236 val := UintValue{} 237 if t != reflect.TypeOf(val) { 238 return data, nil 239 } 240 241 fv := data.(float64) 242 if fv < 0 { 243 return nil, fmt.Errorf("value cannot be negative") 244 } 245 246 // The standard guarantees at least this, and this is fine for 247 // values we expect to use in configs vs. being fancy with the 248 // machine's size for uint. 249 if fv > (1<<32 - 1) { 250 return nil, fmt.Errorf("value is too large") 251 } 252 253 val.v = new(uint) 254 *(val.v) = (uint)(fv) 255 return val, nil 256 } 257 } 258 259 // VisitFn is a callback that gets a chance to visit each file found during a 260 // traversal with visit(). 261 type VisitFn func(path string) error 262 263 // Visit will call the visitor function on the path if it's a file, or for each 264 // file in the path if it's a directory. Directories will not be recursed into, 265 // and files in the directory will be visited in alphabetical order. 266 func Visit(path string, visitor VisitFn) error { 267 f, err := os.Open(path) 268 if err != nil { 269 return fmt.Errorf("error reading %q: %v", path, err) 270 } 271 defer f.Close() 272 273 fi, err := f.Stat() 274 if err != nil { 275 return fmt.Errorf("error checking %q: %v", path, err) 276 } 277 278 if !fi.IsDir() { 279 if err := visitor(path); err != nil { 280 return fmt.Errorf("error in %q: %v", path, err) 281 } 282 return nil 283 } 284 285 contents, err := f.Readdir(-1) 286 if err != nil { 287 return fmt.Errorf("error listing %q: %v", path, err) 288 } 289 290 sort.Sort(dirEnts(contents)) 291 for _, fi := range contents { 292 if fi.IsDir() { 293 continue 294 } 295 296 fullPath := filepath.Join(path, fi.Name()) 297 if err := visitor(fullPath); err != nil { 298 return fmt.Errorf("error in %q: %v", fullPath, err) 299 } 300 } 301 302 return nil 303 } 304 305 // dirEnts applies sort.Interface to directory entries for sorting by name. 306 type dirEnts []os.FileInfo 307 308 func (d dirEnts) Len() int { return len(d) } 309 func (d dirEnts) Less(i, j int) bool { return d[i].Name() < d[j].Name() } 310 func (d dirEnts) Swap(i, j int) { d[i], d[j] = d[j], d[i] }