camfish/flag.go
2024-12-31 01:27:53 -05:00

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