getopt: init module

This commit is contained in:
Jeremy Baxter 2026-03-19 14:07:07 +13:00
parent 8290a04536
commit 2729e58eab
17 changed files with 1076 additions and 3 deletions

155
getopt/getopt.go Normal file
View file

@ -0,0 +1,155 @@
// getopt is a POSIX-compatible implementation of getopt(3) for Go.
//
// Example usage:
//
// import (
// "os"
// "git.sr.ht/~sircmpwn/getopt"
// )
//
// func main() {
// opts, optind, err := getopt.Getopts(os.Args, "abc:d:")
// if err != nil {
// panic(err)
// }
// for _, opt := range opts {
// switch opt.Option {
// case 'a':
// println("Option -a specified")
// case 'b':
// println("Option -b specified")
// case 'c':
// println("Option -c specified: " + opt.Value)
// case 'd':
// println("Option -d specified: " + opt.Value)
// }
// }
// println("Remaining arguments:")
// for _, arg := range os.Args[optind:] {
// println(arg)
// }
// }
//
// A flag[0]-like interface is also supported.
//
// import (
// "git.sr.ht/~sircmpwn/getopt"
// )
//
// func main() {
// a := getopt.Bool("a", false, "turn on option a")
// b := getopt.Int("b", 1, "set b to a numerical value")
// var opt string
// getopt.StringVar(&opt, "c", "", "let c be specified string")
//
// err := getopt.Parse()
// if err != nil {
// panic(err)
// }
//
// print("Value of a: ")
// println(*a)
// print("Value of b: ")
// println(*b)
// println("Value of c: " + opt)
//
// println("Remaining arguments:")
// for _, arg := range getopt.Args() {
// println(arg)
// }
// }
//
// [0]: https://golang.org/pkg/flag/
package getopt
import (
"fmt"
"os"
)
// In the case of "-o example", Option is 'o' and "example" is Value. For
// options which do not take an argument, Value is "".
type Option struct {
Option rune
Value string
}
// This is returned when an unknown option is found in argv, but not in the
// option spec.
type UnknownOptionError rune
func (e UnknownOptionError) Error() string {
return fmt.Sprintf("%s: unknown option -%c", os.Args[0], rune(e))
}
// This is returned when an option with a mandatory argument is missing that
// argument.
type MissingOptionError rune
func (e MissingOptionError) Error() string {
return fmt.Sprintf("%s: expected argument for -%c", os.Args[0], rune(e))
}
// Getopts implements a POSIX-compatible options interface.
//
// Returns a slice of options and the index of the first non-option argument.
//
// If an error is returned, you must print it to stderr to be POSIX complaint.
func Getopts(argv []string, spec string) ([]Option, int, error) {
optmap := make(map[rune]bool)
runes := []rune(spec)
for i, rn := range spec {
if rn == ':' {
if i == 0 {
continue
}
optmap[runes[i-1]] = true
} else {
optmap[rn] = false
}
}
var (
i int
opts []Option
)
for i = 1; i < len(argv); i++ {
arg := argv[i]
runes = []rune(arg)
if len(arg) == 0 || arg == "-" {
break
}
if arg[0] != '-' {
break
}
if arg == "--" {
i++
break
}
for j, opt := range runes[1:] {
if optopt, ok := optmap[opt]; !ok {
opts = append(opts, Option{'?', ""})
return opts, i, UnknownOptionError(opt)
} else if optopt {
if j+1 < len(runes)-1 {
opts = append(opts, Option{opt, string(runes[j+2:])})
break
} else {
if i+1 >= len(argv) {
if len(spec) >= 1 && spec[0] == ':' {
opts = append(opts, Option{':', string(opt)})
} else {
return opts, i, MissingOptionError(opt)
}
} else {
opts = append(opts, Option{opt, argv[i+1]})
i++
}
}
} else {
opts = append(opts, Option{opt, ""})
}
}
}
return opts, i, nil
}