go-micro.dev/v5@v5.12.0/config/source/cli/cli.go (about)

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