gob/shell/command/flag/flag.go

322 lines
7.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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