Compare commits
6 Commits
guimaster
...
jcarrmaste
Author | SHA1 | Date |
---|---|---|
|
5716f9af0d | |
|
3d847431ab | |
|
f92d210ca7 | |
|
6b16520795 | |
|
0af6f25365 | |
|
530fcb84d4 |
|
@ -22,3 +22,5 @@ _testmain.go
|
|||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
go.*
|
||||
|
|
10
Makefile
10
Makefile
|
@ -9,13 +9,3 @@ redomod:
|
|||
rm -f go.*
|
||||
GO111MODULE= go mod init
|
||||
GO111MODULE= go mod tidy
|
||||
|
||||
test:
|
||||
go test
|
||||
|
||||
vet:
|
||||
@GO111MODULE=off go vet
|
||||
@echo this go binary package builds okay
|
||||
|
||||
goimports:
|
||||
goimports -w *.go
|
||||
|
|
14
go.mod
14
go.mod
|
@ -1,14 +0,0 @@
|
|||
module go.wit.com/dev/alexflint/arg
|
||||
|
||||
require (
|
||||
github.com/alexflint/go-scalar v1.2.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0 // indirect
|
||||
)
|
||||
|
||||
go 1.18
|
16
go.sum
16
go.sum
|
@ -1,16 +0,0 @@
|
|||
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
||||
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
62
parse.go
62
parse.go
|
@ -11,7 +11,7 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
|
||||
scalar "github.com/alexflint/go-scalar"
|
||||
"go.wit.com/dev/alexflint/scalar"
|
||||
)
|
||||
|
||||
// path represents a sequence of steps to find the output location for an
|
||||
|
@ -80,18 +80,18 @@ var ErrVersion = errors.New("version requested by user")
|
|||
var mustParseExit = os.Exit
|
||||
var mustParseOut io.Writer = os.Stdout
|
||||
|
||||
// This stores the args sent from modules
|
||||
var register []interface{}
|
||||
|
||||
/*
|
||||
This allows you to have common arg values defined in a GO package
|
||||
Use this in your packages to register
|
||||
variables with go-arg. Then add this to your init()
|
||||
|
||||
package 'foo'
|
||||
function init() {
|
||||
args.Register(&argsFoo)
|
||||
}
|
||||
*/
|
||||
|
||||
// This stores the args sent from the GO packages
|
||||
var register []interface{}
|
||||
|
||||
func Register(dest ...interface{}) {
|
||||
register = append(register, dest...)
|
||||
}
|
||||
|
@ -102,6 +102,7 @@ func MustParse(dest ...interface{}) *Parser {
|
|||
return mustParse(Config{Exit: mustParseExit, Out: mustParseOut}, register...)
|
||||
}
|
||||
|
||||
|
||||
// mustParse is a helper that facilitates testing
|
||||
func mustParse(config Config, dest ...interface{}) *Parser {
|
||||
p, err := NewParser(config, dest...)
|
||||
|
@ -302,13 +303,6 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Set the parent of the subcommands to be the top-level command
|
||||
// to make sure that global options work when there is more than one
|
||||
// dest supplied.
|
||||
for _, subcommand := range p.cmd.subcommands {
|
||||
subcommand.parent = p.cmd
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
|
@ -717,7 +711,7 @@ func (p *Parser) process(args []string) error {
|
|||
if spec.cardinality == multiple {
|
||||
var values []string
|
||||
if value == "" {
|
||||
for i+1 < len(args) && isValue(args[i+1], spec.field.Type, specs) && args[i+1] != "--" {
|
||||
for i+1 < len(args) && !isFlag(args[i+1]) && args[i+1] != "--" {
|
||||
values = append(values, args[i+1])
|
||||
i++
|
||||
if spec.separate {
|
||||
|
@ -745,7 +739,7 @@ func (p *Parser) process(args []string) error {
|
|||
if i+1 == len(args) {
|
||||
return fmt.Errorf("missing value for %s", arg)
|
||||
}
|
||||
if !isValue(args[i+1], spec.field.Type, specs) {
|
||||
if !nextIsNumeric(spec.field.Type, args[i+1]) && isFlag(args[i+1]) {
|
||||
return fmt.Errorf("missing value for %s", arg)
|
||||
}
|
||||
value = args[i+1]
|
||||
|
@ -770,13 +764,13 @@ func (p *Parser) process(args []string) error {
|
|||
if spec.cardinality == multiple {
|
||||
err := setSliceOrMap(p.val(spec.dest), positionals, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error processing %s: %v", spec.placeholder, err)
|
||||
return fmt.Errorf("error processing %s: %v", spec.field.Name, err)
|
||||
}
|
||||
positionals = nil
|
||||
} else {
|
||||
err := scalar.ParseValue(p.val(spec.dest), positionals[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error processing %s: %v", spec.placeholder, err)
|
||||
return fmt.Errorf("error processing %s: %v", spec.field.Name, err)
|
||||
}
|
||||
positionals = positionals[1:]
|
||||
}
|
||||
|
@ -791,13 +785,18 @@ func (p *Parser) process(args []string) error {
|
|||
continue
|
||||
}
|
||||
|
||||
name := strings.ToLower(spec.field.Name)
|
||||
if spec.long != "" && !spec.positional {
|
||||
name = "--" + spec.long
|
||||
}
|
||||
|
||||
if spec.required {
|
||||
if spec.short == "" && spec.long == "" {
|
||||
msg := fmt.Sprintf("environment variable %s is required", spec.env)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("%s is required", spec.placeholder)
|
||||
msg := fmt.Sprintf("%s is required", name)
|
||||
if spec.env != "" {
|
||||
msg += " (or environment variable " + spec.env + ")"
|
||||
}
|
||||
|
@ -818,29 +817,22 @@ func (p *Parser) process(args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// isFlag returns true if a token is a flag such as "-v" or "--user" but not "-" or "--"
|
||||
func isFlag(s string) bool {
|
||||
return strings.HasPrefix(s, "-") && strings.TrimLeft(s, "-") != ""
|
||||
}
|
||||
|
||||
// isValue returns true if a token should be consumed as a value for a flag of type t. This
|
||||
// is almost always the inverse of isFlag. The one exception is for negative numbers, in which
|
||||
// case we check the list of active options and return true if its not present there.
|
||||
func isValue(s string, t reflect.Type, specs []*spec) bool {
|
||||
func nextIsNumeric(t reflect.Type, s string) bool {
|
||||
switch t.Kind() {
|
||||
case reflect.Ptr, reflect.Slice:
|
||||
return isValue(s, t.Elem(), specs)
|
||||
case reflect.Ptr:
|
||||
return nextIsNumeric(t.Elem(), s)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
v := reflect.New(t)
|
||||
err := scalar.ParseValue(v, s)
|
||||
// if value can be parsed and is not an explicit option declared elsewhere, then use it as a value
|
||||
if err == nil && (!strings.HasPrefix(s, "-") || findOption(specs, strings.TrimPrefix(s, "-")) == nil) {
|
||||
return true
|
||||
}
|
||||
return err == nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// default case that is used in all cases other than negative numbers: inverse of isFlag
|
||||
return !isFlag(s)
|
||||
// isFlag returns true if a token is a flag such as "-v" or "--user" but not "-" or "--"
|
||||
func isFlag(s string) bool {
|
||||
return strings.HasPrefix(s, "-") && strings.TrimLeft(s, "-") != ""
|
||||
}
|
||||
|
||||
// val returns a reflect.Value corresponding to the current value for the
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
scalar "github.com/alexflint/go-scalar"
|
||||
"go.wit.com/dev/alexflint/scalar"
|
||||
)
|
||||
|
||||
var textUnmarshalerType = reflect.TypeOf([]encoding.TextUnmarshaler{}).Elem()
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
|
||||
scalar "github.com/alexflint/go-scalar"
|
||||
"go.wit.com/dev/alexflint/scalar"
|
||||
)
|
||||
|
||||
// setSliceOrMap parses a sequence of strings into a slice or map. If clear is
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
all:
|
||||
@echo
|
||||
@echo
|
||||
|
||||
test:
|
||||
|
||||
redomod:
|
||||
rm -f go.*
|
||||
GO111MODULE= go mod init
|
||||
GO111MODULE= go mod tidy
|
|
@ -0,0 +1,11 @@
|
|||
module go.wit.com/dev/alexflint/arg/test
|
||||
|
||||
go 1.21.4
|
||||
|
||||
require github.com/stretchr/testify v1.8.4
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/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/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -120,91 +120,17 @@ func TestNegativeInt(t *testing.T) {
|
|||
assert.EqualValues(t, args.Foo, -100)
|
||||
}
|
||||
|
||||
func TestNegativeFloat(t *testing.T) {
|
||||
var args struct {
|
||||
Foo float64
|
||||
}
|
||||
err := parse("-foo -99", &args)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, args.Foo, -99)
|
||||
}
|
||||
|
||||
func TestNumericFlag(t *testing.T) {
|
||||
var args struct {
|
||||
UseIPv6 bool `arg:"-6"`
|
||||
Foo int
|
||||
}
|
||||
err := parse("-6", &args)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, args.UseIPv6, true)
|
||||
}
|
||||
|
||||
func TestNumericFlagTakesPrecedence(t *testing.T) {
|
||||
var args struct {
|
||||
UseIPv6 bool `arg:"-6"`
|
||||
Foo int
|
||||
}
|
||||
err := parse("-foo -6", &args)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRepeatedNegativeInts(t *testing.T) {
|
||||
var args struct {
|
||||
Ints []int `arg:"--numbers"`
|
||||
}
|
||||
err := parse("--numbers -1 -2 -6", &args)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, args.Ints, []int{-1, -2, -6})
|
||||
}
|
||||
|
||||
func TestRepeatedNegativeFloats(t *testing.T) {
|
||||
var args struct {
|
||||
Floats []float32 `arg:"--numbers"`
|
||||
}
|
||||
err := parse("--numbers -1 -2 -6", &args)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, args.Floats, []float32{-1, -2, -6})
|
||||
}
|
||||
|
||||
func TestRepeatedNegativeFloatsThenNumericFlag(t *testing.T) {
|
||||
var args struct {
|
||||
Floats []float32 `arg:"--numbers"`
|
||||
UseIPv6 bool `arg:"-6"`
|
||||
}
|
||||
err := parse("--numbers -1 -2 -6", &args)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, args.Floats, []float32{-1, -2})
|
||||
assert.True(t, args.UseIPv6)
|
||||
}
|
||||
|
||||
func TestRepeatedNegativeFloatsThenNonexistentFlag(t *testing.T) {
|
||||
var args struct {
|
||||
Floats []float32 `arg:"--numbers"`
|
||||
UseIPv6 bool `arg:"-6"`
|
||||
}
|
||||
err := parse("--numbers -1 -2 -n", &args)
|
||||
require.Error(t, err, "unknown argument -n")
|
||||
}
|
||||
|
||||
func TestRepeatedNegativeIntsThenFloat(t *testing.T) {
|
||||
var args struct {
|
||||
Ints []int `arg:"--numbers"`
|
||||
}
|
||||
err := parse("--numbers -1 -2 -0.1", &args)
|
||||
require.Error(t, err, "unknown argument -0.1")
|
||||
}
|
||||
|
||||
func TestNegativeIntAndFloatAndTricks(t *testing.T) {
|
||||
var args struct {
|
||||
Foo int
|
||||
Bar float64
|
||||
N int `arg:"--100"`
|
||||
}
|
||||
err := parse("-foo -99 -bar -60.14 -100 -101", &args)
|
||||
err := parse("-foo -100 -bar -60.14 -100 -100", &args)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, args.Foo, -99)
|
||||
assert.EqualValues(t, args.Foo, -100)
|
||||
assert.EqualValues(t, args.Bar, -60.14)
|
||||
assert.EqualValues(t, args.N, -101)
|
||||
assert.EqualValues(t, args.N, -100)
|
||||
}
|
||||
|
||||
func TestUint(t *testing.T) {
|
||||
|
@ -599,6 +525,15 @@ func TestMissingValueInMiddle(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNegativeValue(t *testing.T) {
|
||||
var args struct {
|
||||
Foo int
|
||||
}
|
||||
err := parse("--foo -123", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, -123, args.Foo)
|
||||
}
|
||||
|
||||
func TestInvalidInt(t *testing.T) {
|
||||
var args struct {
|
||||
Foo int
|
Loading…
Reference in New Issue