2019-05-22 23:16:55 +02:00
|
|
|
package flagutil
|
|
|
|
|
2019-06-18 09:26:44 +02:00
|
|
|
import (
|
|
|
|
"flag"
|
2020-05-06 21:27:15 +02:00
|
|
|
"fmt"
|
|
|
|
"strconv"
|
2019-06-18 09:26:44 +02:00
|
|
|
"strings"
|
2020-12-15 11:51:12 +01:00
|
|
|
"time"
|
2019-06-18 09:26:44 +02:00
|
|
|
)
|
|
|
|
|
2022-10-01 17:26:05 +02:00
|
|
|
// NewArrayString returns new ArrayString with the given name and description.
|
|
|
|
func NewArrayString(name, description string) *ArrayString {
|
2022-10-01 17:39:34 +02:00
|
|
|
description += "\nSupports an `array` of values separated by comma or specified via multiple flags."
|
2022-10-01 17:26:05 +02:00
|
|
|
var a ArrayString
|
2019-06-18 09:26:44 +02:00
|
|
|
flag.Var(&a, name, description)
|
|
|
|
return &a
|
|
|
|
}
|
2019-05-22 23:16:55 +02:00
|
|
|
|
2023-08-12 13:17:55 +02:00
|
|
|
// NewArrayDuration returns new ArrayDuration with the given name, defaultValue and description.
|
|
|
|
func NewArrayDuration(name string, defaultValue time.Duration, description string) *ArrayDuration {
|
|
|
|
description += fmt.Sprintf(" (default %s)", defaultValue)
|
2022-10-01 17:39:34 +02:00
|
|
|
description += "\nSupports `array` of values separated by comma or specified via multiple flags."
|
2023-08-12 13:17:55 +02:00
|
|
|
a := &ArrayDuration{
|
|
|
|
defaultValue: defaultValue,
|
|
|
|
}
|
|
|
|
flag.Var(a, name, description)
|
|
|
|
return a
|
2020-12-15 11:51:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewArrayBool returns new ArrayBool with the given name and description.
|
|
|
|
func NewArrayBool(name, description string) *ArrayBool {
|
2022-10-01 17:39:34 +02:00
|
|
|
description += "\nSupports `array` of values separated by comma or specified via multiple flags."
|
2020-12-15 11:51:12 +01:00
|
|
|
var a ArrayBool
|
|
|
|
flag.Var(&a, name, description)
|
|
|
|
return &a
|
|
|
|
}
|
|
|
|
|
2023-08-12 13:17:55 +02:00
|
|
|
// NewArrayInt returns new ArrayInt with the given name, defaultValue and description.
|
|
|
|
func NewArrayInt(name string, defaultValue int, description string) *ArrayInt {
|
|
|
|
description += fmt.Sprintf(" (default %d)", defaultValue)
|
2022-10-01 17:39:34 +02:00
|
|
|
description += "\nSupports `array` of values separated by comma or specified via multiple flags."
|
2023-08-12 13:17:55 +02:00
|
|
|
a := &ArrayInt{
|
|
|
|
defaultValue: defaultValue,
|
|
|
|
}
|
|
|
|
flag.Var(a, name, description)
|
|
|
|
return a
|
2021-02-01 13:27:05 +01:00
|
|
|
}
|
|
|
|
|
2023-08-12 13:17:55 +02:00
|
|
|
// NewArrayBytes returns new ArrayBytes with the given name, defaultValue and description.
|
|
|
|
func NewArrayBytes(name string, defaultValue int64, description string) *ArrayBytes {
|
2022-12-15 02:52:32 +01:00
|
|
|
description += "\nSupports the following optional suffixes for size values: KB, MB, GB, TB, KiB, MiB, GiB, TiB."
|
2023-08-12 13:17:55 +02:00
|
|
|
description += fmt.Sprintf(" (default %d)", defaultValue)
|
2022-10-01 17:39:34 +02:00
|
|
|
description += "\nSupports `array` of values separated by comma or specified via multiple flags."
|
2023-08-12 13:17:55 +02:00
|
|
|
a := &ArrayBytes{
|
|
|
|
defaultValue: defaultValue,
|
|
|
|
}
|
|
|
|
flag.Var(a, name, description)
|
|
|
|
return a
|
2022-10-01 17:26:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// ArrayString is a flag that holds an array of strings.
|
2020-05-06 21:27:15 +02:00
|
|
|
//
|
|
|
|
// It may be set either by specifying multiple flags with the given name
|
|
|
|
// passed to NewArray or by joining flag values by comma.
|
|
|
|
//
|
|
|
|
// The following example sets equivalent flag array with two items (value1, value2):
|
|
|
|
//
|
2022-07-11 18:21:59 +02:00
|
|
|
// -foo=value1 -foo=value2
|
|
|
|
// -foo=value1,value2
|
2020-05-06 21:27:15 +02:00
|
|
|
//
|
2023-03-29 06:10:16 +02:00
|
|
|
// Each flag value may contain commas inside single quotes, double quotes, [], () or {} braces.
|
|
|
|
// For example, -foo=[a,b,c] defines a single command-line flag with `[a,b,c]` value.
|
2020-05-06 21:27:15 +02:00
|
|
|
//
|
2023-03-29 06:10:16 +02:00
|
|
|
// Flag values may be quoted. For instance, the following arg creates an array of ("a", "b,c") items:
|
|
|
|
//
|
|
|
|
// -foo='a,"b,c"'
|
2022-10-01 17:26:05 +02:00
|
|
|
type ArrayString []string
|
2019-05-22 23:16:55 +02:00
|
|
|
|
|
|
|
// String implements flag.Value interface
|
2022-10-01 17:26:05 +02:00
|
|
|
func (a *ArrayString) String() string {
|
2020-05-06 21:27:15 +02:00
|
|
|
aEscaped := make([]string, len(*a))
|
|
|
|
for i, v := range *a {
|
2023-03-29 06:10:16 +02:00
|
|
|
if strings.ContainsAny(v, `,'"{[(`+"\n") {
|
2020-05-06 21:27:15 +02:00
|
|
|
v = fmt.Sprintf("%q", v)
|
|
|
|
}
|
|
|
|
aEscaped[i] = v
|
|
|
|
}
|
|
|
|
return strings.Join(aEscaped, ",")
|
2019-05-22 23:16:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set implements flag.Value interface
|
2022-10-01 17:26:05 +02:00
|
|
|
func (a *ArrayString) Set(value string) error {
|
2020-05-06 21:27:15 +02:00
|
|
|
values := parseArrayValues(value)
|
2019-05-22 23:16:55 +02:00
|
|
|
*a = append(*a, values...)
|
|
|
|
return nil
|
|
|
|
}
|
2020-05-06 15:51:32 +02:00
|
|
|
|
2020-05-06 21:27:15 +02:00
|
|
|
func parseArrayValues(s string) []string {
|
|
|
|
if len(s) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var values []string
|
|
|
|
for {
|
|
|
|
v, tail := getNextArrayValue(s)
|
|
|
|
values = append(values, v)
|
|
|
|
if len(tail) == 0 {
|
|
|
|
return values
|
|
|
|
}
|
|
|
|
s = tail
|
2023-03-29 06:10:16 +02:00
|
|
|
if s[0] == ',' {
|
|
|
|
s = s[1:]
|
|
|
|
}
|
2020-05-06 21:27:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-29 06:10:16 +02:00
|
|
|
var closeQuotes = map[byte]byte{
|
|
|
|
'"': '"',
|
|
|
|
'\'': '\'',
|
|
|
|
'[': ']',
|
|
|
|
'{': '}',
|
|
|
|
'(': ')',
|
|
|
|
}
|
|
|
|
|
2020-05-06 21:27:15 +02:00
|
|
|
func getNextArrayValue(s string) (string, string) {
|
2023-03-29 06:10:16 +02:00
|
|
|
v, tail := getNextArrayValueMaybeQuoted(s)
|
|
|
|
if strings.HasPrefix(v, `"`) && strings.HasSuffix(v, `"`) {
|
|
|
|
vUnquoted, err := strconv.Unquote(v)
|
|
|
|
if err == nil {
|
|
|
|
return vUnquoted, tail
|
|
|
|
}
|
|
|
|
v = v[1 : len(v)-1]
|
|
|
|
v = strings.ReplaceAll(v, `\"`, `"`)
|
|
|
|
v = strings.ReplaceAll(v, `\\`, `\`)
|
|
|
|
return v, tail
|
2020-05-06 21:27:15 +02:00
|
|
|
}
|
2023-03-29 06:10:16 +02:00
|
|
|
if strings.HasPrefix(v, `'`) && strings.HasSuffix(v, `'`) {
|
|
|
|
v = v[1 : len(v)-1]
|
|
|
|
v = strings.ReplaceAll(v, `\'`, "'")
|
|
|
|
v = strings.ReplaceAll(v, `\\`, `\`)
|
|
|
|
return v, tail
|
|
|
|
}
|
|
|
|
return v, tail
|
|
|
|
}
|
|
|
|
|
|
|
|
func getNextArrayValueMaybeQuoted(s string) (string, string) {
|
|
|
|
idx := 0
|
|
|
|
for {
|
|
|
|
n := strings.IndexAny(s[idx:], `,"'[{(`)
|
2020-05-06 21:27:15 +02:00
|
|
|
if n < 0 {
|
|
|
|
// The last item
|
|
|
|
return s, ""
|
|
|
|
}
|
2023-03-29 06:10:16 +02:00
|
|
|
idx += n
|
|
|
|
ch := s[idx]
|
|
|
|
if ch == ',' {
|
|
|
|
// The next item
|
|
|
|
return s[:idx], s[idx:]
|
|
|
|
}
|
|
|
|
idx++
|
|
|
|
m := indexCloseQuote(s[idx:], closeQuotes[ch])
|
|
|
|
idx += m
|
2020-05-06 21:27:15 +02:00
|
|
|
}
|
2023-03-29 06:10:16 +02:00
|
|
|
}
|
2020-05-06 21:27:15 +02:00
|
|
|
|
2023-03-29 06:10:16 +02:00
|
|
|
func indexCloseQuote(s string, closeQuote byte) int {
|
|
|
|
if closeQuote == '"' || closeQuote == '\'' {
|
|
|
|
idx := 0
|
|
|
|
for {
|
|
|
|
n := strings.IndexByte(s[idx:], closeQuote)
|
|
|
|
if n < 0 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
idx += n
|
|
|
|
if n := getTrailingBackslashesCount(s[:idx]); n%2 == 1 {
|
|
|
|
// The quote is escaped with backslash. Skip it
|
|
|
|
idx++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return idx + 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
idx := 0
|
2020-05-06 21:27:15 +02:00
|
|
|
for {
|
2023-03-29 06:10:16 +02:00
|
|
|
n := strings.IndexAny(s[idx:], `"'[{()}]`)
|
2020-05-06 21:27:15 +02:00
|
|
|
if n < 0 {
|
2023-03-29 06:10:16 +02:00
|
|
|
return 0
|
2020-05-06 21:27:15 +02:00
|
|
|
}
|
2023-03-29 06:10:16 +02:00
|
|
|
idx += n
|
|
|
|
ch := s[idx]
|
|
|
|
if ch == closeQuote {
|
|
|
|
return idx + 1
|
2020-05-06 21:27:15 +02:00
|
|
|
}
|
2023-03-29 06:10:16 +02:00
|
|
|
idx++
|
|
|
|
m := indexCloseQuote(s[idx:], closeQuotes[ch])
|
|
|
|
if m == 0 {
|
|
|
|
return 0
|
2020-05-06 21:27:15 +02:00
|
|
|
}
|
2023-03-29 06:10:16 +02:00
|
|
|
idx += m
|
2020-05-06 21:27:15 +02:00
|
|
|
}
|
2023-03-29 06:10:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func getTrailingBackslashesCount(s string) int {
|
|
|
|
n := len(s)
|
|
|
|
for n > 0 && s[n-1] == '\\' {
|
|
|
|
n--
|
2020-05-06 21:27:15 +02:00
|
|
|
}
|
2023-03-29 06:10:16 +02:00
|
|
|
return len(s) - n
|
2020-05-06 21:27:15 +02:00
|
|
|
}
|
|
|
|
|
2020-05-06 15:51:32 +02:00
|
|
|
// GetOptionalArg returns optional arg under the given argIdx.
|
2022-10-01 17:26:05 +02:00
|
|
|
func (a *ArrayString) GetOptionalArg(argIdx int) string {
|
2020-05-06 15:51:32 +02:00
|
|
|
x := *a
|
|
|
|
if argIdx >= len(x) {
|
|
|
|
if len(x) == 1 {
|
|
|
|
return x[0]
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return x[argIdx]
|
|
|
|
}
|
2020-12-15 11:51:12 +01:00
|
|
|
|
|
|
|
// ArrayBool is a flag that holds an array of booleans values.
|
2022-10-01 17:26:05 +02:00
|
|
|
//
|
|
|
|
// Has the same api as ArrayString.
|
2020-12-15 11:51:12 +01:00
|
|
|
type ArrayBool []bool
|
|
|
|
|
|
|
|
// IsBoolFlag implements flag.IsBoolFlag interface
|
|
|
|
func (a *ArrayBool) IsBoolFlag() bool { return true }
|
|
|
|
|
|
|
|
// String implements flag.Value interface
|
|
|
|
func (a *ArrayBool) String() string {
|
2023-08-12 13:17:55 +02:00
|
|
|
formattedResults := make([]string, len(*a))
|
2020-12-15 11:51:12 +01:00
|
|
|
for i, v := range *a {
|
2023-08-12 13:17:55 +02:00
|
|
|
formattedResults[i] = strconv.FormatBool(v)
|
2020-12-15 11:51:12 +01:00
|
|
|
}
|
2023-08-12 13:17:55 +02:00
|
|
|
return strings.Join(formattedResults, ",")
|
2020-12-15 11:51:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set implements flag.Value interface
|
|
|
|
func (a *ArrayBool) Set(value string) error {
|
|
|
|
values := parseArrayValues(value)
|
|
|
|
for _, v := range values {
|
|
|
|
b, err := strconv.ParseBool(v)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
*a = append(*a, b)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetOptionalArg returns optional arg under the given argIdx.
|
|
|
|
func (a *ArrayBool) GetOptionalArg(argIdx int) bool {
|
|
|
|
x := *a
|
|
|
|
if argIdx >= len(x) {
|
|
|
|
if len(x) == 1 {
|
|
|
|
return x[0]
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return x[argIdx]
|
|
|
|
}
|
|
|
|
|
|
|
|
// ArrayDuration is a flag that holds an array of time.Duration values.
|
2022-10-01 17:26:05 +02:00
|
|
|
//
|
|
|
|
// Has the same api as ArrayString.
|
2023-08-12 13:17:55 +02:00
|
|
|
type ArrayDuration struct {
|
|
|
|
defaultValue time.Duration
|
|
|
|
a []time.Duration
|
|
|
|
}
|
2020-12-15 11:51:12 +01:00
|
|
|
|
|
|
|
// String implements flag.Value interface
|
|
|
|
func (a *ArrayDuration) String() string {
|
2023-08-12 13:17:55 +02:00
|
|
|
x := a.a
|
|
|
|
formattedResults := make([]string, len(x))
|
|
|
|
for i, v := range x {
|
|
|
|
formattedResults[i] = v.String()
|
2020-12-15 11:51:12 +01:00
|
|
|
}
|
2023-08-12 13:17:55 +02:00
|
|
|
return strings.Join(formattedResults, ",")
|
2020-12-15 11:51:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set implements flag.Value interface
|
|
|
|
func (a *ArrayDuration) Set(value string) error {
|
|
|
|
values := parseArrayValues(value)
|
|
|
|
for _, v := range values {
|
|
|
|
b, err := time.ParseDuration(v)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-08-12 13:17:55 +02:00
|
|
|
a.a = append(a.a, b)
|
2020-12-15 11:51:12 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-08-12 13:17:55 +02:00
|
|
|
// GetOptionalArg returns optional arg under the given argIdx, or default value, if argIdx not found.
|
|
|
|
func (a *ArrayDuration) GetOptionalArg(argIdx int) time.Duration {
|
|
|
|
x := a.a
|
2020-12-15 11:51:12 +01:00
|
|
|
if argIdx >= len(x) {
|
|
|
|
if len(x) == 1 {
|
|
|
|
return x[0]
|
|
|
|
}
|
2023-08-12 13:17:55 +02:00
|
|
|
return a.defaultValue
|
2020-12-15 11:51:12 +01:00
|
|
|
}
|
|
|
|
return x[argIdx]
|
|
|
|
}
|
2021-02-01 13:27:05 +01:00
|
|
|
|
|
|
|
// ArrayInt is flag that holds an array of ints.
|
2022-10-01 17:26:05 +02:00
|
|
|
//
|
|
|
|
// Has the same api as ArrayString.
|
2023-08-12 13:17:55 +02:00
|
|
|
type ArrayInt struct {
|
|
|
|
defaultValue int
|
|
|
|
a []int
|
|
|
|
}
|
2021-02-01 13:27:05 +01:00
|
|
|
|
|
|
|
// String implements flag.Value interface
|
|
|
|
func (a *ArrayInt) String() string {
|
2023-08-12 13:17:55 +02:00
|
|
|
x := a.a
|
2021-02-01 13:27:05 +01:00
|
|
|
formattedInts := make([]string, len(x))
|
|
|
|
for i, v := range x {
|
|
|
|
formattedInts[i] = strconv.Itoa(v)
|
|
|
|
}
|
|
|
|
return strings.Join(formattedInts, ",")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set implements flag.Value interface
|
|
|
|
func (a *ArrayInt) Set(value string) error {
|
|
|
|
values := parseArrayValues(value)
|
|
|
|
for _, v := range values {
|
|
|
|
n, err := strconv.Atoi(v)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-08-12 13:17:55 +02:00
|
|
|
a.a = append(a.a, n)
|
2021-02-01 13:27:05 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-08-12 13:17:55 +02:00
|
|
|
// GetOptionalArg returns optional arg under the given argIdx or default value.
|
|
|
|
func (a *ArrayInt) GetOptionalArg(argIdx int) int {
|
|
|
|
x := a.a
|
2021-02-01 13:27:05 +01:00
|
|
|
if argIdx < len(x) {
|
|
|
|
return x[argIdx]
|
|
|
|
}
|
|
|
|
if len(x) == 1 {
|
|
|
|
return x[0]
|
|
|
|
}
|
2023-08-12 13:17:55 +02:00
|
|
|
return a.defaultValue
|
2021-02-01 13:27:05 +01:00
|
|
|
}
|
2022-10-01 17:26:05 +02:00
|
|
|
|
|
|
|
// ArrayBytes is flag that holds an array of Bytes.
|
|
|
|
//
|
|
|
|
// Has the same api as ArrayString.
|
2023-08-12 13:17:55 +02:00
|
|
|
type ArrayBytes struct {
|
|
|
|
defaultValue int64
|
|
|
|
a []*Bytes
|
|
|
|
}
|
2022-10-01 17:26:05 +02:00
|
|
|
|
|
|
|
// String implements flag.Value interface
|
|
|
|
func (a *ArrayBytes) String() string {
|
2023-08-12 13:17:55 +02:00
|
|
|
x := a.a
|
2022-10-01 17:26:05 +02:00
|
|
|
formattedBytes := make([]string, len(x))
|
|
|
|
for i, v := range x {
|
|
|
|
formattedBytes[i] = v.String()
|
|
|
|
}
|
|
|
|
return strings.Join(formattedBytes, ",")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set implemented flag.Value interface
|
|
|
|
func (a *ArrayBytes) Set(value string) error {
|
|
|
|
values := parseArrayValues(value)
|
|
|
|
for _, v := range values {
|
|
|
|
var b Bytes
|
|
|
|
if err := b.Set(v); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-08-12 13:17:55 +02:00
|
|
|
a.a = append(a.a, &b)
|
2022-10-01 17:26:05 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-08-12 13:17:55 +02:00
|
|
|
// GetOptionalArg returns optional arg under the given argIdx, or default value
|
|
|
|
func (a *ArrayBytes) GetOptionalArg(argIdx int) int64 {
|
|
|
|
x := a.a
|
2022-10-01 17:26:05 +02:00
|
|
|
if argIdx < len(x) {
|
|
|
|
return x[argIdx].N
|
|
|
|
}
|
|
|
|
if len(x) == 1 {
|
|
|
|
return x[0].N
|
|
|
|
}
|
2023-08-12 13:17:55 +02:00
|
|
|
return a.defaultValue
|
2022-10-01 17:26:05 +02:00
|
|
|
}
|