322 lines
7.6 KiB
Go
322 lines
7.6 KiB
Go
|
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 := "<number>", 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 := "<string>", 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 := "<int>", 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 := "<string>", 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...)
|
|||
|
}
|