Skip to content

Commit

Permalink
Merge 03b1b48 into 1427fe6
Browse files Browse the repository at this point in the history
  • Loading branch information
goofinator committed Dec 3, 2019
2 parents 1427fe6 + 03b1b48 commit 275c1a8
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 29 deletions.
26 changes: 25 additions & 1 deletion argparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func (o *Command) NewCommand(name string, description string) *Command {
// Long name is required.
// Returns pointer to boolean with starting value `false`. If Parser finds the flag
// provided on Command line arguments, then the value is changed to true.
// Only for Flag shorthand arguments can be combined together such as `rm -rf`
// Set of Flag and FlagCounter shorthand arguments can be combined together such as `tar -cvaf foo.tar foo`
func (o *Command) Flag(short string, long string, opts *Options) *bool {
var result bool

Expand All @@ -164,6 +164,30 @@ func (o *Command) Flag(short string, long string, opts *Options) *bool {
return &result
}

// FlagCounter Creates new flagCounter type of argument, which is integer value showing the number of times the argument has been provided.
// Takes short name, long name and pointer to options (optional).
// Short name must be single character, but can be omitted by giving empty string.
// Long name is required.
// Returns pointer to integer with starting value `0`. Each time Parser finds the flag
// provided on Command line arguments, the value is incremented by 1.
// Set of FlagCounter and Flag shorthand arguments can be combined together such as `tar -cvaf foo.tar foo`
func (o *Command) FlagCounter(short string, long string, opts *Options) *int {
var result int

a := &arg{
result: &result,
sname: short,
lname: long,
size: 1,
opts: opts,
unique: false,
}

o.addArg(a)

return &result
}

// String creates new string argument, which will return whatever follows the argument on CLI.
// Takes as arguments short name (must be single character or an empty string)
// long name and (optional) options
Expand Down
156 changes: 156 additions & 0 deletions argparse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,138 @@ func TestFlagMultiShorthand1(t *testing.T) {
}
}

func TestFlagCounterSimple1(t *testing.T) {
testArgs := []string{"progname", "--flag-arg1", "--flag-arg3", "--flag-arg3"}

p := NewParser("", "description")
flag1 := p.FlagCounter("", "flag-arg1", nil)
flag2 := p.FlagCounter("", "flag-arg2", nil)
flag3 := p.FlagCounter("", "flag-arg3", nil)

err := p.Parse(testArgs)
if err != nil {
t.Errorf("Test %s failed with error: %s", t.Name(), err.Error())
return
}

if flag1 == nil {
t.Errorf("Test %s failed with flag1 being nil pointer", t.Name())
return
}

if flag2 == nil {
t.Errorf("Test %s failed with flag2 being nil pointer", t.Name())
return
}

if flag3 == nil {
t.Errorf("Test %s failed with flag3 being nil pointer", t.Name())
return
}

if *flag1 != 1 {
t.Errorf("Test %s failed with flag1 being %d", t.Name(), *flag1)
return
}

if *flag2 != 0 {
t.Errorf("Test %s failed with flag2 being %d", t.Name(), *flag2)
return
}

if *flag3 != 2 {
t.Errorf("Test %s failed with flag3 being %d", t.Name(), *flag3)
return
}
}

func TestFlagCounterSimple2(t *testing.T) {
testArgs := []string{"progname", "--flag-arg1", "-f", "--flag-arg3", "-f"}

p := NewParser("", "description")
flag1 := p.FlagCounter("", "flag-arg1", nil)
flag2 := p.FlagCounter("", "flag-arg2", nil)
flag3 := p.FlagCounter("f", "flag-arg3", nil)

err := p.Parse(testArgs)
if err != nil {
t.Errorf("Test %s failed with error: %s", t.Name(), err.Error())
return
}

if flag1 == nil {
t.Errorf("Test %s failed with flag1 being nil pointer", t.Name())
return
}

if flag2 == nil {
t.Errorf("Test %s failed with flag2 being nil pointer", t.Name())
return
}

if flag3 == nil {
t.Errorf("Test %s failed with flag3 being nil pointer", t.Name())
return
}

if *flag1 != 1 {
t.Errorf("Test %s failed with flag1 being %d", t.Name(), *flag1)
return
}

if *flag2 != 0 {
t.Errorf("Test %s failed with flag2 being %d", t.Name(), *flag2)
return
}

if *flag3 != 3 {
t.Errorf("Test %s failed with flag3 being %d", t.Name(), *flag3)
return
}
}

func TestFlagCounterMultiShorthand1(t *testing.T) {
testArgs := []string{"progname", "-abbcbcadaa", "-e"}

p := NewParser("", "description")
flag1 := p.FlagCounter("a", "aa", nil)
flag2 := p.FlagCounter("b", "bb", nil)
flag3 := p.FlagCounter("c", "cc", nil)
flag4 := p.FlagCounter("d", "dd", nil)
flag5 := p.FlagCounter("e", "ee", nil)
flag6 := p.FlagCounter("f", "ff", nil)

err := p.Parse(testArgs)
if err != nil {
t.Errorf("Test %s failed with error: %s", t.Name(), err.Error())
return
}

if *flag1 != 4 {
t.Errorf("Test %s failed with flag1 being %d", t.Name(), *flag1)
}

if *flag2 != 3 {
t.Errorf("Test %s failed with flag2 being %d", t.Name(), *flag2)
}

if *flag3 != 2 {
t.Errorf("Test %s failed with flag3 being %d", t.Name(), *flag3)
}

if *flag4 != 1 {
t.Errorf("Test %s failed with flag4 being %d", t.Name(), *flag4)
}

if *flag5 != 1 {
t.Errorf("Test %s failed with flag5 being %d", t.Name(), *flag5)
}

if *flag6 != 0 {
t.Errorf("Test %s failed with flag6 being %d", t.Name(), *flag6)
}
}

func TestFailDuplicate(t *testing.T) {
testArgs := []string{"progname", "--flag-arg1", "-f"}

Expand All @@ -141,6 +273,30 @@ func TestFailDuplicate(t *testing.T) {
t.Errorf("Test %s failed with. Duplicate flag use not detected", t.Name())
return
}

testArgs = []string{"progname", "--flag-arg2", "-ff"}

p = NewParser("", "description")
_ = p.Flag("f", "flag-arg1", nil)
_ = p.Flag("", "flag-arg2", nil)

err = p.Parse(testArgs)
if err == nil {
t.Errorf("Test %s failed with. Duplicate flag use not detected", t.Name())
return
}

testArgs = []string{"progname", "--flag-arg2", "-f"}

p = NewParser("", "description")
_ = p.Flag("f", "flag-arg1", nil)
_ = p.Flag("", "flag-arg2", nil)

err = p.Parse(testArgs)
if err != nil {
t.Errorf("Test %s failed with. Fake duplicate flag detected", t.Name())
return
}
}

func TestFailCaseSensitive(t *testing.T) {
Expand Down
64 changes: 38 additions & 26 deletions argument.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ func (o arg) GetLname() string {

type help struct{}

func (o *arg) check(argument string) bool {
//Check if argumet present.
//For args with size 1 (Flag,FlagCounter) multiple shorthand in one argument are allowed,
//so check - returns the number of occurrences.
//For other args check - returns 1 if occured or 0 in no
func (o *arg) check(argument string) int {
// Shortcut to showing help
if argument == "-h" || argument == "--help" {
helpText := o.parent.Help(nil)
Expand All @@ -55,30 +59,27 @@ func (o *arg) check(argument string) bool {
// If argument begins with "--" and next is not "-" then it is a long name
if len(argument) > 2 && strings.HasPrefix(argument, "--") && argument[2] != '-' {
if argument[2:] == o.lname {
return true
return 1
}
}
}
// Check for short name only if not empty
if o.sname != "" {
// If argument begins with "-" and next is not "-" then it is a short name
if len(argument) > 1 && strings.HasPrefix(argument, "-") && argument[1] != '-' {
switch o.result.(type) {
case *bool:
// For flags we allow multiple shorthand in one
if strings.Contains(argument[1:], o.sname) {
return true
}
default:
// For args with size 1 (Flag,FlagCounter) multiple shorthand in one argument are allowed
if o.size == 1 {
return strings.Count(argument[1:], o.sname)
// For all other types it must be separate argument
} else {
if argument[1:] == o.sname {
return true
return 1
}
}
}
}

return false
return 0
}

func (o *arg) reduce(position int, args *[]string) {
Expand All @@ -98,17 +99,16 @@ func (o *arg) reduce(position int, args *[]string) {
if o.sname != "" {
// If argument begins with "-" and next is not "-" then it is a short name
if len(argument) > 1 && strings.HasPrefix(argument, "-") && argument[1] != '-' {
switch o.result.(type) {
case *bool:
// For flags we allow multiple shorthand in one
// For args with size 1 (Flag,FlagCounter) we allow multiple shorthand in one
if o.size == 1 {
if strings.Contains(argument[1:], o.sname) {
(*args)[position] = strings.Replace(argument, o.sname, "", -1)
if (*args)[position] == "-" {
(*args)[position] = ""
}
}
default:
// For all other types it must be separate argument
} else {
if argument[1:] == o.sname {
for i := position; i < position+o.size; i++ {
(*args)[i] = ""
Expand All @@ -119,9 +119,9 @@ func (o *arg) reduce(position int, args *[]string) {
}
}

func (o *arg) parse(args []string) error {
func (o *arg) parse(args []string, argCount int) error {
// If unique do not allow more than one time
if o.unique && o.parsed {
if o.unique && (o.parsed || argCount > 1) {
return fmt.Errorf("[%s] can only be present once", o.name())
}

Expand All @@ -138,22 +138,31 @@ func (o *arg) parse(args []string) error {
helpText := o.parent.Help(nil)
fmt.Print(helpText)
os.Exit(0)
//data of bool type is for Flag argument
case *bool:
*o.result.(*bool) = true
o.parsed = true
//data of integer type is for
case *int:
if len(args) < 1 {
return fmt.Errorf("[%s] must be followed by an integer", o.name())
}
if len(args) > 1 {
switch {
//FlagCounter argument
case len(args) < 1:
if o.size > 1 {
return fmt.Errorf("[%s] must be followed by an integer", o.name())
}
*o.result.(*int) += argCount
case len(args) > 1:
return fmt.Errorf("[%s] followed by too many arguments", o.name())
//or Int argument with one integer parameter
default:
val, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("[%s] bad interger value [%s]", o.name(), args[0])
}
*o.result.(*int) = val
}
val, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("[%s] bad interger value [%s]", o.name(), args[0])
}
*o.result.(*int) = val
o.parsed = true
//data of float64 type is for Float argument with one float parameter
case *float64:
if len(args) < 1 {
return fmt.Errorf("[%s] must be followed by a floating point number", o.name())
Expand All @@ -167,6 +176,7 @@ func (o *arg) parse(args []string) error {
}
*o.result.(*float64) = val
o.parsed = true
//data of string type is for String argument with one string parameter
case *string:
if len(args) < 1 {
return fmt.Errorf("[%s] must be followed by a string", o.name())
Expand All @@ -188,6 +198,7 @@ func (o *arg) parse(args []string) error {
}
*o.result.(*string) = args[0]
o.parsed = true
//data of os.File type is for File argument with one file name parameter
case *os.File:
if len(args) < 1 {
return fmt.Errorf("[%s] must be followed by a path to file", o.name())
Expand All @@ -201,6 +212,7 @@ func (o *arg) parse(args []string) error {
}
*o.result.(*os.File) = *f
o.parsed = true
//data of []string type is for List argument with set of string parameters
case *[]string:
if len(args) < 1 {
return fmt.Errorf("[%s] must be followed by a string", o.name())
Expand Down
4 changes: 2 additions & 2 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ func (o *Command) parse(args *[]string) error {
if arg == "" {
continue
}
if oarg.check(arg) {
if cnt := oarg.check(arg); cnt > 0 {
if len(*args) < j+oarg.size {
return fmt.Errorf("not enough arguments for %s", oarg.name())
}
err := oarg.parse((*args)[j+1 : j+oarg.size])
err := oarg.parse((*args)[j+1:j+oarg.size], cnt)
if err != nil {
return err
}
Expand Down

0 comments on commit 275c1a8

Please sign in to comment.