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...)
|
||
}
|