gob/shell/command/flag/flag.go

322 lines
7.6 KiB
Go
Raw Permalink Normal View History

2023-10-05 08:42:40 +00:00
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 largument à partir de lentré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 largument 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 largument nommé.
func (f *flag[T]) Name() string {
return f.name
}
// Usage retourne laide sur lusage du flag
func (f *flag[T]) Usage() string {
return f.usage
}
// ArgName retorune le nom donné à largument 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é à largument qui suit
// - un pointeur vers sa valeur
// - sa valeur par défaut si non parsé
// - son nombre darguments (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...)
}