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