143 lines
4.4 KiB
Go
143 lines
4.4 KiB
Go
package camfish
|
|
|
|
import "fmt"
|
|
import "iter"
|
|
import "git.tebibyte.media/sashakoshka/go-cli"
|
|
|
|
// FlagSet holds command-line flags to be parsed.
|
|
type FlagSet interface {
|
|
// Flag creates a new flag. If there are naming collisions between
|
|
// flags, flags specified later take precedence over those specified
|
|
// earlier.
|
|
//
|
|
// A short and long form of the flag may be specified. The short form of
|
|
// a flag is invoked with a single dash and one or more runes, each rune
|
|
// invoking its corresponding flag exactly once. The long form is
|
|
// invoked with two dashes followed by the text of the long form, and it
|
|
// should be in lower kebab case. If short is zero, the flag will not
|
|
// have a short form. All flags must have a long form.
|
|
Flag(short rune, long string, help string, validate func(string) error) Flag
|
|
}
|
|
|
|
// Flag represents the result of parsing a command-line flag. It is filled in
|
|
// automatically during argument parsing, and the values within it can be
|
|
// accessed afterwards.
|
|
type Flag interface {
|
|
// First returns the value where the flag was first found. If the flag
|
|
// was never specified, false is returned for found. If the flag either
|
|
// was never specified or does not take input, the string "true" will be
|
|
// returned. If at least one value is given but this function is never
|
|
// called to recieve it, the environment will exit with an error.
|
|
First() (value string, found bool)
|
|
// All returns an iterator over all instances of this flag that were
|
|
// specified. If this flag takes no input, all returned values will be
|
|
// the string "true". If multiple instances of a flag are given but this
|
|
// function is never called to receive them, will the environment exit
|
|
// with an error.
|
|
All() iter.Seq2[int, string]
|
|
}
|
|
|
|
// flagSet is an implementation of [FlagSet] that wraps [cli.Cli].
|
|
type flagSet struct {
|
|
name string
|
|
description string
|
|
cli cli.Cli
|
|
short map[rune ] int
|
|
long map[string] int
|
|
flags []*flag
|
|
}
|
|
|
|
func (this *flagSet) Flag(short rune, long string, help string, validate func(string) error) Flag {
|
|
if this.short == nil { this.short = make(map[rune ] int) }
|
|
if this.long == nil { this.long = make(map[string] int) }
|
|
fla := &flag {
|
|
short: short,
|
|
long: long,
|
|
help: help,
|
|
validate: validate,
|
|
}
|
|
if short != 0 { this.short[short] = len(this.flags) }
|
|
if long != "" { this.long[long] = len(this.flags) }
|
|
this.flags = append(this.flags, fla)
|
|
return fla
|
|
}
|
|
|
|
// parse parses the arguments using the [flag]s contained within the set.
|
|
func (this *flagSet) parse(args []string) error {
|
|
cliFlags := make([]*cli.Flag, len(this.flags))
|
|
for index, fla := range this.flags {
|
|
index := index
|
|
cliFlags[index] = &cli.Flag {
|
|
Short: fla.short,
|
|
Long: fla.long,
|
|
Help: fla.help,
|
|
Validate: fla.validate,
|
|
Found: func(_ *cli.Cli, value string) {
|
|
this.flags[index].values = append(this.flags[index].values, value)
|
|
},
|
|
}
|
|
}
|
|
|
|
this.cli.Description = this.description
|
|
this.cli.Flags = cliFlags
|
|
_, err := this.cli.Parse(args)
|
|
return err
|
|
}
|
|
|
|
// fullyReceived returns an error if not all specified values were used. This
|
|
// behavior is currently unused.
|
|
func (this *flagSet) fullyReceived() error {
|
|
for _, fla := range this.flags {
|
|
if !fla.fullyReceived() {
|
|
return FlagError {
|
|
Long: fla.long,
|
|
Err: fmt.Errorf (
|
|
"%w: %v",
|
|
ErrExtraneousValues,
|
|
fla.values[fla.received:]),
|
|
}
|
|
}
|
|
}
|
|
if len(this.cli.Args) > 0 {
|
|
return ErrExtraneousValues
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// usage prints usage/help information to [os.Stderr]. it only works properly
|
|
// after parse has been run.
|
|
func (this *flagSet) usage() {
|
|
this.cli.Usage()
|
|
}
|
|
|
|
// flag is an implementation of [Flag] that
|
|
type flag struct {
|
|
short rune
|
|
long string
|
|
help string
|
|
validate func(string) error
|
|
values []string
|
|
received int
|
|
}
|
|
|
|
func (this *flag) First() (value string, found bool) {
|
|
if len(this.values) < 1 { return "", false }
|
|
if this.received < 1 { this.received = 1 }
|
|
return this.values[0], true
|
|
}
|
|
|
|
func (this *flag) All() iter.Seq2[int, string] {
|
|
return func(yield func(int, string) bool) {
|
|
for index, value := range this.values {
|
|
if this.received <= index { this.received = index + 1 }
|
|
if !yield(index, value) { return }
|
|
}
|
|
}
|
|
}
|
|
|
|
// fullyReceived returns whether all values were used. If this flag does not
|
|
// take in a value, it just returns true.
|
|
func (this *flag) fullyReceived() bool {
|
|
return this.validate == nil || this.received >= len(this.values)
|
|
}
|