287 lines
9.5 KiB
Go
287 lines
9.5 KiB
Go
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
|
|
|
|
// 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
|
|
}
|