package flag import ( "fmt" "strings" "gitea.zaclys.com/bvaudour/gob/convert" . "gitea.zaclys.com/bvaudour/gob/option" "gitea.zaclys.com/bvaudour/gob/shell/command" ) // ConversionFunc est une fonction de conversion. // // Les éléments retournés sont : // - next : liste des éléments restants à parser // - value : la valeur parsée // - parsed : le nombre d’éléments parsés type ConversionFunc[T any] func([]string, *flag[T]) (Result[[]string], T, int) type callbackFunc[T any] func([]string, string) (Result[[]string], T, int) // Flag représente un argument nommé de commande. type Flag interface { command.Flag capacity() int nbParsed() int def() any } type flag[T any] struct { name string flags []string fval string usage string multiple int value *T defaultval T parsed int conv ConversionFunc[T] } // Parse parse l’argument à partir de l’entrée fournie // et retourne les éléments restant à parser. // Si le parsage échoue, une erreur est retournée. func (f *flag[T]) Parse(args []string) (next Result[[]string]) { var value T var parsed int if next, value, parsed = f.conv(args, f); next.IsOk() { f.value = &value f.parsed += parsed } return } // IsParsed retourne vrai si l’argument a déjà été parsé. func (f *flag[T]) IsParsed() bool { return f.parsed > 0 } // Reset réinitialise le parsage. func (f *flag[T]) Reset() { f.parsed = 0 f.value = &f.defaultval } // Name retourne le nom principal de l’argument nommé. func (f *flag[T]) Name() string { return f.name } // Usage retourne l’aide sur l’usage du flag func (f *flag[T]) Usage() string { return f.usage } // ArgName retorune le nom donné à l’argument suivant le flag. func (f *flag[T]) ArgName() string { s := f.fval switch f.multiple { case -1: s += " (...)" case 0: s += " (optional)" case 1: default: s = fmt.Sprintf("%s (%d)", s, f.multiple) } return strings.TrimSpace(s) } // Flags retourne tous les flags disponibles identifiant le même flag. func (f *flag[T]) Flags() []string { return f.flags } // Value retourne la valeur du flag. func (f *flag[T]) Value() any { return *f.value } func (f *flag[T]) capacity() int { return f.multiple } func (f *flag[T]) nbParsed() int { return f.parsed } func (f *flag[T]) def() any { return f.defaultval } func optional(fac bool) int { if fac { return 0 } return 1 } func nbArgs(n int) int { if n < 1 { return -1 } return n } func checkArgLen(args []string, name string) (err Option[error]) { if len(args) == 0 { err = Some(fmt.Errorf("argument needed for flag %s", name)) } return } func checkCapacity(l, nb int, name string) (err Option[error]) { if nb > 0 && l >= nb { err = Some(fmt.Errorf("overflow value for flag %s", name)) } return } func convertArg[T any](args []string, name string, v *T) (next Result[[]string], parsed int) { if convert.Convert(args[0], v) { next, parsed = Ok(args[1:]), 1 } else { next = Err[[]string](fmt.Errorf("%s: invalid value for flag %s", args[0], name)) } return } func genericArg[T any](args []string, name string) (next Result[[]string], value T, parsed int) { chck := checkArgLen(args, name) if err, ok := chck.Get(); ok { next = Err[[]string](err) } else { next, parsed = convertArg(args, name, &value) } return } func stringArg(args []string, name string) (next Result[[]string], value string, parsed int) { chck := checkArgLen(args, name) if err, ok := chck.Get(); ok { next = Err[[]string](err) } else { next, value, parsed = Ok(args[1:]), args[0], 1 } return } func boolArg(args []string, name string) (next Result[[]string], val bool, parsed int) { chck := checkArgLen(args, name) if err, ok := chck.Get(); ok { next = Err[[]string](err) } else { next, val, parsed = Ok(args), true, 1 } return } func singleParse[T any](cb callbackFunc[T]) ConversionFunc[T] { return func(args []string, f *flag[T]) (next Result[[]string], value T, parsed int) { return cb(args, f.name) } } func multipleParse[T any](cb callbackFunc[T]) ConversionFunc[[]T] { return func(args []string, f *flag[[]T]) (next Result[[]string], value []T, parsed int) { l, n := len(*f.value), f.multiple chck := checkCapacity(l, n, f.name) if err, ok := chck.Get(); ok { next = Err[[]string](err) return } result := make([]T, l+1) next, result[l], parsed = cb(args, f.name) args, ok := next.Ok() if !ok { return } copy(result[:l], *f.value) if n > 0 { n-- } for len(args) > 0 && n != 0 { var v T var p int next, v, p = cb(args, f.name) if args, ok = next.Ok(); !ok { return } result = append(result, v) parsed += p if n > 0 { n-- } } value = result return } } // New retourne un nouvel argument nommé avec : // - son nom, // - son usage, // - le nom donné à l’argument qui suit // - un pointeur vers sa valeur // - sa valeur par défaut si non parsé // - son nombre d’arguments (indéfini si -1, optionnel si 0) // - sa fonction de conversion // - des alias supplémentaires func New[T any]( name, usage, argname string, value *T, defaultvalue T, nb int, conv ConversionFunc[T], others ...string, ) Flag { t := convert.TypeOf(value).Elem() if nb < 0 || nb > 1 && !t.Is(convert.Slice) { panic("value must be a pointer of slice") } f := &flag[T]{ name: name, flags: append([]string{name}, others...), fval: argname, usage: usage, multiple: nb, value: value, defaultval: defaultvalue, conv: conv, } f.Reset() return f } // Number retourne un argument nommé de type nombre. func Number[T convert.NumberType](name, usage string, value *T, defaultvalue T, fac bool, others ...string) Flag { argname, nb := "", optional(fac) conv := singleParse(genericArg[T]) return New(name, usage, argname, value, defaultvalue, nb, conv, others...) } // String retourne un argument nommé de type chaîne de caractères. func String(name, usage string, value *string, defaultvalue string, fac bool, others ...string) Flag { argname, nb := "", optional(fac) conv := singleParse(stringArg) return New(name, usage, argname, value, defaultvalue, nb, conv, others...) } // Bool retourne un argument nommé sans argument supplémentaire. func Bool(name, usage string, value *bool, defaultvalue bool, fac bool, others ...string) Flag { argname, nb := "", optional(fac) conv := singleParse(boolArg) return New(name, usage, argname, value, defaultvalue, nb, conv, others...) } // NumberSlice retourne un argument nommé de type tableau de nombres. func NumberSlice[T convert.NumberType](name, usage string, nb int, value *[]T, defaultvalue []T, others ...string) Flag { argname, nb := "", nbArgs(nb) conv := multipleParse(genericArg[T]) return New(name, usage, argname, value, defaultvalue, nb, conv, others...) } // StringSlice retourne un argument nommé de type tableau de strings. func StringSlice(name, usage string, nb int, value *[]string, defaultvalue []string, others ...string) Flag { argname, nb := "", nbArgs(nb) conv := multipleParse(stringArg) return New(name, usage, argname, value, defaultvalue, nb, conv, others...) } // BoolSlice retourne un argument nommé de type tableau de booléens func BoolSlice(name, usage string, nb int, value *[]bool, defaultvalue []bool, others ...string) Flag { argname, nb := "", nbArgs(nb) conv := func(args []string, f *flag[[]bool]) (next Result[[]string], val []bool, parsed int) { l := len(*f.value) val = make([]bool, l+1) copy(val[:l], *f.value) next, val[l], parsed = Ok(args), true, 1 return } return New(name, usage, argname, value, defaultvalue, nb, conv, others...) }