| package cli |
| |
| import ( |
| "flag" |
| "reflect" |
| ) |
| |
| type ( |
| // SliceFlag extends implementations like StringSliceFlag and IntSliceFlag with support for using slices directly, |
| // as Value and/or Destination. |
| // See also SliceFlagTarget, MultiStringFlag, MultiFloat64Flag, MultiInt64Flag, MultiIntFlag. |
| SliceFlag[T SliceFlagTarget[E], S ~[]E, E any] struct { |
| Target T |
| Value S |
| Destination *S |
| } |
| |
| // SliceFlagTarget models a target implementation for use with SliceFlag. |
| // The three methods, SetValue, SetDestination, and GetDestination, are necessary to propagate Value and |
| // Destination, where Value is propagated inwards (initially), and Destination is propagated outwards (on every |
| // update). |
| SliceFlagTarget[E any] interface { |
| Flag |
| RequiredFlag |
| DocGenerationFlag |
| VisibleFlag |
| CategorizableFlag |
| |
| // SetValue should propagate the given slice to the target, ideally as a new value. |
| // Note that a nil slice should nil/clear any existing value (modelled as ~[]E). |
| SetValue(slice []E) |
| // SetDestination should propagate the given slice to the target, ideally as a new value. |
| // Note that a nil slice should nil/clear any existing value (modelled as ~*[]E). |
| SetDestination(slice []E) |
| // GetDestination should return the current value referenced by any destination, or nil if nil/unset. |
| GetDestination() []E |
| } |
| |
| // MultiStringFlag extends StringSliceFlag with support for using slices directly, as Value and/or Destination. |
| // See also SliceFlag. |
| MultiStringFlag = SliceFlag[*StringSliceFlag, []string, string] |
| |
| // MultiFloat64Flag extends Float64SliceFlag with support for using slices directly, as Value and/or Destination. |
| // See also SliceFlag. |
| MultiFloat64Flag = SliceFlag[*Float64SliceFlag, []float64, float64] |
| |
| // MultiInt64Flag extends Int64SliceFlag with support for using slices directly, as Value and/or Destination. |
| // See also SliceFlag. |
| MultiInt64Flag = SliceFlag[*Int64SliceFlag, []int64, int64] |
| |
| // MultiIntFlag extends IntSliceFlag with support for using slices directly, as Value and/or Destination. |
| // See also SliceFlag. |
| MultiIntFlag = SliceFlag[*IntSliceFlag, []int, int] |
| |
| flagValueHook struct { |
| value Generic |
| hook func() |
| } |
| ) |
| |
| var ( |
| // compile time assertions |
| |
| _ SliceFlagTarget[string] = (*StringSliceFlag)(nil) |
| _ SliceFlagTarget[string] = (*SliceFlag[*StringSliceFlag, []string, string])(nil) |
| _ SliceFlagTarget[string] = (*MultiStringFlag)(nil) |
| _ SliceFlagTarget[float64] = (*MultiFloat64Flag)(nil) |
| _ SliceFlagTarget[int64] = (*MultiInt64Flag)(nil) |
| _ SliceFlagTarget[int] = (*MultiIntFlag)(nil) |
| |
| _ Generic = (*flagValueHook)(nil) |
| _ Serializer = (*flagValueHook)(nil) |
| ) |
| |
| func (x *SliceFlag[T, S, E]) Apply(set *flag.FlagSet) error { |
| x.Target.SetValue(x.convertSlice(x.Value)) |
| |
| destination := x.Destination |
| if destination == nil { |
| x.Target.SetDestination(nil) |
| |
| return x.Target.Apply(set) |
| } |
| |
| x.Target.SetDestination(x.convertSlice(*destination)) |
| |
| return applyFlagValueHook(set, x.Target.Apply, func() { |
| *destination = x.Target.GetDestination() |
| }) |
| } |
| |
| func (x *SliceFlag[T, S, E]) convertSlice(slice S) []E { |
| result := make([]E, len(slice)) |
| copy(result, slice) |
| return result |
| } |
| |
| func (x *SliceFlag[T, S, E]) SetValue(slice S) { |
| x.Value = slice |
| } |
| |
| func (x *SliceFlag[T, S, E]) SetDestination(slice S) { |
| if slice != nil { |
| x.Destination = &slice |
| } else { |
| x.Destination = nil |
| } |
| } |
| |
| func (x *SliceFlag[T, S, E]) GetDestination() S { |
| if destination := x.Destination; destination != nil { |
| return *destination |
| } |
| return nil |
| } |
| |
| func (x *SliceFlag[T, S, E]) String() string { return x.Target.String() } |
| func (x *SliceFlag[T, S, E]) Names() []string { return x.Target.Names() } |
| func (x *SliceFlag[T, S, E]) IsSet() bool { return x.Target.IsSet() } |
| func (x *SliceFlag[T, S, E]) IsRequired() bool { return x.Target.IsRequired() } |
| func (x *SliceFlag[T, S, E]) TakesValue() bool { return x.Target.TakesValue() } |
| func (x *SliceFlag[T, S, E]) GetUsage() string { return x.Target.GetUsage() } |
| func (x *SliceFlag[T, S, E]) GetValue() string { return x.Target.GetValue() } |
| func (x *SliceFlag[T, S, E]) GetDefaultText() string { return x.Target.GetDefaultText() } |
| func (x *SliceFlag[T, S, E]) GetEnvVars() []string { return x.Target.GetEnvVars() } |
| func (x *SliceFlag[T, S, E]) IsVisible() bool { return x.Target.IsVisible() } |
| func (x *SliceFlag[T, S, E]) GetCategory() string { return x.Target.GetCategory() } |
| |
| func (x *flagValueHook) Set(value string) error { |
| if err := x.value.Set(value); err != nil { |
| return err |
| } |
| x.hook() |
| return nil |
| } |
| |
| func (x *flagValueHook) String() string { |
| // note: this is necessary due to the way Go's flag package handles defaults |
| isZeroValue := func(f flag.Value, v string) bool { |
| /* |
| https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/flag/flag.go;drc=2580d0e08d5e9f979b943758d3c49877fb2324cb;l=453 |
| |
| Copyright (c) 2009 The Go Authors. All rights reserved. |
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the following conditions are |
| met: |
| * Redistributions of source code must retain the above copyright |
| notice, this list of conditions and the following disclaimer. |
| * Redistributions in binary form must reproduce the above |
| copyright notice, this list of conditions and the following disclaimer |
| in the documentation and/or other materials provided with the |
| distribution. |
| * Neither the name of Google Inc. nor the names of its |
| contributors may be used to endorse or promote products derived from |
| this software without specific prior written permission. |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| // Build a zero value of the flag's Value type, and see if the |
| // result of calling its String method equals the value passed in. |
| // This works unless the Value type is itself an interface type. |
| typ := reflect.TypeOf(f) |
| var z reflect.Value |
| if typ.Kind() == reflect.Pointer { |
| z = reflect.New(typ.Elem()) |
| } else { |
| z = reflect.Zero(typ) |
| } |
| return v == z.Interface().(flag.Value).String() |
| } |
| if x.value != nil { |
| // only return non-empty if not the same string as returned by the zero value |
| if s := x.value.String(); !isZeroValue(x.value, s) { |
| return s |
| } |
| } |
| return `` |
| } |
| |
| func (x *flagValueHook) Serialize() string { |
| if value, ok := x.value.(Serializer); ok { |
| return value.Serialize() |
| } |
| return x.String() |
| } |
| |
| // applyFlagValueHook wraps calls apply then wraps flags to call a hook function on update and after initial apply. |
| func applyFlagValueHook(set *flag.FlagSet, apply func(set *flag.FlagSet) error, hook func()) error { |
| if apply == nil || set == nil || hook == nil { |
| panic(`invalid input`) |
| } |
| var tmp flag.FlagSet |
| if err := apply(&tmp); err != nil { |
| return err |
| } |
| tmp.VisitAll(func(f *flag.Flag) { set.Var(&flagValueHook{value: f.Value, hook: hook}, f.Name, f.Usage) }) |
| hook() |
| return nil |
| } |
| |
| // newSliceFlagValue is for implementing SliceFlagTarget.SetValue and SliceFlagTarget.SetDestination. |
| // It's e.g. as part of StringSliceFlag.SetValue, using the factory NewStringSlice. |
| func newSliceFlagValue[R any, S ~[]E, E any](factory func(defaults ...E) *R, defaults S) *R { |
| if defaults == nil { |
| return nil |
| } |
| return factory(defaults...) |
| } |
| |
| // unwrapFlagValue strips any/all *flagValueHook wrappers. |
| func unwrapFlagValue(v flag.Value) flag.Value { |
| for { |
| h, ok := v.(*flagValueHook) |
| if !ok { |
| return v |
| } |
| v = h.value |
| } |
| } |
| |
| // NOTE: the methods below are in this file to make use of the build constraint |
| |
| func (f *Float64SliceFlag) SetValue(slice []float64) { |
| f.Value = newSliceFlagValue(NewFloat64Slice, slice) |
| } |
| |
| func (f *Float64SliceFlag) SetDestination(slice []float64) { |
| f.Destination = newSliceFlagValue(NewFloat64Slice, slice) |
| } |
| |
| func (f *Float64SliceFlag) GetDestination() []float64 { |
| if destination := f.Destination; destination != nil { |
| return destination.Value() |
| } |
| return nil |
| } |
| |
| func (f *Int64SliceFlag) SetValue(slice []int64) { |
| f.Value = newSliceFlagValue(NewInt64Slice, slice) |
| } |
| |
| func (f *Int64SliceFlag) SetDestination(slice []int64) { |
| f.Destination = newSliceFlagValue(NewInt64Slice, slice) |
| } |
| |
| func (f *Int64SliceFlag) GetDestination() []int64 { |
| if destination := f.Destination; destination != nil { |
| return destination.Value() |
| } |
| return nil |
| } |
| |
| func (f *IntSliceFlag) SetValue(slice []int) { |
| f.Value = newSliceFlagValue(NewIntSlice, slice) |
| } |
| |
| func (f *IntSliceFlag) SetDestination(slice []int) { |
| f.Destination = newSliceFlagValue(NewIntSlice, slice) |
| } |
| |
| func (f *IntSliceFlag) GetDestination() []int { |
| if destination := f.Destination; destination != nil { |
| return destination.Value() |
| } |
| return nil |
| } |
| |
| func (f *StringSliceFlag) SetValue(slice []string) { |
| f.Value = newSliceFlagValue(NewStringSlice, slice) |
| } |
| |
| func (f *StringSliceFlag) SetDestination(slice []string) { |
| f.Destination = newSliceFlagValue(NewStringSlice, slice) |
| } |
| |
| func (f *StringSliceFlag) GetDestination() []string { |
| if destination := f.Destination; destination != nil { |
| return destination.Value() |
| } |
| return nil |
| } |