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