gob/shell/command/flag/flagset.go

433 lines
9.1 KiB
Go
Raw Permalink 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/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
}