New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Initial implementation of positional arguments #102
Changes from 5 commits
70c9186
3d0a35c
7c3906d
1e3af52
24b09dd
b53ab63
10d85f1
c62e945
1e2e679
c1c45f5
f965350
c2b22b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2751,3 +2751,253 @@ func TestCommandHelpSetSnameOnly(t *testing.T) { | |
t.Error("Help arugment names should have defaulted") | ||
} | ||
} | ||
|
||
func TestCommandPositional(t *testing.T) { | ||
testArgs1 := []string{"pos", "heyo"} | ||
parser := NewParser("pos", "") | ||
strval := parser.String("s", "string", &Options{Positional: true}) | ||
|
||
if err := parser.Parse(testArgs1); err != nil { | ||
t.Error(err.Error()) | ||
} else if *strval != "heyo" { | ||
t.Errorf("Strval did not match expected") | ||
} | ||
} | ||
|
||
func TestCommandPositionalErr(t *testing.T) { | ||
errArgs1 := []string{"pos"} | ||
parser := NewParser("pos", "") | ||
strval := parser.String("s", "beep", &Options{Positional: true}) | ||
|
||
if err := parser.Parse(errArgs1); err == nil { | ||
t.Errorf("Positional required") | ||
} else if err.Error() != "[beep] is required" { | ||
// This is an admittedly slightly confusing error message | ||
// but it basically means the parser bailed because the arg list | ||
// is empty...? | ||
t.Error(err.Error()) | ||
} else if *strval != "" { | ||
t.Errorf("Strval nonempty") | ||
} | ||
} | ||
|
||
func TestCommandPositionals(t *testing.T) { | ||
testArgs1 := []string{"posint", "5", "abc", "1.0"} | ||
parser := NewParser("posint", "") | ||
intval := parser.Int("i", "integer", &Options{Positional: true}) | ||
strval := parser.String("s", "string", &Options{Positional: true}) | ||
floatval := parser.Float("f", "floats", &Options{Positional: true}) | ||
|
||
if err := parser.Parse(testArgs1); err != nil { | ||
t.Error(err.Error()) | ||
} else if *intval != 5 { | ||
t.Error("Intval did not match expected") | ||
} else if *strval != "abc" { | ||
t.Error("Strval did not match expected") | ||
} else if *floatval != 1.0 { | ||
t.Error("Floatval did not match expected") | ||
} | ||
} | ||
|
||
func TestCommandPositionalsErr(t *testing.T) { | ||
errArgs1 := []string{"posint", "abc", "abc", "1.0"} | ||
parser := NewParser("posint", "") | ||
_ = parser.Int("i", "cool", &Options{Positional: true}) | ||
_ = parser.String("s", "string", &Options{Positional: true}) | ||
_ = parser.Float("f", "floats", &Options{Positional: true}) | ||
|
||
if err := parser.Parse(errArgs1); err == nil { | ||
t.Error("String argument accepted for integer") | ||
} else if err.Error() != "[cool] bad integer value [abc]" { | ||
t.Error(err.Error()) | ||
} | ||
} | ||
|
||
func TestPos1(t *testing.T) { | ||
testArgs1 := []string{"pos", "subcommand1", "-i", "2", "abc"} | ||
parser := NewParser("pos", "") | ||
|
||
strval := parser.String("s", "string", &Options{Positional: true}) | ||
com1 := parser.NewCommand("subcommand1", "beep") | ||
intval := com1.Int("i", "integer", nil) | ||
|
||
if err := parser.Parse(testArgs1); err != nil { | ||
t.Error(err.Error()) | ||
} else if *strval != "abc" { | ||
t.Error("Strval did not match expected") | ||
} else if *intval != 2 { | ||
t.Error("intval did not match expected") | ||
} | ||
} | ||
|
||
func TestPos2(t *testing.T) { | ||
testArgs1 := []string{"pos", "subcommand1", "a123"} | ||
parser := NewParser("pos", "") | ||
|
||
strval := parser.String("s", "string", &Options{Positional: true}) | ||
com1 := parser.NewCommand("subcommand1", "beep") | ||
intval := com1.Int("i", "integer", nil) | ||
|
||
if err := parser.Parse(testArgs1); err != nil { | ||
t.Error(err.Error()) | ||
} else if *strval != "a123" { | ||
t.Error("Strval did not match expected") | ||
} else if *intval != 0 { | ||
t.Error("intval did not match expected") | ||
} | ||
} | ||
|
||
func TestPos3(t *testing.T) { | ||
testArgs1 := []string{"pos", "subcommand1", "xyz", "--integer", "3"} | ||
parser := NewParser("pos", "") | ||
|
||
strval := parser.String("s", "string", &Options{Positional: true}) | ||
com1 := parser.NewCommand("subcommand1", "beep") | ||
intval := com1.Int("i", "integer", nil) | ||
|
||
if err := parser.Parse(testArgs1); err != nil { | ||
t.Error(err.Error()) | ||
} else if *strval != "xyz" { | ||
t.Error("Strval did not match expected") | ||
} else if *intval != 3 { | ||
t.Error("intval did not match expected") | ||
} | ||
} | ||
|
||
func TestPos4(t *testing.T) { | ||
testArgs1 := []string{"pos", "abc"} | ||
parser := NewParser("pos", "") | ||
|
||
strval := parser.String("s", "string", &Options{Positional: true}) | ||
com1 := parser.NewCommand("subcommand1", "beep") | ||
intval := com1.Int("i", "integer", nil) | ||
|
||
if err := parser.Parse(testArgs1); err != nil && | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In its current form this test will give false result (test Pass) if there is an error, but error message is different. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should fail in that case, I don't see how the current if statement could cause that problem. However I did realize it will pass if the error is nil which is definitely incorrect. Fixed that. |
||
err.Error() != "[sub]Command required" { | ||
t.Error(err.Error()) | ||
} else if *strval != "" { | ||
t.Error("Strval did not match expected") | ||
} else if *intval != 0 { | ||
t.Error("intval did not match expected") | ||
} | ||
} | ||
|
||
func TestPos5(t *testing.T) { | ||
errStr := "unable to add Flag: argument type cannot be positional" | ||
parser := NewParser("pos", "") | ||
var boolval *bool | ||
// Catch the panic | ||
defer func() { | ||
err := recover() | ||
if err.(error).Error() != errStr { | ||
t.Error(err.(error).Error()) | ||
} else if boolval != nil { | ||
t.Error("Boolval was set") | ||
} | ||
}() | ||
boolval = parser.Flag("", "booly", &Options{Positional: true}) | ||
} | ||
|
||
func TestPos6(t *testing.T) { | ||
testArgs1 := []string{"pos", "subcommand1", "-i=2", "abc"} | ||
parser := NewParser("pos", "") | ||
|
||
strval := parser.String("s", "string", &Options{Positional: true}) | ||
com1 := parser.NewCommand("subcommand1", "beep") | ||
intval := com1.Int("i", "integer", nil) | ||
|
||
if err := parser.Parse(testArgs1); err != nil { | ||
t.Error(err.Error()) | ||
vsachs marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} else if *strval != "abc" { | ||
t.Error("Strval did not match expected") | ||
} else if *intval != 2 { | ||
t.Error("intval did not match expected") | ||
} | ||
} | ||
|
||
func TestPosErr1(t *testing.T) { | ||
errArgs1 := []string{"pos", "subcommand1"} | ||
parser := NewParser("pos", "") | ||
|
||
strval := parser.String("s", "posy", &Options{Positional: true, Default: "abc"}) | ||
com1 := parser.NewCommand("subcommand1", "beep") | ||
intval := com1.Int("i", "integer", nil) | ||
|
||
if err := parser.Parse(errArgs1); err == nil { | ||
t.Error("Subcommand should be required") | ||
} else if err.Error() != "[posy] is required" { | ||
t.Error(err.Error()) | ||
} else if *strval != "" { | ||
t.Error("strval incorrectly defaulted:" + *strval) | ||
} else if *intval != 0 { | ||
t.Error("intval did not match expected") | ||
} | ||
} | ||
|
||
func TestCommandSubcommandPositionals(t *testing.T) { | ||
testArgs1 := []string{"pos", "subcommand2", "efg"} | ||
testArgs2 := []string{"pos", "subcommand1"} | ||
testArgs3 := []string{"pos", "subcommand2", "abc", "-i", "1"} | ||
testArgs4 := []string{"pos", "subcommand2", "abc", "--integer", "1"} | ||
testArgs5 := []string{"pos", "subcommand2", "abc", "-i=1"} | ||
testArgs6 := []string{"pos", "subcommand2", "abc", "--integer=1"} | ||
// flags before positional must use `=` for values | ||
testArgs7 := []string{"pos", "subcommand2", "-i=1", "abc"} | ||
testArgs8 := []string{"pos", "subcommand2", "--integer=1", "abc"} | ||
testArgs9 := []string{"pos", "subcommand3", "second"} | ||
// Error cases | ||
errArgs1 := []string{"pos", "subcommand2", "-i", "1"} | ||
errArgs2 := []string{"pos", "subcommand2", "-i", "1", "abc"} | ||
errArgs3 := []string{"pos", "subcommand3", "abc"} | ||
|
||
newParser := func() *Parser { | ||
parser := NewParser("pos", "") | ||
_ = parser.NewCommand("subcommand1", "") | ||
com2 := parser.NewCommand("subcommand2", "") | ||
com2.String("s", "string", &Options{Positional: true}) | ||
com2.Int("i", "integer", nil) | ||
com2.Flag("b", "bool", nil) | ||
com3 := parser.NewCommand("subcommand3", "") | ||
com3.Selector("", "select", []string{"first", "second"}, | ||
&Options{Positional: true}) | ||
return parser | ||
} | ||
|
||
if err := newParser().Parse(testArgs1); err != nil { | ||
t.Error(err.Error()) | ||
} | ||
if err := newParser().Parse(testArgs2); err != nil { | ||
t.Error(err.Error()) | ||
} | ||
if err := newParser().Parse(testArgs3); err != nil { | ||
t.Error(err.Error()) | ||
} | ||
if err := newParser().Parse(testArgs4); err != nil { | ||
t.Error(err.Error()) | ||
} | ||
if err := newParser().Parse(testArgs5); err != nil { | ||
t.Error(err.Error()) | ||
} | ||
if err := newParser().Parse(testArgs6); err != nil { | ||
t.Error(err.Error()) | ||
} | ||
if err := newParser().Parse(testArgs7); err != nil { | ||
t.Error(err.Error()) | ||
} | ||
if err := newParser().Parse(testArgs8); err != nil { | ||
t.Error(err.Error()) | ||
} | ||
if err := newParser().Parse(testArgs9); err != nil { | ||
t.Error(err.Error()) | ||
} | ||
|
||
if err := newParser().Parse(errArgs1); err == nil { | ||
t.Error("Expected error") | ||
} | ||
if err := newParser().Parse(errArgs2); err == nil { | ||
t.Error("Expected error") | ||
} | ||
if err := newParser().Parse(errArgs3); err == nil { | ||
t.Error("Expected error") | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, interesting, while
Default == nil
makes sense (positional either provided or it is not), I am not so sure aboutRequired == true
.Assuming for a sub-command tree we defined 5 positionals:
progname cmd1 cmd2 prognamePositional1 prognamePositional2 cmd1Positional1 cmd1Positional2 cmd2Positional
, and input provided on CLIprogname cmd1 cmd2 somestr1 somestr2 somestr3
, the only way we can match positional arguments is in the order they provided. Hence:prognamePositional1 == somestr1
prognamePositional2 == somestr2
cmd1Positional1 == somestr3
cmd1Positional2 == nil
cmd2Positional == nil
Which would make it possible to define "optional" positionals, as in -- if it was not provided, it is not set to any value, meantime any that provided will be matched to their corresponding positional in exactly same order (that is in example above
prognamePositional1
should always be matched tosomestr1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe I can allow this relatively easily but it's going to open a can of worms. What if the only optional positionals are actually prognamePositional1 and prognamePositional2? We can't distinguish which positionals the user is trying to fill (unless we actually allow naming of the positionals on the CLI, which... basically defeats the whole point), so we will still end up with the same result above except that the last two are required and nil, giving us an error. Effectively they are all required in this scenario.
Do we want to require that any optional positionals be at the tail end of added arguments? If so this has the implication: