1package cli
2
3import (
4	"flag"
5	"fmt"
6	"strconv"
7	"strings"
8)
9
10// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
11type Int64Slice []int64
12
13// Set parses the value into an integer and appends it to the list of values
14func (f *Int64Slice) Set(value string) error {
15	tmp, err := strconv.ParseInt(value, 10, 64)
16	if err != nil {
17		return err
18	}
19	*f = append(*f, tmp)
20	return nil
21}
22
23// String returns a readable representation of this value (for usage defaults)
24func (f *Int64Slice) String() string {
25	slice := make([]string, len(*f))
26	for i, v := range *f {
27		slice[i] = strconv.FormatInt(v, 10)
28	}
29
30	return strings.Join(slice, ",")
31}
32
33// Value returns the slice of ints set by this flag
34func (f *Int64Slice) Value() []int64 {
35	return *f
36}
37
38// Get returns the slice of ints set by this flag
39func (f *Int64Slice) Get() interface{} {
40	return *f
41}
42
43// Int64SliceFlag is a flag with type *Int64Slice
44type Int64SliceFlag struct {
45	Name     string
46	Usage    string
47	EnvVar   string
48	FilePath string
49	Required bool
50	Hidden   bool
51	Value    *Int64Slice
52}
53
54// String returns a readable representation of this value
55// (for usage defaults)
56func (f Int64SliceFlag) String() string {
57	return FlagStringer(f)
58}
59
60// GetName returns the name of the flag
61func (f Int64SliceFlag) GetName() string {
62	return f.Name
63}
64
65// IsRequired returns whether or not the flag is required
66func (f Int64SliceFlag) IsRequired() bool {
67	return f.Required
68}
69
70// TakesValue returns true of the flag takes a value, otherwise false
71func (f Int64SliceFlag) TakesValue() bool {
72	return true
73}
74
75// GetUsage returns the usage string for the flag
76func (f Int64SliceFlag) GetUsage() string {
77	return f.Usage
78}
79
80// GetValue returns the flags value as string representation and an empty
81// string if the flag takes no value at all.
82func (f Int64SliceFlag) GetValue() string {
83	if f.Value != nil {
84		return f.Value.String()
85	}
86	return ""
87}
88
89// Apply populates the flag given the flag set and environment
90// Ignores errors
91func (f Int64SliceFlag) Apply(set *flag.FlagSet) {
92	_ = f.ApplyWithError(set)
93}
94
95// ApplyWithError populates the flag given the flag set and environment
96func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error {
97	if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok {
98		newVal := &Int64Slice{}
99		for _, s := range strings.Split(envVal, ",") {
100			s = strings.TrimSpace(s)
101			if err := newVal.Set(s); err != nil {
102				return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err)
103			}
104		}
105		if f.Value == nil {
106			f.Value = newVal
107		} else {
108			*f.Value = *newVal
109		}
110	}
111
112	eachName(f.Name, func(name string) {
113		if f.Value == nil {
114			f.Value = &Int64Slice{}
115		}
116		set.Var(f.Value, name, f.Usage)
117	})
118
119	return nil
120}
121
122// Int64Slice looks up the value of a local Int64SliceFlag, returns
123// nil if not found
124func (c *Context) Int64Slice(name string) []int64 {
125	return lookupInt64Slice(name, c.flagSet)
126}
127
128// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
129// nil if not found
130func (c *Context) GlobalInt64Slice(name string) []int64 {
131	if fs := lookupGlobalFlagSet(name, c); fs != nil {
132		return lookupInt64Slice(name, fs)
133	}
134	return nil
135}
136
137func lookupInt64Slice(name string, set *flag.FlagSet) []int64 {
138	f := set.Lookup(name)
139	if f != nil {
140		value, ok := f.Value.(*Int64Slice)
141		if !ok {
142			return nil
143		}
144
145		// extract the slice from asserted value
146		parsed := value.Value()
147
148		// extract default value from the flag
149		var defaultVal []int64
150		for _, v := range strings.Split(f.DefValue, ",") {
151			if v != "" {
152				int64Value, err := strconv.ParseInt(v, 10, 64)
153				if err != nil {
154					panic(err)
155				}
156				defaultVal = append(defaultVal, int64Value)
157			}
158		}
159		// if the current value is not equal to the default value
160		// remove the default values from the flag
161		if !isInt64SliceEqual(parsed, defaultVal) {
162			for _, v := range defaultVal {
163				parsed = removeFromInt64Slice(parsed, v)
164			}
165		}
166		return parsed
167	}
168	return nil
169}
170
171func removeFromInt64Slice(slice []int64, val int64) []int64 {
172	for i, v := range slice {
173		if v == val {
174			ret := append([]int64{}, slice[:i]...)
175			ret = append(ret, slice[i+1:]...)
176			return ret
177		}
178	}
179	return slice
180}
181
182func isInt64SliceEqual(newValue, defaultValue []int64) bool {
183	// If one is nil, the other must also be nil.
184	if (newValue == nil) != (defaultValue == nil) {
185		return false
186	}
187
188	if len(newValue) != len(defaultValue) {
189		return false
190	}
191
192	for i, v := range newValue {
193		if v != defaultValue[i] {
194			return false
195		}
196	}
197
198	return true
199}
200