gob/shell/command/flag/flagset.go

433 lines
9.1 KiB
Go
Raw Normal View History

2023-10-05 08:42:40 +00:00
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 laide 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++
}
// Sil 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 sil navait
// 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 laide 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
}