Module shell (2)
This commit is contained in:
parent
898822d78f
commit
8e5ad41b11
|
@ -0,0 +1,127 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gitea.zaclys.com/bvaudour/gob/format"
|
||||
. "gitea.zaclys.com/bvaudour/gob/option"
|
||||
)
|
||||
|
||||
// Parser est une interface pour parser des arguments en ligne de commande.
|
||||
type Parser interface {
|
||||
Parse([]string) Result[[]string]
|
||||
IsParsed() bool
|
||||
Reset()
|
||||
}
|
||||
|
||||
// Helper est une interface permettant d’afficher l’aide d’une ligne de commande.
|
||||
type Helper interface {
|
||||
Name() string
|
||||
Usage() string
|
||||
}
|
||||
|
||||
// Command représente une commande à saisir.
|
||||
type Command interface {
|
||||
Parser
|
||||
Helper
|
||||
Help() string
|
||||
Flags() FlagSet
|
||||
Run()
|
||||
}
|
||||
|
||||
// CommandSet est une commande regroupant plusieurs sous-commandes.
|
||||
type CommandSet interface {
|
||||
Parser
|
||||
Helper
|
||||
Help() string
|
||||
Commands() []Command
|
||||
}
|
||||
|
||||
// CommandHelp retourne l’aide sur une commande
|
||||
func CommandHelp(c Command) string {
|
||||
h := c.Help()
|
||||
if h != "" {
|
||||
return h
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString(c.Usage())
|
||||
sb.WriteByte('\n')
|
||||
sb.WriteString(format.Apply("Usage:", "bold", "underline"))
|
||||
sb.WriteByte(' ')
|
||||
sb.WriteString(format.Apply(c.Name(), "l_yellow"))
|
||||
sb.WriteString(format.Apply(" <options> <args>", "yellow"))
|
||||
sb.WriteByte('\n')
|
||||
if fs := c.Flags(); fs != nil {
|
||||
sb.WriteByte('\n')
|
||||
sb.WriteString(fs.Help())
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// CommandSetHelp retourne l’aide sur un ensemble de commandes
|
||||
func CommandSetHelp(cs CommandSet) string {
|
||||
h := cs.Help()
|
||||
if h != "" {
|
||||
return h
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString(cs.Usage())
|
||||
sb.WriteByte('\n')
|
||||
sb.WriteString(format.Apply("Usage:", "bold", "underline"))
|
||||
sb.WriteByte(' ')
|
||||
sb.WriteString(format.Apply(cs.Name(), "l_yellow"))
|
||||
sb.WriteString(format.Apply(" <cmd> <args>", "yellow"))
|
||||
sb.WriteString("\n\n")
|
||||
sb.WriteString(format.Apply("Available commands:", "bold", "l_red"))
|
||||
sb.WriteByte('\n')
|
||||
|
||||
var (
|
||||
cmds = cs.Commands()
|
||||
lines [][]string
|
||||
n int
|
||||
hasHelp bool
|
||||
)
|
||||
|
||||
for _, c := range cmds {
|
||||
hasHelp = hasHelp || c.Name() == "help"
|
||||
cmd, usage := c.Name(), strings.Split(c.Usage(), "\n")
|
||||
if len(cmd) > n {
|
||||
n = len(cmd)
|
||||
}
|
||||
for i, l := range usage {
|
||||
columns := make([]string, 2)
|
||||
if i == 0 {
|
||||
columns[0] = cmd
|
||||
}
|
||||
columns[1] = l
|
||||
lines = append(lines, columns)
|
||||
}
|
||||
}
|
||||
|
||||
if !hasHelp {
|
||||
cmd, usage := "help", "Print this help"
|
||||
if len(cmd) > n {
|
||||
n = len(cmd)
|
||||
}
|
||||
lines = append(lines, []string{cmd, usage})
|
||||
}
|
||||
|
||||
f, c := format.NewAlignment(format.LEFT, n), format.ColorOf("l_yellow")
|
||||
for _, columns := range lines {
|
||||
cmd, usage := columns[0], columns[1]
|
||||
sb.WriteString(format.Tab(2))
|
||||
if cmd == "" {
|
||||
sb.WriteString(f.Format(cmd))
|
||||
} else {
|
||||
sb.WriteString(c.Format(f.Format(cmd)))
|
||||
}
|
||||
sb.WriteString(format.Tab(1))
|
||||
sb.WriteString(usage)
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
. "gitea.zaclys.com/bvaudour/gob/option"
|
||||
)
|
||||
|
||||
// Arg représente un argument de commande
|
||||
type Arg struct {
|
||||
name string
|
||||
usage string
|
||||
value *[]string
|
||||
nb int
|
||||
}
|
||||
|
||||
// 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 (a *Arg) Parse(args []string) (next Result[[]string]) {
|
||||
l := len(args)
|
||||
switch a.nb {
|
||||
case -1:
|
||||
*a.value = make([]string, l)
|
||||
copy(*a.value, args)
|
||||
case 0:
|
||||
if l > 0 {
|
||||
next, *a.value = Ok(args[1:]), []string{args[0]}
|
||||
}
|
||||
default:
|
||||
if l < a.nb {
|
||||
next = Err[[]string](fmt.Errorf("Not enough args for %s", a.name))
|
||||
} else {
|
||||
*a.value = make([]string, a.nb)
|
||||
copy(*a.value, args[:a.nb])
|
||||
next = Ok(args[a.nb:])
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsParsed retourne vrai si l’argument a déjà été parsé.
|
||||
func (a *Arg) IsParsed() bool {
|
||||
return len(*a.value) > 0
|
||||
}
|
||||
|
||||
// Reset réinitialise le parsage.
|
||||
func (a *Arg) Reset() {
|
||||
*a.value = []string{}
|
||||
}
|
||||
|
||||
// Name retourne le nom de l’argument.
|
||||
func (a *Arg) Name() string {
|
||||
return a.name
|
||||
}
|
||||
|
||||
// Usage retourne l’aide sur l’utilisation de l’argument.
|
||||
func (a *Arg) Usage() string {
|
||||
return a.usage
|
||||
}
|
||||
|
||||
// NewArg retourne un nouvel argument avec son nom, sa description
|
||||
// le nombre d’éléments attendu et un pointeur vers la valeur parsée.
|
||||
//
|
||||
// Si nbargs < 0, l’argument peut recevoir un nombre d’éléments indéterminé.
|
||||
// Si nbargs = 0, l’argument est facultatif.
|
||||
func NewArg(name, usage string, nbargs int, value *[]string) *Arg {
|
||||
if nbargs < 0 {
|
||||
nbargs = -1
|
||||
}
|
||||
|
||||
return &Arg{
|
||||
name: name,
|
||||
usage: usage,
|
||||
value: value,
|
||||
nb: nbargs,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,321 @@
|
|||
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...)
|
||||
}
|
|
@ -0,0 +1,432 @@
|
|||
package flag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gitea.zaclys.com/bvaudour/gob/collection"
|
||||
"gitea.zaclys.com/bvaudour/gob/format"
|
||||
. "gitea.zaclys.com/bvaudour/gob/option"
|
||||
"gitea.zaclys.com/bvaudour/gob/shell/command"
|
||||
)
|
||||
|
||||
func formatLines(sb *strings.Builder, lines [][]string) {
|
||||
n := 0
|
||||
for _, columns := range lines {
|
||||
l := len([]rune(columns[0]))
|
||||
if l > n {
|
||||
n = l
|
||||
}
|
||||
}
|
||||
|
||||
f, c := format.NewAlignment(format.LEFT, n), format.ColorOf("l_yellow")
|
||||
for _, columns := range lines {
|
||||
left, right := columns[0], columns[1]
|
||||
sb.WriteString(format.Tab(2))
|
||||
if left == "" {
|
||||
sb.WriteString(f.Format(left))
|
||||
} else {
|
||||
sb.WriteString(c.Format(f.Format(left)))
|
||||
}
|
||||
sb.WriteString(format.Tab(1))
|
||||
sb.WriteString(right)
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
|
||||
// FlagSet et un ensemble de flags
|
||||
type FlagSet struct {
|
||||
flags []Flag
|
||||
fmap map[string]Flag
|
||||
args []*Arg
|
||||
unix bool
|
||||
parsed bool
|
||||
helpNeeded bool
|
||||
}
|
||||
|
||||
func (fs *FlagSet) checkFlagsCapacity(args []string) Result[[]string] {
|
||||
for _, flg := range fs.flags {
|
||||
c, p := flg.capacity(), flg.nbParsed()
|
||||
if c > 0 && p != c {
|
||||
return Err[[]string](fmt.Errorf("Mismatch number of args for flag %s: required (%d), has (%d)", flg.Name(), c, p))
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(args)
|
||||
}
|
||||
|
||||
func (fs *FlagSet) checkArgsCapacity(args []string) Result[[]string] {
|
||||
for _, arg := range fs.args {
|
||||
if arg.nb > 0 && len(*arg.value) < arg.nb {
|
||||
return Err[[]string](fmt.Errorf("Not enough args for %s", arg.name))
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(args)
|
||||
}
|
||||
|
||||
func (fs *FlagSet) parseUnix(args []string) (next Result[[]string]) {
|
||||
var (
|
||||
notFlags, unconsumedArgs []string
|
||||
f string
|
||||
flg Flag
|
||||
ok bool
|
||||
)
|
||||
|
||||
// Décomposition des arguments raccourcis.
|
||||
args = formatArgs(args)
|
||||
|
||||
// On sépare ce qui fait parti des options nommées des options non nommées.
|
||||
for i, e := range args {
|
||||
if e == "--" {
|
||||
args, notFlags = args[:i], args[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
flags := collection.NewSet[string]()
|
||||
for f := range fs.fmap {
|
||||
flags.Add(f)
|
||||
}
|
||||
|
||||
// On vérifie que l’aide est demandée.
|
||||
for _, e := range args {
|
||||
switch e {
|
||||
case "-h":
|
||||
if !flags.Contains("-h") {
|
||||
fs.parsed, fs.helpNeeded = true, true
|
||||
return Ok(args[:0])
|
||||
}
|
||||
case "--help":
|
||||
if !flags.Contains("--help") {
|
||||
fs.parsed, fs.helpNeeded = true, true
|
||||
return Ok(args[:0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parsage des flags
|
||||
for len(args) > 0 {
|
||||
f = args[0]
|
||||
if flg, ok = fs.fmap[f]; !ok {
|
||||
if isUnixFlag(f) {
|
||||
return Err[[]string](fmt.Errorf("Invalid flag: %s", f))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
nextFlagIdx := len(args)
|
||||
for i, e := range args {
|
||||
if flags.Contains(e) {
|
||||
nextFlagIdx = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
next = flg.Parse(args[:nextFlagIdx])
|
||||
if unconsumedArgs, ok = next.Ok(); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
args = append(unconsumedArgs, args[nextFlagIdx:]...)
|
||||
}
|
||||
|
||||
if containsUnixFlag(args) {
|
||||
return Err[[]string](fmt.Errorf("Not a flag: %s", args[0]))
|
||||
}
|
||||
return Ok(append(args, notFlags...))
|
||||
}
|
||||
|
||||
func (fs *FlagSet) parseNotUnix(args []string) (next Result[[]string]) {
|
||||
var (
|
||||
unconsumedArgs []string
|
||||
flg Flag
|
||||
ok bool
|
||||
)
|
||||
|
||||
flags := collection.NewSet[string]()
|
||||
for f := range fs.fmap {
|
||||
flags.Add(f)
|
||||
}
|
||||
|
||||
// Parsage des flags
|
||||
for len(args) > 0 {
|
||||
f := args[0]
|
||||
if flg, ok = fs.fmap[f]; !ok {
|
||||
break
|
||||
}
|
||||
|
||||
nextFlagIdx := len(args)
|
||||
for i, e := range args {
|
||||
if flags.Contains(e) {
|
||||
nextFlagIdx = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
next = flg.Parse(args[:nextFlagIdx])
|
||||
if unconsumedArgs, ok = next.Ok(); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
args = append(unconsumedArgs, args[nextFlagIdx:]...)
|
||||
}
|
||||
|
||||
// On vérifie que les arguments restants ne sont pas des flags.
|
||||
if flags.ContainsOneOf(args...) {
|
||||
return Err[[]string](fmt.Errorf("Not a flag: %s", args[0]))
|
||||
}
|
||||
|
||||
return Ok(args)
|
||||
}
|
||||
|
||||
func (fs *FlagSet) parseArgs(args []string) (next Result[[]string]) {
|
||||
var (
|
||||
i int
|
||||
ok bool
|
||||
)
|
||||
|
||||
for len(args) > 0 && i < len(fs.args) {
|
||||
next = fs.args[i].Parse(args)
|
||||
if args, ok = next.Ok(); !ok {
|
||||
return
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
// S’il reste des arguments à parser, on lève une erreur
|
||||
if len(args) > 0 {
|
||||
return Err[[]string](fmt.Errorf("Too much args given"))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Parse parse les arguments pour alimenter le flagset.
|
||||
// Il retourne la liste des arguments restant à parser et une erreur éventuelle
|
||||
// si le parsage a échoué.
|
||||
func (fs *FlagSet) Parse(args []string) (next Result[[]string]) {
|
||||
var ok bool
|
||||
|
||||
// Parsage des flags
|
||||
if fs.unix {
|
||||
next = fs.parseUnix(args)
|
||||
} else {
|
||||
next = fs.parseNotUnix(args)
|
||||
}
|
||||
if args, ok = next.Ok(); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// On vérifie que les flags ont la bonne capacité.
|
||||
next = fs.checkFlagsCapacity(args)
|
||||
if args, ok = next.Ok(); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Parsage des arguments
|
||||
next = fs.parseArgs(args)
|
||||
if args, ok = next.Ok(); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// On vérifie que les arguments ont la bonne capacité.
|
||||
next = fs.checkArgsCapacity(args)
|
||||
|
||||
// Si tout est bon, on marque le set comme parsé.
|
||||
if args, ok = next.Ok(); ok {
|
||||
fs.parsed = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// IsParsed retourne vrai si le flagset a été parsé.
|
||||
func (fs *FlagSet) IsParsed() bool {
|
||||
return fs.parsed
|
||||
}
|
||||
|
||||
// Reset réinitialise l’état du flagset, comme s’il n’avait
|
||||
// jamais été parsé.
|
||||
func (fs *FlagSet) Reset() {
|
||||
fs.helpNeeded = false
|
||||
fs.parsed = false
|
||||
|
||||
for _, f := range fs.flags {
|
||||
f.Reset()
|
||||
}
|
||||
|
||||
for _, a := range fs.args {
|
||||
a.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// HelpRequired retourne vrai si le parsage contient un flag de type aide.
|
||||
func (fs *FlagSet) HelpRequired() bool {
|
||||
return fs.helpNeeded
|
||||
}
|
||||
|
||||
// Flags retourne la liste des flags disponibles dans le flagset.
|
||||
func (fs *FlagSet) Flags() []command.Flag {
|
||||
out := make([]command.Flag, len(fs.flags))
|
||||
for i, f := range fs.flags {
|
||||
out[i] = f
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// Args retourne la valeur des arguments non nommés.
|
||||
func (fs *FlagSet) Args() []string {
|
||||
var out []string
|
||||
for _, a := range fs.args {
|
||||
out = append(out, *a.value...)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (fs *FlagSet) helpUnix(sb *strings.Builder) {
|
||||
var (
|
||||
lines [][]string
|
||||
hl, hs bool
|
||||
)
|
||||
|
||||
for _, f := range fs.flags {
|
||||
var shorts, longs []string
|
||||
for _, f := range f.Flags() {
|
||||
if isShort(f) {
|
||||
hs = hs || f != "-h"
|
||||
shorts = append(shorts, f)
|
||||
} else {
|
||||
hl = hl || f != "--help"
|
||||
longs = append(longs, f)
|
||||
}
|
||||
}
|
||||
var (
|
||||
flags = strings.Join(append(shorts, longs...), ",")
|
||||
name, def = f.ArgName(), f.def()
|
||||
arg string
|
||||
)
|
||||
if name == "" {
|
||||
arg = fmt.Sprintf("Arg: (%s) [default: %v]", name, def)
|
||||
} else {
|
||||
arg = fmt.Sprintf("[default: %v]", def)
|
||||
}
|
||||
lines = append(lines, []string{flags, format.Apply(arg, "italic")})
|
||||
for _, u := range strings.Split(f.Usage(), "\n") {
|
||||
lines = append(lines, []string{"", u})
|
||||
}
|
||||
}
|
||||
|
||||
if hs || hl {
|
||||
lines = append(lines, []string{"-h,--help", "Print this help"})
|
||||
}
|
||||
|
||||
formatLines(sb, lines)
|
||||
}
|
||||
|
||||
func (fs *FlagSet) helpFlags(sb *strings.Builder) {
|
||||
var lines [][]string
|
||||
for _, f := range fs.flags {
|
||||
name := fmt.Sprintf("%s %s", f.Name(), f.ArgName())
|
||||
def := fmt.Sprintf("default: %v", f.def())
|
||||
lines = append(lines, []string{name, format.Apply(def, "italic")})
|
||||
for _, u := range strings.Split(f.Usage(), "\n") {
|
||||
lines = append(lines, []string{"", u})
|
||||
}
|
||||
aliases := f.Flags()
|
||||
if l := len(aliases); l > 1 {
|
||||
lines = append(lines, []string{"", format.Apply(fmt.Sprintf("other flags: %s", strings.Join(aliases[:l-1], ",")), "italic", "yellow")})
|
||||
}
|
||||
}
|
||||
|
||||
formatLines(sb, lines)
|
||||
}
|
||||
|
||||
func (fs *FlagSet) helpArgs(sb *strings.Builder) {
|
||||
var lines [][]string
|
||||
for _, arg := range fs.args {
|
||||
name, usage := arg.name, strings.Split(arg.usage, "\n")
|
||||
for i, line := range usage {
|
||||
columns := make([]string, 2)
|
||||
if i == 0 {
|
||||
columns[0] = name
|
||||
}
|
||||
columns[1] = line
|
||||
lines = append(lines, columns)
|
||||
}
|
||||
}
|
||||
|
||||
formatLines(sb, lines)
|
||||
}
|
||||
|
||||
// Help retourne l’aide complète sur le flagset.
|
||||
func (fs *FlagSet) Help() string {
|
||||
var sb strings.Builder
|
||||
|
||||
if len(fs.flags) > 0 {
|
||||
sb.WriteString(format.Apply("Options:\n", "bold", "l_red"))
|
||||
if fs.unix {
|
||||
(fs.helpUnix(&sb))
|
||||
} else {
|
||||
fs.helpFlags(&sb)
|
||||
}
|
||||
}
|
||||
|
||||
if len(fs.args) > 0 {
|
||||
if len(fs.flags) > 0 {
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
sb.WriteString(format.Apply("Arguments:\n", "bold", "l_red"))
|
||||
fs.helpArgs(&sb)
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// NewSet retourne un nouveau set de flags.
|
||||
// Si unix, les arguments nommés doivent être de type
|
||||
// unix : -n pour les noms courts et --name pour les noms longs.
|
||||
func NewSet(unix bool) *FlagSet {
|
||||
return &FlagSet{
|
||||
unix: unix,
|
||||
fmap: make(map[string]Flag),
|
||||
}
|
||||
}
|
||||
|
||||
// Add ajoute des flags au flagset.
|
||||
func (fs *FlagSet) Add(flags ...Flag) *FlagSet {
|
||||
for _, f := range flags {
|
||||
for _, e := range f.Flags() {
|
||||
if fs.unix && !isUnixFlag(e) {
|
||||
panic(fmt.Sprintf("Flag %s is not an unix flag", e))
|
||||
}
|
||||
if _, exists := fs.fmap[e]; exists {
|
||||
panic(fmt.Sprintf("Flag %s is already defined", e))
|
||||
}
|
||||
fs.fmap[e] = f
|
||||
}
|
||||
fs.flags = append(fs.flags, f)
|
||||
}
|
||||
|
||||
return fs
|
||||
}
|
||||
|
||||
// AddArgs ajoute des arguments au flagset.
|
||||
func (fs *FlagSet) AddArgs(args ...*Arg) *FlagSet {
|
||||
var lastArg *Arg
|
||||
if l := len(fs.args); l > 0 {
|
||||
lastArg = fs.args[l-1]
|
||||
}
|
||||
|
||||
for _, a := range args {
|
||||
if lastArg != nil && lastArg.nb < 1 {
|
||||
panic("facultative argument must be unique and last located")
|
||||
}
|
||||
lastArg = a
|
||||
}
|
||||
fs.args = append(fs.args, args...)
|
||||
|
||||
return fs
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package flag
|
||||
|
||||
func isShort(e string) bool {
|
||||
re := []rune(e)
|
||||
|
||||
return e != "--" && len(re) == 2 && re[0] == '-'
|
||||
}
|
||||
|
||||
func isLong(e string) bool {
|
||||
return len(e) > 2 && e[:2] == "--"
|
||||
}
|
||||
|
||||
func isCombined(e string) bool {
|
||||
re := []rune(e)
|
||||
|
||||
return len(re) > 2 && re[0] != '-'
|
||||
}
|
||||
|
||||
func isUnixFlag(e string) bool {
|
||||
return isShort(e) || isLong(e)
|
||||
}
|
||||
|
||||
func formatArg(e string) []string {
|
||||
if isCombined(e) {
|
||||
var out []string
|
||||
for _, r := range e[1:] {
|
||||
out = append(out, string([]rune{'-', r}))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
return []string{e}
|
||||
}
|
||||
|
||||
func formatArgs(args []string) []string {
|
||||
var (
|
||||
out []string
|
||||
ignore bool
|
||||
)
|
||||
|
||||
for _, e := range args {
|
||||
if ignore {
|
||||
out = append(out, e)
|
||||
} else {
|
||||
ignore = e == "--"
|
||||
out = append(out, formatArg(e)...)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func containsUnixFlag(args []string) bool {
|
||||
for _, arg := range args {
|
||||
if isUnixFlag(arg) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package command
|
||||
|
||||
// Flag est une interface représentant des arguments nommés d’une ligne de commande.
|
||||
type Flag interface {
|
||||
Parser
|
||||
Helper
|
||||
ArgName() string
|
||||
Flags() []string
|
||||
Value() any
|
||||
}
|
||||
|
||||
// FlagSet est un ensemble de flags.
|
||||
type FlagSet interface {
|
||||
Parser
|
||||
Help() string
|
||||
HelpRequired() bool
|
||||
Flags() []Flag
|
||||
Args() []string
|
||||
}
|
Loading…
Reference in New Issue