diff --git a/flag.go b/flag.go index 9e37f79..a98e11d 100644 --- a/flag.go +++ b/flag.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "reflect" + "strings" "time" "go.unistack.org/micro/v3/config" @@ -30,6 +31,8 @@ var ( type flagConfig struct { fset *flag.FlagSet opts config.Options + name string + env string } func (c *flagConfig) Options() config.Options { @@ -40,19 +43,23 @@ func (c *flagConfig) Init(opts ...config.Option) error { for _, o := range opts { o(&c.opts) } + c.configure() fields, err := rutil.StructFields(c.opts.Struct) if err != nil { return err } - // flag.CommandLine.Init(os.Args[0], flag.ContinueOnError) for _, sf := range fields { tf, ok := sf.Field.Tag.Lookup(c.opts.StructTag) if !ok { continue } + fn, fd, fv := getFlagOpts(tf) + if tf, ok = sf.Field.Tag.Lookup(c.env); ok { + fd += fmt.Sprintf(" (env %s)", tf) + } rcheck := true @@ -67,7 +74,6 @@ func (c *flagConfig) Init(opts ...config.Option) error { return nil } - fmt.Printf("register %s flag\n", fn) switch vi.(type) { case time.Duration: err = c.flagDuration(sf.Value, fn, fv, fd) @@ -149,42 +155,125 @@ func (c *flagConfig) Watch(ctx context.Context, opts ...config.WatchOption) (con return nil, fmt.Errorf("not implemented") } +func (c *flagConfig) usage() { + mapDelim := DefaultMapDelim + sliceDelim := DefaultSliceDelim + + if c.opts.Context != nil { + if d, ok := c.opts.Context.Value(mapDelimKey{}).(string); ok { + mapDelim = d + } + if d, ok := c.opts.Context.Value(sliceDelimKey{}).(string); ok { + sliceDelim = d + } + } + + if c.name == "" { + fmt.Fprintf(c.fset.Output(), "Usage:\n") + } else { + fmt.Fprintf(c.fset.Output(), "Usage of %s:\n", c.name) + } + + c.fset.VisitAll(func(f *flag.Flag) { + var b strings.Builder + fmt.Fprintf(&b, " -%s", f.Name) // Two spaces before -; see next two comments. + _, usage := flag.UnquoteUsage(f) + name := "value" + v := reflect.TypeOf(f.Value).String() + b.WriteString(" ") + switch v { + case "*flag.boolFlag": + name = "bool" + case "*flag.durationValue": + name = "duration" + case "*flag.float64Value": + name = "float" + case "*flag.intValue", "*flag.int64Value": + name = "int" + case "*flag.stringValue": + name = "string" + case "*flag.uintValue", "*flag.uint64Value": + name = "uint" + case "*flag.mapValue": + // nv := f.Value.(*mapValue) + name = fmt.Sprintf("string key=val with %q as separator", mapDelim) + case "*flag.sliceValue": + // nv := f.Value.(*sliceValue) + name = fmt.Sprintf("string with %q as separator", sliceDelim) + } + b.WriteString(name) + + // Boolean flags of one ASCII letter are so common we + // treat them specially, putting their usage on the same line. + if b.Len() <= 4 { // space, space, '-', 'x'. + b.WriteString("\t") + } else { + // Four spaces before the tab triggers good alignment + // for both 4- and 8-space tab stops. + b.WriteString("\n \t") + } + b.WriteString(strings.ReplaceAll(usage, "\n", "\n \t")) + + if f.Value.String() == f.DefValue { + fmt.Fprintf(&b, " (default %q)", f.DefValue) + } else { + fmt.Fprintf(&b, " (default %q current %q)", f.DefValue, f.Value) + } + + fmt.Fprint(c.fset.Output(), b.String(), "\n") + }) +} + +func (c *flagConfig) configure() { + flagSet := flag.CommandLine + flagSetName := os.Args[0] + flagSetErrorHandling := flag.ExitOnError + flagEnv := "env" + var flagUsage func() + var isSet bool + + if c.opts.Context != nil { + if v, ok := c.opts.Context.Value(flagSetNameKey{}).(string); ok { + isSet = true + flagSetName = v + } + if v, ok := c.opts.Context.Value(flagSetErrorHandlingKey{}).(flag.ErrorHandling); ok { + isSet = true + flagSetErrorHandling = v + } + if v, ok := c.opts.Context.Value(flagSetKey{}).(*flag.FlagSet); ok { + flagSet = v + } + if v, ok := c.opts.Context.Value(flagSetUsageKey{}).(func()); ok { + flagUsage = v + } + if v, ok := c.opts.Context.Value(flagEnvKey{}).(string); ok { + flagEnv = v + } + } + c.fset = flagSet + + if isSet { + c.fset.Init(flagSetName, flagSetErrorHandling) + } + if flagUsage != nil { + c.fset.Usage = flagUsage + } else { + c.fset.Usage = c.usage + } + c.env = flagEnv + + c.name = flagSetName +} + func NewConfig(opts ...config.Option) config.Config { options := config.NewOptions(opts...) if len(options.StructTag) == 0 { options.StructTag = DefaultStructTag } - flagSet := flag.CommandLine - flagSetName := os.Args[0] - flagSetErrorHandling := flag.ExitOnError - var flagUsage func() - var isSet bool - if options.Context != nil { - if v, ok := options.Context.Value(flagSetNameKey{}).(string); ok { - isSet = true - flagSetName = v - } - if v, ok := options.Context.Value(flagSetErrorHandlingKey{}).(flag.ErrorHandling); ok { - isSet = true - flagSetErrorHandling = v - } - if v, ok := options.Context.Value(flagSetKey{}).(*flag.FlagSet); ok { - flagSet = v - } - if v, ok := options.Context.Value(flagSetUsageKey{}).(func()); ok { - flagUsage = v - } - } - - if isSet { - flagSet.Init(flagSetName, flagSetErrorHandling) - } - if flagUsage != nil { - flagSet.Usage = flagUsage - } - - c := &flagConfig{opts: options, fset: flagSet} + c := &flagConfig{opts: options} + c.configure() return c } diff --git a/options.go b/options.go index 02a84e3..3830aed 100644 --- a/options.go +++ b/options.go @@ -54,3 +54,11 @@ type flagSetUsageKey struct{} func FlagUsage(fn func()) config.Option { return config.SetOption(flagSetUsageKey{}, fn) } + + +type flagEnvKey struct{} + +// FlagEnv set flag set usage func +func FlagEnv(n string) config.Option { + return config.SetOption(flagEnvKey{}, n) +} diff --git a/util.go b/util.go index a59cf26..8f9c704 100644 --- a/util.go +++ b/util.go @@ -15,7 +15,7 @@ type mapValue struct { v reflect.Value } -func (v mapValue) String() string { +func (v *mapValue) String() string { if v.v.Kind() != reflect.Invalid { var kv []string it := v.v.MapRange() @@ -29,7 +29,11 @@ func (v mapValue) String() string { return v.def } -func (v mapValue) Set(s string) error { +func (v *mapValue) Get() interface{} { + return v.v.Interface() +} + +func (v *mapValue) Set(s string) error { ps := strings.Split(s, v.delim) if len(ps) == 0 { return nil @@ -72,7 +76,7 @@ type sliceValue struct { v reflect.Value } -func (v sliceValue) String() string { +func (v *sliceValue) String() string { if v.v.Kind() != reflect.Invalid { var kv []string for idx := 0; idx < v.v.Len(); idx++ { @@ -83,7 +87,11 @@ func (v sliceValue) String() string { return v.def } -func (v sliceValue) Set(s string) error { +func (v *sliceValue) Get() interface{} { + return v.v.Interface() +} + +func (v *sliceValue) Set(s string) error { p := strings.Split(s, v.delim) v.v.Set(reflect.MakeSlice(v.v.Type(), len(p), len(p))) switch v.v.Type().Elem().Kind() {