getopt: init module

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

2
cli.go
View file

@ -6,7 +6,7 @@ import (
"path/filepath" "path/filepath"
"git.sr.ht/~sircmpwn/getopt" "git.baxters.nz/jeremy/cli/getopt"
) )
var undef = "(undefined)" var undef = "(undefined)"

View file

@ -0,0 +1,4 @@
(undo-tree-save-format-version . 1)
"ea9995832c3e0239223cbc9357def034cfcdda62"
[nil current nil nil (27067 8166 957871 684000) 0 nil]
nil

26
getopt/LICENSE Normal file
View file

@ -0,0 +1,26 @@
Copyright 2019 Drew DeVault <sir@cmpwn.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

43
getopt/bool.go Normal file
View file

@ -0,0 +1,43 @@
package getopt
import (
"flag"
"strconv"
)
type boolFlag interface {
flag.Value
IsBoolFlag() bool
}
type boolVal bool
func (b *boolVal) String() string {
return strconv.FormatBool(bool(*b))
}
func (b *boolVal) Set(val string) error {
*b = boolVal(true)
return nil
}
func (b *boolVal) IsBoolFlag() bool {
return true
}
// BoolVar defines a bool flag with specified name, default value, and
// usage string. The argument p points to a bool variable in which to
// store the value of the flag.
func (set *FlagSet) BoolVar(p *bool, name string, value bool, usage string) {
*p = value
set.Var((*boolVal)(p), name, usage)
}
// Bool defines a bool flag with specified name, default value, and
// usage string. The return value is the address of a bool variable that
// stores the value of the flag.
func (set *FlagSet) Bool(name string, value bool, usage string) *bool {
var b bool
set.BoolVar(&b, name, value, usage)
return &b
}

180
getopt/commandline.go Normal file
View file

@ -0,0 +1,180 @@
package getopt
import (
"flag"
"io"
"os"
"time"
)
// CommandLine is the default set of command-line flags, parsed from
// os.Args. The top-level functions such as BoolVar, Arg, and so on are
// wrappers for the methods of CommandLine.
var CommandLine = NewFlagSet(os.Args[0], flag.ExitOnError)
// Usage prints a usage message documenting all defined command-line
// flags to os.Stderr. It is called when an error occurs while parsing
// flags. The function is a variable that may be changed to point to a
// custom function. By default it prints a simple header and calls
// PrintDefaults; for details about the format of the output and how to
// control it, see the documentation for PrintDefaults. Custom usage
// functions may choose to exit the program; by default exiting happens
// anyway as the command line's error handling strategy is set to
// ExitOnError.
var Usage = CommandLine.Usage
// PrintDefaults prints, to standard error unless configured otherwise,
// a usage message showing the default settings of all defined
// command-line flags.
func PrintDefaults() { CommandLine.PrintDefaults() }
// Arg returns the i'th command-line argument. Arg(0) is the first
// remaining argument after flags have been processed. Arg returns an
// empty string if the requested element does not exist.
func Arg(i int) string { return CommandLine.Arg(i) }
// Args returns the non-flag command-line arguments.
func Args() []string { return CommandLine.Args() }
// NArg is the number of arguments remaining after flags have been
// processed.
func NArg() int { return CommandLine.NArg() }
// NFlag returns the number of command-line flags that have been set.
func NFlag() int { return CommandLine.NFlag() }
// Parsed reports whether the command-line flags have been parsed.
func Parsed() bool { return CommandLine.Parsed() }
// Parse parses the command-line flags from os.Args. Must be called
// after all flags are defined and before flags are accessed by the
// program.
func Parse() error { return CommandLine.Parse() }
// Var defines a flag with the specified name and usage string. The type
// and value of the flag are represented by the first argument, of type
// Value, which typically holds a user-defined implementation of Value.
// For instance, the caller could create a flag that turns a
// comma-separated string into a slice of strings by giving the slice
// the methods of Value; in particular, Set would decompose the
// comma-separated string into the slice.
func Var(value flag.Value, name string, usage string) {
CommandLine.Var(value, name, usage)
}
// Lookup returns the Flag structure of the named flag, returning nil if
// none exists.
func Lookup(name string) *Flag {
return CommandLine.Lookup(name)
}
// SetOutput sets the destination for usage and error messages. If
// output is nil, os.Stderr is used.
func SetOutput(output io.Writer) {
CommandLine.SetOutput(output)
}
// BoolVar defines a bool flag with specified name, default value, and
// usage string. The argument p points to a bool variable in which to
// store the value of the flag.
func BoolVar(p *bool, name string, value bool, usage string) {
CommandLine.BoolVar(p, name, value, usage)
}
// Bool defines a bool flag with specified name, default value, and
// usage string. The return value is the address of a bool variable that
// stores the value of the flag.
func Bool(name string, value bool, usage string) *bool {
return CommandLine.Bool(name, value, usage)
}
// DurationVar defines a time.Duration flag with specified name, default
// value, and usage string. The argument p points to a time.Duration
// variable in which to store the value of the flag. The flag accepts a
// value acceptable to time.ParseDuration.
func DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
CommandLine.DurationVar(p, name, value, usage)
}
// Duration defines a time.Duration flag with specified name, default
// value, and usage string. The return value is the address of a
// time.Duration variable that stores the value of the flag. The flag
// accepts a value acceptable to time.ParseDuration.
func Duration(name string, value time.Duration, usage string) *time.Duration {
return CommandLine.Duration(name, value, usage)
}
// Float64Var defines a float64 flag with specified name, default value,
// and usage string. The argument p points to a float64 variable in
// which to store the value of the flag.
func Float64Var(p *float64, name string, value float64, usage string) {
CommandLine.Float64Var(p, name, value, usage)
}
// Float64 defines a float64 flag with specified name, default value,
// and usage string. The return value is the address of a float64
// variable that stores the value of the flag.
func Float64(name string, value float64, usage string) *float64 {
return CommandLine.Float64(name, value, usage)
}
// IntVar defines a int flag with specified name, default value,
// and usage string. The argument p points to a int variable in
// which to store the value of the flag.
func IntVar(p *int, name string, value int, usage string) {
CommandLine.IntVar(p, name, value, usage)
}
// Int defines an int flag with specified name, default value, and usage
// string. The return value is the address of an int variable that
// stores the value of the flag.
func Int(name string, value int, usage string) *int {
return CommandLine.Int(name, value, usage)
}
// Int64Var defines an int64 flag with specified name, default value,
// and usage string. The argument p points to an int64 variable in which
// to store the value of the flag.
func Int64Var(p *int64, name string, value int64, usage string) {
CommandLine.Int64Var(p, name, value, usage)
}
// Int64 defines an int64 flag with specified name, default value, and
// usage string. The return value is the address of an int64 variable
// that stores the value of the flag.
func Int64(name string, value int64, usage string) *int64 {
return CommandLine.Int64(name, value, usage)
}
// StringVar defines a string flag with specified name, default value,
// and usage string. The argument p points to a string variable in which
// to store the value of the flag.
func StringVar(p *string, name string, value string, usage string) {
CommandLine.StringVar(p, name, value, usage)
}
// String defines a string flag with specified name, default value, and
// usage string. The return value is the address of a string variable
// that stores the value of the flag.
func String(name string, value string, usage string) *string {
return CommandLine.String(name, value, usage)
}
// Uint64Var defines a uint64 flag with specified name, default value,
// and usage string. The argument p points to a uint64 variable in which
// to store the value of the flag.
func Uint64Var(p *uint64, name string, value uint64, usage string) {
CommandLine.Uint64Var(p, name, value, usage)
}
// Uint64 defines a uint64 flag with specified name, default value, and
// usage string. The return value is the address of a uint64 variable
// that stores the value of the flag.
func Uint64(name string, value uint64, usage string) *uint64 {
return CommandLine.Uint64(name, value, usage)
}
// Set sets the value of the named flag.
func Set(name, value string) error {
return CommandLine.Set(name, value)
}

39
getopt/duration.go Normal file
View file

@ -0,0 +1,39 @@
package getopt
import (
"time"
)
type durationVal time.Duration
func (d *durationVal) String() string {
return time.Duration(*d).String()
}
func (d *durationVal) Set(val string) error {
v, err := time.ParseDuration(val)
if err != nil {
return err
}
*d = durationVal(v)
return nil
}
// DurationVar defines a time.Duration flag with specified name, default
// value, and usage string. The argument p points to a time.Duration
// variable in which to store the value of the flag. The flag accepts a
// value acceptable to time.ParseDuration.
func (set *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
*p = value
set.Var((*durationVal)(p), name, usage)
}
// Duration defines a time.Duration flag with specified name, default
// value, and usage string. The return value is the address of a
// time.Duration variable that stores the value of the flag. The flag
// accepts a value acceptable to time.ParseDuration.
func (set *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration {
var d time.Duration
set.DurationVar(&d, name, value, usage)
return &d
}

74
getopt/flag_test.go Normal file
View file

@ -0,0 +1,74 @@
package getopt
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestInt(t *testing.T) {
assert := assert.New(t)
p := NewFlagSet("", 0)
var k int
p.IntVar(&k, "k", 16, "set k")
i := p.Int64("i", -1, "set i")
j := p.Uint("j", 64, "set j")
err := p.parse([]string{"bin", "-i", "32", "normal arg"})
assert.Nil(err, "Expected err to be nil")
assert.Equal(3, p.optindex, "Expected to only parse two arguments")
assert.Equal(int64(32), *i, "Expected -i argument to equal 32")
assert.Equal(uint(64), *j, "Expected -j argument to equal 64, since unset")
assert.Equal(16, k, "Expected -k argument to equal 16, since unset")
}
func TestBool(t *testing.T) {
assert := assert.New(t)
p := NewFlagSet("", 0)
var a bool
p.BoolVar(&a, "a", false, "set a")
b := p.Bool("b", false, "set b")
err := p.parse([]string{"bin", "-a", "normal arg"})
assert.Nil(err, "Expected err to be nil")
assert.Equal(2, p.optindex, "Expected to only parse two arguments")
assert.Equal(true, a, "Expected -a argument to be set")
assert.Equal(false, *b, "Expected -b argument to not be set")
}
func TestString(t *testing.T) {
assert := assert.New(t)
p := NewFlagSet("", 0)
get := p.String("c", "default", "get -c")
opt := "some options"
err := p.parse([]string{"bin", "-c", opt, "normal arg"})
assert.Nil(err, "Expected err to be nil")
assert.Equal(opt, *get, "Expected argument to be parsed")
}
func TestFloat64(t *testing.T) {
assert := assert.New(t)
p := NewFlagSet("", 0)
f := p.Float64("f", -3.14, "get -f")
err := p.parse([]string{"bin", "-f", "3.14", "normal arg"})
assert.Nil(err, "Expected err to be nil")
assert.Equal(3.14, *f, "Expected -f to equal 3.14")
}
func TestDuration(t *testing.T) {
assert := assert.New(t)
p := NewFlagSet("", 0)
d := p.Duration("d", 0, "get -d")
err := p.parse([]string{"bin", "-d", "1h3m", "normal arg"})
assert.Nil(err, "Expected err to be nil")
assert.Equal(time.Hour+3*time.Minute, *d, "Expected -d to equal 1 hour and 3 minutes")
}

259
getopt/flagset.go Normal file
View file

@ -0,0 +1,259 @@
package getopt
import (
"bytes"
"flag"
"fmt"
"io"
"os"
"strings"
"unicode/utf8"
)
const (
typeBool = iota
typeString
typeInt
typeUint
typeInt64
typeUint64
typeFloat64
typeDuration
)
// A Flag represents the state of a flag.
type Flag struct {
Name string
Rune rune
Value flag.Value
Usage string
used bool
}
// A FlagSet represents a set of defined flags. The zero value of a
// FlagSet has no name and has ContinueOnError error handling.
type FlagSet struct {
parsed bool
optindex int
flags map[rune]*Flag
output io.Writer
name string
errorHandling flag.ErrorHandling
Usage func()
}
// NewFlagSet returns a new, empty flag set.
func NewFlagSet(name string, err flag.ErrorHandling) *FlagSet {
set := FlagSet{
name: name,
flags: make(map[rune]*Flag),
output: os.Stderr,
errorHandling: err,
}
set.Usage = func() {
if set.name != "" {
fmt.Fprintf(set.output, "Usage of %s:\n\n", set.name)
} else {
fmt.Fprintf(set.output, "Usage:\n\n")
}
set.PrintDefaults()
}
return &set
}
func (set *FlagSet) parse(args []string) error {
var buf bytes.Buffer
for r, f := range set.flags {
buf.WriteRune(r)
if f, ok := f.Value.(boolFlag); !ok || !f.IsBoolFlag() {
buf.WriteByte(':')
}
}
options, optind, err := Getopts(args, buf.String())
if err != nil {
return err
}
set.optindex = optind
for _, opt := range options {
err = set.Set(fmt.Sprintf("%c", opt.Option), opt.Value)
if err != nil {
return err
}
}
set.parsed = true
return nil
}
// Set sets the value of the named flag.
func (set *FlagSet) Set(name, value string) error {
r, _ := utf8.DecodeRuneInString(name)
flag, ok := set.flags[r]
if !ok {
return fmt.Errorf("no such flag -%v", name)
}
flag.used = true
return flag.Value.Set(value)
}
// Arg returns the i'th command-line argument. Arg(0) is the first
// remaining argument after flags have been processed. Arg returns an
// empty string if the requested element does not exist.
func (set *FlagSet) Arg(i int) string {
var arg string
if len(set.Args()) < i {
arg = set.Args()[i]
}
return arg
}
// Args returns the non-flag command-line arguments.
func (set *FlagSet) Args() []string {
return os.Args[set.optindex:]
}
// NArg is the number of arguments remaining after flags have been
// processed.
func (set *FlagSet) NArg() int {
return len(set.Args())
}
// NFlag returns the number of command-line flags that have been set.
func (set *FlagSet) NFlag() int {
return len(set.flags)
}
// Parsed reports whether the command-line flags have been parsed.
func (set *FlagSet) Parsed() bool {
return set.parsed
}
// ParseSlice parses the command-line flags from args. Must be called
// after all flags are defined and before flags are accessed by the
// program.
func (set *FlagSet) ParseSlice(args []string) (err error) {
err = set.parse(args)
if err != nil {
switch set.errorHandling {
case flag.PanicOnError:
panic(err)
case flag.ExitOnError:
fmt.Println(err)
os.Exit(2)
}
}
return
}
// Parse parses the command-line flags from os.Args. Must be called
// after all flags are defined and before flags are accessed by the
// program.
func (set *FlagSet) Parse() error {
return set.ParseSlice(os.Args)
}
// ErrorHandling returns the error handling behavior of the flag set.
func (set *FlagSet) ErrorHandling() flag.ErrorHandling {
return set.errorHandling
}
// Var defines a flag with the specified name and usage string. The type
// and value of the flag are represented by the first argument, of type
// Value, which typically holds a user-defined implementation of Value.
// For instance, the caller could create a flag that turns a
// comma-separated string into a slice of strings by giving the slice
// the methods of Value; in particular, Set would decompose the
// comma-separated string into the slice.
func (set *FlagSet) Var(value flag.Value, name string, usage string) {
r, _ := utf8.DecodeRuneInString(name)
set.flags[r] = &Flag{
Name: name,
Rune: r,
Value: value,
Usage: usage,
}
}
func (set *FlagSet) visitIf(cond func(*Flag) bool, fn func(*Flag)) {
var runes []rune
for r := range set.flags {
runes = append(runes, r)
}
for i := range runes {
for j := i; j > 0; j-- {
if runes[j] < runes[j-1] {
runes[j], runes[j-1] = runes[j-1], runes[j]
}
}
}
for _, r := range runes {
if cond == nil || cond(set.flags[r]) {
fn(set.flags[r])
}
}
}
// VisitAll visits the command-line flags in lexicographical order,
// calling fn for each. It visits all flags, even those not set.
func (set *FlagSet) VisitAll(fn func(*Flag)) {
set.visitIf(nil, fn)
}
// Visit visits the command-line flags in lexicographical order,
// calling fn for each.
func (set *FlagSet) Visit(fn func(*Flag)) {
set.visitIf(func(flag *Flag) bool { return flag.used }, fn)
}
// Lookup returns the Flag structure of the named flag, returning nil if
// none exists.
func (set *FlagSet) Lookup(name string) *Flag {
r, _ := utf8.DecodeRuneInString(name)
return set.flags[r]
}
// PrintDefaults prints, to standard error unless configured otherwise,
// a usage message showing the default settings of all defined
// command-line flags.
func (set *FlagSet) PrintDefaults() {
out := set.output
set.VisitAll(func(flag *Flag) {
fmt.Fprintf(out, " -%c", flag.Rune)
val := flag.Value
usage := strings.Replace(flag.Usage, "\n", "\n \t", -1)
fmt.Fprintf(out, "\t%s", usage)
if _, ok := val.(*stringVal); ok {
fmt.Fprintf(out, " (default %q)", val)
} else if _, ok := val.(*boolVal); !ok {
fmt.Fprintf(out, " (default %s)", val)
}
fmt.Fprintf(out, "\n")
})
}
// SetOutput sets the destination for usage and error messages. If
// output is nil, os.Stderr is used.
func (set *FlagSet) SetOutput(output io.Writer) {
if output == nil {
set.output = os.Stderr
} else {
set.output = output
}
}
// Output returns the destination for usage and error messages.
// os.Stderr is returned if output was not set or was set to nil.
func (set *FlagSet) Output() io.Writer {
return set.output
}

33
getopt/float.go Normal file
View file

@ -0,0 +1,33 @@
package getopt
import (
"fmt"
)
type floatVal float64
func (f *floatVal) String() string {
return fmt.Sprint(float64(*f))
}
func (f *floatVal) Set(val string) error {
_, err := fmt.Sscanf(val, "%g", f)
return err
}
// Float64Var defines a float64 flag with specified name, default value,
// and usage string. The argument p points to a float64 variable in
// which to store the value of the flag.
func (set *FlagSet) Float64Var(p *float64, name string, value float64, usage string) {
*p = value
set.Var((*floatVal)(p), name, usage)
}
// Float64 defines a float64 flag with specified name, default value,
// and usage string. The return value is the address of a float64
// variable that stores the value of the flag.
func (set *FlagSet) Float64(name string, value float64, usage string) *float64 {
var f float64
set.Float64Var(&f, name, value, usage)
return &f
}

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
}

89
getopt/getopt_test.go Normal file
View file

@ -0,0 +1,89 @@
package getopt
import (
"fmt"
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSimpleCase(t *testing.T) {
assert := assert.New(t)
opts, i, err := Getopts([]string{
"test_bin", "-afo", "output-file", "normal arg"}, "afo:")
assert.Nil(err, "Expected err to be nil")
assert.Equal(len(opts), 3, "Expected 3 options to be parsed")
assert.Equal(i, 3, "Expected non-option args to start at index 2")
assert.Equal(opts[0], Option{'a', ""})
assert.Equal(opts[1], Option{'f', ""})
assert.Equal(opts[2], Option{'o', "output-file"})
}
func TestShortFormArgument(t *testing.T) {
assert := assert.New(t)
opts, i, err := Getopts([]string{
"test_bin", "-afooutput-file", "normal arg"}, "afo:")
assert.Nil(err, "Expected err to be nil")
assert.Equal(len(opts), 3, "Expected 3 options to be parsed")
assert.Equal(i, 2, "Expected non-option args to start at index 2")
assert.Equal(opts[0], Option{'a', ""})
assert.Equal(opts[1], Option{'f', ""})
assert.Equal(opts[2], Option{'o', "output-file"})
}
func TestSeparateArgs(t *testing.T) {
assert := assert.New(t)
opts, i, err := Getopts([]string{
"test_bin", "-a", "-f", "-o", "output-file", "normal arg"}, "afo:")
assert.Nil(err, "Expected err to be nil")
assert.Equal(len(opts), 3, "Expected 3 options to be parsed")
assert.Equal(i, 5, "Expected non-option args to start at index 5")
assert.Equal(opts[0], Option{'a', ""})
assert.Equal(opts[1], Option{'f', ""})
assert.Equal(opts[2], Option{'o', "output-file"})
}
func TestTwoDashes(t *testing.T) {
assert := assert.New(t)
opts, i, err := Getopts([]string{
"test_bin", "-afo", "output-file", "--", "-f", "normal arg"}, "afo:")
assert.Nil(err, "Expected err to be nil")
assert.Equal(len(opts), 3, "Expected 3 options to be parsed")
assert.Equal(i, 4, "Expected non-option args to start at index 4")
assert.Equal(opts[0], Option{'a', ""})
assert.Equal(opts[1], Option{'f', ""})
assert.Equal(opts[2], Option{'o', "output-file"})
}
func TestUnknownOption(t *testing.T) {
assert := assert.New(t)
_, _, err := Getopts([]string{"test_bin", "-x"}, "y")
var errt UnknownOptionError
assert.IsType(err, errt, "Expected unknown option error")
assert.Equal(err.Error(), fmt.Sprintf("%s: unknown option -x", os.Args[0]),
"Expected POSIX-compatible error message")
}
func TestMissingOption(t *testing.T) {
assert := assert.New(t)
_, _, err := Getopts([]string{"test_bin", "-x"}, "x:")
var errt MissingOptionError
assert.IsType(err, errt, "Expected missing option error")
assert.Equal(err.Error(), fmt.Sprintf("%s: expected argument for -x",
os.Args[0]), "Expected POSIX-compatible error message")
}
func TestExpectedMissingOption(t *testing.T) {
assert := assert.New(t)
opts, _, err := Getopts([]string{"test_bin", "-x"}, ":x:")
assert.Nil(err, "Expected err to be nil")
assert.Equal(len(opts), 1, "Expected 1 option to be parsed")
assert.Equal(opts[0], Option{':', "x"})
}
func TestNoOption(t *testing.T) {
assert := assert.New(t)
_, i, _ := Getopts([]string{"test_bin"}, "")
assert.Equal(i, 1, "Expected non-option args to start at index 1")
}

3
getopt/go.mod Normal file
View file

@ -0,0 +1,3 @@
module git.sr.ht/~sircmpwn/getopt
require github.com/stretchr/testify v1.3.0

7
getopt/go.sum Normal file
View file

@ -0,0 +1,7 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

61
getopt/int.go Normal file
View file

@ -0,0 +1,61 @@
package getopt
import (
"fmt"
)
type intVal int
func (i *intVal) String() string {
return fmt.Sprint(*i)
}
func (i *intVal) Set(val string) error {
_, err := fmt.Sscanf(val, "%d", i)
return err
}
// IntVar defines a int flag with specified name, default value,
// and usage string. The argument p points to a int variable in
// which to store the value of the flag.
func (set *FlagSet) IntVar(p *int, name string, value int, usage string) {
*p = value
set.Var((*intVal)(p), name, usage)
}
// Int defines an int flag with specified name, default value, and usage
// string. The return value is the address of an int variable that
// stores the value of the flag.
func (set *FlagSet) Int(name string, value int, usage string) *int {
p := new(int)
set.IntVar(p, name, value, usage)
return p
}
type int64Val int64
func (i *int64Val) String() string {
return fmt.Sprint(int64(*i))
}
func (i *int64Val) Set(val string) error {
_, err := fmt.Sscanf(val, "%d", i)
return err
}
// Int64Var defines an int64 flag with specified name, default value,
// and usage string. The argument p points to an int64 variable in which
// to store the value of the flag.
func (set *FlagSet) Int64Var(p *int64, name string, value int64, usage string) {
*p = value
set.Var((*int64Val)(p), name, usage)
}
// Int64 defines an int64 flag with specified name, default value, and
// usage string. The return value is the address of an int64 variable
// that stores the value of the flag.
func (set *FlagSet) Int64(name string, value int64, usage string) *int64 {
var i int64
set.Int64Var(&i, name, value, usage)
return &i
}

29
getopt/string.go Normal file
View file

@ -0,0 +1,29 @@
package getopt
type stringVal string
func (s *stringVal) String() string {
return string(*s)
}
func (s *stringVal) Set(val string) error {
*s = stringVal(val)
return nil
}
// StringVar defines a string flag with specified name, default value,
// and usage string. The argument p points to a string variable in which
// to store the value of the flag.
func (set *FlagSet) StringVar(p *string, name string, value string, usage string) {
*p = value
set.Var((*stringVal)(p), name, usage)
}
// String defines a string flag with specified name, default value, and
// usage string. The return value is the address of a string variable
// that stores the value of the flag.
func (set *FlagSet) String(name string, value string, usage string) *string {
var s string
set.StringVar(&s, name, value, usage)
return &s
}

73
getopt/uint.go Normal file
View file

@ -0,0 +1,73 @@
package getopt
import "fmt"
type uintVal uint
func (i *uintVal) String() string {
return fmt.Sprint(uint(*i))
}
func (i *uintVal) Set(val string) error {
_, err := fmt.Sscanf(val, "%d", i)
return err
}
// UintVar defines a uint flag with specified name, default value, and
// usage string. The argument p points to a uint variable in which to
// store the value of the flag.
func (set *FlagSet) UintVar(p *uint, name string, value uint, usage string) {
*p = value
set.Var((*uintVal)(p), name, usage)
}
// UintVar defines a uint flag with specified name, default value, and
// usage string. The argument p points to a uint variable in which to
// store the value of the flag.
func UintVar(p *uint, name string, value uint, usage string) {
CommandLine.UintVar(p, name, value, usage)
}
// Uint defines a uint flag with specified name, default value, and
// usage string. The return value is the address of a uint variable that
// stores the value of the flag.
func (set *FlagSet) Uint(name string, value uint, usage string) *uint {
var b uint
set.UintVar(&b, name, value, usage)
return &b
}
// Uint defines a uint flag with specified name, default value, and
// usage string. The return value is the address of a uint variable that
// stores the value of the flag.
func Uint(name string, value uint, usage string) *uint {
return CommandLine.Uint(name, value, usage)
}
type uint64Val uint64
func (i *uint64Val) String() string {
return fmt.Sprint(uint64(*i))
}
func (i *uint64Val) Set(val string) error {
_, err := fmt.Sscanf(val, "%d", i)
return err
}
// Uint64Var defines a uint64 flag with specified name, default value,
// and usage string. The argument p points to a uint64 variable in which
// to store the value of the flag.
func (set *FlagSet) Uint64Var(p *uint64, name string, value uint64, usage string) {
*p = value
set.Var((*uint64Val)(p), name, usage)
}
// Uint64 defines a uint64 flag with specified name, default value, and
// usage string. The return value is the address of a uint64 variable
// that stores the value of the flag.
func (set *FlagSet) Uint64(name string, value uint64, usage string) *uint64 {
var b uint64
set.Uint64Var(&b, name, value, usage)
return &b
}

2
go.mod
View file

@ -1,5 +1,3 @@
module git.baxters.nz/jeremy/cli module git.baxters.nz/jeremy/cli
go 1.26.1 go 1.26.1
require git.sr.ht/~sircmpwn/getopt v1.0.0