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 }