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