github.com/annwntech/go-micro/v2@v2.9.5/config/source/cli/cli.go (about) 1 package cli 2 3 import ( 4 "flag" 5 "io/ioutil" 6 "os" 7 "strings" 8 "time" 9 10 "github.com/imdario/mergo" 11 "github.com/micro/cli/v2" 12 "github.com/annwntech/go-micro/v2/config/cmd" 13 "github.com/annwntech/go-micro/v2/config/source" 14 ) 15 16 type cliSource struct { 17 opts source.Options 18 ctx *cli.Context 19 } 20 21 func (c *cliSource) Read() (*source.ChangeSet, error) { 22 var changes map[string]interface{} 23 24 // directly using app cli flags, to access default values of not specified options 25 for _, f := range c.ctx.App.Flags { 26 name := f.Names()[0] 27 tmp := toEntry(name, c.ctx.Generic(name)) 28 mergo.Map(&changes, tmp) // need to sort error handling 29 } 30 31 b, err := c.opts.Encoder.Encode(changes) 32 if err != nil { 33 return nil, err 34 } 35 36 cs := &source.ChangeSet{ 37 Format: c.opts.Encoder.String(), 38 Data: b, 39 Timestamp: time.Now(), 40 Source: c.String(), 41 } 42 cs.Checksum = cs.Sum() 43 44 return cs, nil 45 } 46 47 func toEntry(name string, v interface{}) map[string]interface{} { 48 n := strings.ToLower(name) 49 keys := strings.FieldsFunc(n, split) 50 reverse(keys) 51 tmp := make(map[string]interface{}) 52 for i, k := range keys { 53 if i == 0 { 54 tmp[k] = v 55 continue 56 } 57 58 tmp = map[string]interface{}{k: tmp} 59 } 60 return tmp 61 } 62 63 func reverse(ss []string) { 64 for i := len(ss)/2 - 1; i >= 0; i-- { 65 opp := len(ss) - 1 - i 66 ss[i], ss[opp] = ss[opp], ss[i] 67 } 68 } 69 70 func split(r rune) bool { 71 return r == '-' || r == '_' 72 } 73 74 func (c *cliSource) Watch() (source.Watcher, error) { 75 return source.NewNoopWatcher() 76 } 77 78 // Write is unsupported 79 func (c *cliSource) Write(cs *source.ChangeSet) error { 80 return nil 81 } 82 83 func (c *cliSource) String() string { 84 return "cli" 85 } 86 87 // NewSource returns a config source for integrating parsed flags from a micro/cli.Context. 88 // Hyphens are delimiters for nesting, and all keys are lowercased. The assumption is that 89 // command line flags have already been parsed. 90 // 91 // Example: 92 // cli.StringFlag{Name: "db-host"}, 93 // 94 // 95 // { 96 // "database": { 97 // "host": "localhost" 98 // } 99 // } 100 func NewSource(opts ...source.Option) source.Source { 101 options := source.NewOptions(opts...) 102 103 var ctx *cli.Context 104 105 if c, ok := options.Context.Value(contextKey{}).(*cli.Context); ok { 106 ctx = c 107 } else { 108 // no context 109 // get the default app/flags 110 app := cmd.App() 111 flags := app.Flags 112 113 // create flagset 114 set := flag.NewFlagSet(app.Name, flag.ContinueOnError) 115 116 // apply flags to set 117 for _, f := range flags { 118 f.Apply(set) 119 } 120 121 // parse flags 122 set.SetOutput(ioutil.Discard) 123 set.Parse(os.Args[1:]) 124 125 // normalise flags 126 normalizeFlags(app.Flags, set) 127 128 // create context 129 ctx = cli.NewContext(app, set, nil) 130 } 131 132 return &cliSource{ 133 ctx: ctx, 134 opts: options, 135 } 136 } 137 138 // WithContext returns a new source with the context specified. 139 // The assumption is that Context is retrieved within an app.Action function. 140 func WithContext(ctx *cli.Context, opts ...source.Option) source.Source { 141 return &cliSource{ 142 ctx: ctx, 143 opts: source.NewOptions(opts...), 144 } 145 }