From b619f2f11ee324e8dc27364dc8d41147fedc3375 Mon Sep 17 00:00:00 2001 From: Jay Date: Wed, 12 Feb 2020 14:55:03 +0000 Subject: [PATCH] add new slice type parser, add help flag --- argparse.go | 112 +++++++++++++++---- argparse_test.go | 285 +++++++++++++++++++++++++++++++++++++++++++++-- argument.go | 76 ++++++++++++- argument_test.go | 217 +++++++++++++++++++++++++++++++++++- example/main.go | 36 ++++-- 5 files changed, 685 insertions(+), 41 deletions(-) diff --git a/argparse.go b/argparse.go index eb8751d..a35e284 100644 --- a/argparse.go +++ b/argparse.go @@ -3,22 +3,30 @@ package argparse import ( "errors" "fmt" + "os" "reflect" + "strings" ) type Parser struct { - args []*arg - parsed bool + args []*arg + showHelp bool + parsed bool +} + +type Option struct { + Require bool } type arg struct { sname string lname string - // value number count + // value item count size int value interface{} unique bool parsed bool + opts *Option } func New() *Parser { @@ -29,7 +37,7 @@ func New() *Parser { func (p *Parser) addArg(a *arg) error { for _, v := range p.args { - if (v.sname == a.sname || v.lname == a.lname) && (a.unique || v.unique) { + if v.sname == a.sname || v.lname == a.lname { return errors.New("option name dup") } } @@ -38,11 +46,14 @@ func (p *Parser) addArg(a *arg) error { return nil } -func (p *Parser) typeVar(i interface{}, short, long string, size int, unique bool) { +func (p *Parser) typeVar(i interface{}, short, long string, size int, unique bool, opts *Option) { t := reflect.ValueOf(i) if t.Kind() != reflect.Ptr { panic(errors.New("var type not ptr")) } + if short == "" && long == "" { + panic(errors.New("short name and long name is empty")) + } a := &arg{ sname: short, @@ -50,6 +61,7 @@ func (p *Parser) typeVar(i interface{}, short, long string, size int, unique boo value: i, size: size, unique: unique, + opts: opts, } if err := p.addArg(a); err != nil { @@ -57,6 +69,24 @@ func (p *Parser) typeVar(i interface{}, short, long string, size int, unique boo } } +func (p *Parser) Help(short, long string) { + p.typeVar(&p.showHelp, short, long, 0, true, nil) +} + +func (p *Parser) printHelp() { + sb := &strings.Builder{} + sb.WriteString("Usage:\n") + for _, v := range p.args { + if _, err := sb.WriteString(fmt.Sprintf("\t%s\t%s\n", v.name(), v.getType())); err != nil { + panic(err) + } + } + fmt.Println(sb.String()) + // if flag.Lookup("test.v") == nil { + os.Exit(2) + // } +} + func (p *Parser) Parse(a []string) error { copyArg := make([]string, len(a)) copy(copyArg, a) @@ -84,6 +114,10 @@ func (p *Parser) parse(args *[]string) error { } p.parsed = true + + if p.showHelp == true { + p.printHelp() + } return nil } @@ -117,52 +151,88 @@ func (p *Parser) parseArguemtns(args *[]string) error { continue } } + if oarg.opts != nil && oarg.opts.Require && !oarg.parsed { + return fmt.Errorf("[%s] is required", oarg.name()) + } } return nil } -func (p *Parser) String(short, long string) *string { +func (p *Parser) String(short, long string, opts *Option) *string { var result string - p.typeVar(&result, short, long, 1, true) + p.typeVar(&result, short, long, 1, true, opts) return &result } -func (p *Parser) StringVar(i *string, short, long string) { - p.typeVar(i, short, long, 1, true) +func (p *Parser) StringVar(i *string, short, long string, opts *Option) { + p.typeVar(i, short, long, 1, true, opts) } -func (p *Parser) Int(short, long string) *int { +func (p *Parser) Int(short, long string, opts *Option) *int { var result int - p.typeVar(&result, short, long, 1, true) + p.typeVar(&result, short, long, 1, true, opts) return &result } -func (p *Parser) IntVar(i *int, short, long string) { - p.typeVar(i, short, long, 1, true) +func (p *Parser) IntVar(i *int, short, long string, opts *Option) { + p.typeVar(i, short, long, 1, true, opts) } -func (p *Parser) Bool(short, long string) *bool { +func (p *Parser) Bool(short, long string, opts *Option) *bool { var result bool - p.typeVar(&result, short, long, 0, true) + p.typeVar(&result, short, long, 0, true, opts) return &result } -func (p *Parser) BoolVar(i *bool, short, long string) { - p.typeVar(i, short, long, 0, true) +func (p *Parser) BoolVar(i *bool, short, long string, opts *Option) { + p.typeVar(i, short, long, 0, true, opts) } -func (p *Parser) Float(short, long string) *float64 { +func (p *Parser) Float(short, long string, opts *Option) *float64 { var result float64 - p.typeVar(&result, short, long, 1, true) + p.typeVar(&result, short, long, 1, true, opts) return &result } -func (p *Parser) FloatVar(i *float64, short, long string) { - p.typeVar(i, short, long, 1, true) +func (p *Parser) FloatVar(i *float64, short, long string, opts *Option) { + p.typeVar(i, short, long, 1, true, opts) +} + +func (p *Parser) StringSlice(short, long string, opts *Option) *[]string { + var result []string + + p.typeVar(&result, short, long, 1, false, opts) + + return &result +} +func (p *Parser) StringSliceVar(i *[]string, short, long string, opts *Option) { + p.typeVar(i, short, long, 1, false, opts) +} + +func (p *Parser) IntSlice(short, long string, opts *Option) *[]int { + var result []int + + p.typeVar(&result, short, long, 1, false, opts) + + return &result +} +func (p *Parser) IntSliceVar(i *[]int, short, long string, opts *Option) { + p.typeVar(i, short, long, 1, false, opts) +} + +func (p *Parser) FloatSlice(short, long string, opts *Option) *[]float64 { + var result []float64 + + p.typeVar(&result, short, long, 1, false, opts) + + return &result +} +func (p *Parser) FloatSliceVar(i *[]float64, short, long string, opts *Option) { + p.typeVar(i, short, long, 1, false, opts) } diff --git a/argparse_test.go b/argparse_test.go index ce77691..2d659d0 100644 --- a/argparse_test.go +++ b/argparse_test.go @@ -99,7 +99,7 @@ func TestParser_typeVar(t *testing.T) { args: tt.fields.args, parsed: tt.fields.parsed, } - p.typeVar(tt.args.i, tt.args.short, tt.args.long, tt.args.size, tt.args.unique) + p.typeVar(tt.args.i, tt.args.short, tt.args.long, tt.args.size, tt.args.unique, nil) }) } } @@ -277,7 +277,7 @@ func TestParser_String(t *testing.T) { args: tt.fields.args, parsed: tt.fields.parsed, } - if got := p.String(tt.args.short, tt.args.long); got == nil { + if got := p.String(tt.args.short, tt.args.long, nil); got == nil { t.Errorf("Parser.String() = %v", got) } }) @@ -316,7 +316,7 @@ func TestParser_StringVar(t *testing.T) { args: tt.fields.args, parsed: tt.fields.parsed, } - p.StringVar(tt.args.i, tt.args.short, tt.args.long) + p.StringVar(tt.args.i, tt.args.short, tt.args.long, nil) }) } } @@ -350,7 +350,7 @@ func TestParser_Int(t *testing.T) { args: tt.fields.args, parsed: tt.fields.parsed, } - if got := p.Int(tt.args.short, tt.args.long); got == nil { + if got := p.Int(tt.args.short, tt.args.long, nil); got == nil { t.Errorf("Parser.Int() = %v", got) } }) @@ -389,7 +389,7 @@ func TestParser_IntVar(t *testing.T) { args: tt.fields.args, parsed: tt.fields.parsed, } - p.IntVar(tt.args.i, tt.args.short, tt.args.long) + p.IntVar(tt.args.i, tt.args.short, tt.args.long, nil) }) } } @@ -423,7 +423,7 @@ func TestParser_Bool(t *testing.T) { args: tt.fields.args, parsed: tt.fields.parsed, } - if got := p.Bool(tt.args.short, tt.args.long); got == nil { + if got := p.Bool(tt.args.short, tt.args.long, nil); got == nil { t.Errorf("Parser.Bool() = %v", got) } }) @@ -462,7 +462,7 @@ func TestParser_BoolVar(t *testing.T) { args: tt.fields.args, parsed: tt.fields.parsed, } - p.BoolVar(tt.args.i, tt.args.short, tt.args.long) + p.BoolVar(tt.args.i, tt.args.short, tt.args.long, nil) }) } } @@ -496,7 +496,7 @@ func TestParser_Float(t *testing.T) { args: tt.fields.args, parsed: tt.fields.parsed, } - if got := p.Float(tt.args.short, tt.args.long); got == nil { + if got := p.Float(tt.args.short, tt.args.long, nil); got == nil { t.Errorf("Parser.Float() = %v", got) } }) @@ -535,7 +535,274 @@ func TestParser_FloatVar(t *testing.T) { args: tt.fields.args, parsed: tt.fields.parsed, } - p.FloatVar(tt.args.i, tt.args.short, tt.args.long) + p.FloatVar(tt.args.i, tt.args.short, tt.args.long, nil) + }) + } +} + +func TestParser_StringSlice(t *testing.T) { + type fields struct { + args []*arg + parsed bool + } + type args struct { + short string + long string + opts *Option + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "test add string slice to parser", + fields: fields{}, + args: args{ + short: "d", + long: "", + opts: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Parser{ + args: tt.fields.args, + parsed: tt.fields.parsed, + } + if got := p.StringSlice(tt.args.short, tt.args.long, tt.args.opts); got == nil { + t.Errorf("Parser.StringSlice() = %v", got) + } + }) + } +} + +func TestParser_StringSliceVar(t *testing.T) { + var s []string + type fields struct { + args []*arg + parsed bool + } + type args struct { + i *[]string + short string + long string + opts *Option + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "test add string slice to parser (ptr)", + fields: fields{}, + args: args{ + i: &s, + short: "d", + long: "", + opts: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Parser{ + args: tt.fields.args, + parsed: tt.fields.parsed, + } + p.StringSliceVar(tt.args.i, tt.args.short, tt.args.long, tt.args.opts) + }) + } +} + +func TestParser_IntSlice(t *testing.T) { + type fields struct { + args []*arg + parsed bool + } + type args struct { + short string + long string + opts *Option + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "test add int slice to parser", + fields: fields{}, + args: args{ + short: "i", + long: "", + opts: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Parser{ + args: tt.fields.args, + parsed: tt.fields.parsed, + } + if got := p.IntSlice(tt.args.short, tt.args.long, tt.args.opts); got == nil { + t.Errorf("Parser.IntSlice() = %v", got) + } + }) + } +} + +func TestParser_IntSliceVar(t *testing.T) { + var i []int + type fields struct { + args []*arg + parsed bool + } + type args struct { + i *[]int + short string + long string + opts *Option + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "test add int slice to parser (ptr)", + fields: fields{}, + args: args{ + i: &i, + short: "i", + long: "", + opts: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Parser{ + args: tt.fields.args, + parsed: tt.fields.parsed, + } + p.IntSliceVar(tt.args.i, tt.args.short, tt.args.long, tt.args.opts) + }) + } +} + +func TestParser_FloatSliceVar(t *testing.T) { + var f []float64 + type fields struct { + args []*arg + parsed bool + } + type args struct { + i *[]float64 + short string + long string + opts *Option + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "test add float slice to parser (ptr)", + fields: fields{}, + args: args{ + i: &f, + short: "f", + long: "", + opts: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Parser{ + args: tt.fields.args, + parsed: tt.fields.parsed, + } + p.FloatSliceVar(tt.args.i, tt.args.short, tt.args.long, tt.args.opts) + }) + } +} + +func TestParser_FloatSlice(t *testing.T) { + type fields struct { + args []*arg + parsed bool + } + type args struct { + short string + long string + opts *Option + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "test add float slice to parser", + fields: fields{}, + args: args{ + short: "f", + long: "", + opts: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Parser{ + args: tt.fields.args, + parsed: tt.fields.parsed, + } + if got := p.FloatSlice(tt.args.short, tt.args.long, tt.args.opts); got == nil { + t.Errorf("Parser.FloatSlice() = %v", got) + } + }) + } +} + +func TestParser_Help(t *testing.T) { + type fields struct { + args []*arg + showHelp bool + parsed bool + } + type args struct { + short string + long string + } + tests := []struct { + name string + fields fields + args args + }{ + { + name: "test add help flag to parser", + fields: fields{}, + args: args{ + short: "h", + long: "", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Parser{ + args: tt.fields.args, + showHelp: tt.fields.showHelp, + parsed: tt.fields.parsed, + } + p.Help(tt.args.short, tt.args.long) }) } } diff --git a/argument.go b/argument.go index 2c9ea18..0fbb676 100644 --- a/argument.go +++ b/argument.go @@ -83,7 +83,28 @@ func (a *arg) name() string { } else if a.lname == "" { return fmt.Sprintf("-%s", a.sname) } else { - return fmt.Sprintf("-%s|--%s", a.sname, a.lname) + return fmt.Sprintf("-%s | --%s", a.sname, a.lname) + } +} + +func (a *arg) getType() string { + switch a.value.(type) { + case *string: + return "string" + case *int: + return "int" + case *bool: + return "bool" + case *float64: + return "float" + case *[]string: + return "[]string" + case *[]int: + return "[]int" + case *[]float64: + return "[]float" + default: + return "not support" } } @@ -106,8 +127,11 @@ func (a *arg) parseType(args []string) error { case *float64: return a.parseFloat(args) case *[]string: + return a.parseStringSlice(args) case *[]int: + return a.parseIntSlice(args) case *[]float64: + return a.parseFloatSlice(args) default: err = fmt.Errorf("unsupport type [%t]", a.value) } @@ -130,6 +154,17 @@ func (a *arg) parseString(args []string) error { return err } +func (a *arg) parseStringSlice(args []string) (err error) { + if len(args) == 0 { + return ErrNoArg + } + + *((a.value).(*[]string)) = append(*((a.value).(*[]string)), args...) + a.parsed = true + + return +} + func (a *arg) parseInt(args []string) error { var err error @@ -144,12 +179,30 @@ func (a *arg) parseInt(args []string) error { *((a.value).(*int)) = i a.parsed = true } else { - return fmt.Errorf("[%s] bad int value", a.name()) + return fmt.Errorf("[%s] bad int value (%v)", a.name(), args[0]) } return err } +func (a *arg) parseIntSlice(args []string) (err error) { + if len(args) == 0 { + return ErrNoArg + } + + for _, v := range args { + if i, err := strconv.Atoi(v); err == nil { + *((a.value).(*[]int)) = append(*((a.value).(*[]int)), i) + } else { + return fmt.Errorf("[%s] bad int value (%v)", a.name(), v) + } + } + + a.parsed = true + + return +} + func (a *arg) parseBool(args []string) error { *a.value.(*bool) = true a.parsed = true @@ -168,7 +221,24 @@ func (a *arg) parseFloat(args []string) (err error) { *a.value.(*float64) = f a.parsed = true } else { - return fmt.Errorf("[%s] bad float value", a.name()) + return fmt.Errorf("[%s] bad float value (%v)", a.name(), args[0]) } return } + +func (a *arg) parseFloatSlice(args []string) (err error) { + if len(args) == 0 { + return ErrNoArg + } + + for _, v := range args { + if f, err := strconv.ParseFloat(v, 64); err == nil { + *((a.value).(*[]float64)) = append(*((a.value).(*[]float64)), f) + } else { + return fmt.Errorf("[%s] bad float value (%v)", a.name(), v) + } + } + a.parsed = true + + return +} diff --git a/argument_test.go b/argument_test.go index 3129712..abc457e 100644 --- a/argument_test.go +++ b/argument_test.go @@ -1,6 +1,8 @@ package argparse -import "testing" +import ( + "testing" +) func Test_arg_check(t *testing.T) { type fields struct { @@ -698,3 +700,216 @@ func Test_arg_parseFloat(t *testing.T) { }) } } + +func Test_arg_parseStringSlice(t *testing.T) { + var s []string + type fields struct { + sname string + lname string + size int + value interface{} + unique bool + parsed bool + opts *Option + } + type args struct { + args []string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "test argument parseStringSlice", + fields: fields{ + size: 1, + value: &s, + unique: false, + }, + args: args{ + args: []string{ + "a", "b", "c", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &arg{ + sname: tt.fields.sname, + lname: tt.fields.lname, + size: tt.fields.size, + value: tt.fields.value, + unique: tt.fields.unique, + parsed: tt.fields.parsed, + opts: tt.fields.opts, + } + if err := a.parseStringSlice(tt.args.args); (err != nil) != tt.wantErr { + t.Errorf("arg.parseStringSlice() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_arg_parseIntSlice(t *testing.T) { + var i []int + type fields struct { + sname string + lname string + size int + value interface{} + unique bool + parsed bool + opts *Option + } + type args struct { + args []string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "test argument parseIntSlice", + fields: fields{ + value: &i, + size: 1, + unique: false, + }, + args: args{ + args: []string{"1", "2", "3"}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &arg{ + sname: tt.fields.sname, + lname: tt.fields.lname, + size: tt.fields.size, + value: tt.fields.value, + unique: tt.fields.unique, + parsed: tt.fields.parsed, + opts: tt.fields.opts, + } + if err := a.parseIntSlice(tt.args.args); (err != nil) != tt.wantErr { + t.Errorf("arg.parseIntSlice() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_arg_parseFloatSlice(t *testing.T) { + var f []float64 + type fields struct { + sname string + lname string + size int + value interface{} + unique bool + parsed bool + opts *Option + } + type args struct { + args []string + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "test argument parseFloatSlice", + fields: fields{ + value: &f, + size: 1, + unique: false, + }, + args: args{ + args: []string{"1.2", "2.3", "3.5"}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &arg{ + sname: tt.fields.sname, + lname: tt.fields.lname, + size: tt.fields.size, + value: tt.fields.value, + unique: tt.fields.unique, + parsed: tt.fields.parsed, + opts: tt.fields.opts, + } + if err := a.parseFloatSlice(tt.args.args); (err != nil) != tt.wantErr { + t.Errorf("arg.parseFloatSlice() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_arg_getType(t *testing.T) { + var s string + var b bool + var i int + var f float64 + type fields struct { + sname string + lname string + size int + value interface{} + unique bool + parsed bool + opts *Option + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "test get argument type 1", + fields: fields{value: &s}, + want: "string", + }, + { + name: "test get argument type 2", + fields: fields{value: &b}, + want: "bool", + }, + { + name: "test get argument type 3", + fields: fields{value: &i}, + want: "int", + }, + { + name: "test get argument type 4", + fields: fields{value: &f}, + want: "float", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := &arg{ + sname: tt.fields.sname, + lname: tt.fields.lname, + size: tt.fields.size, + value: tt.fields.value, + unique: tt.fields.unique, + parsed: tt.fields.parsed, + opts: tt.fields.opts, + } + if got := a.getType(); got != tt.want { + t.Errorf("arg.getType() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/example/main.go b/example/main.go index 834df6b..4f200f6 100644 --- a/example/main.go +++ b/example/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "log" "git.trj.tw/golang/argparse" ) @@ -14,6 +15,9 @@ func main() { "--port", "3000", "--float", "1.23", "-n", "name!!", + "-i", "item1", + "-i", "item2", + "-h", } p := argparse.New() @@ -23,15 +27,33 @@ func main() { var port int var floatVal float64 var name *string + var sarr []string + var missReq int + _ = missReq - p.BoolVar(&versionFlag, "v", "version") - p.StringVar(&configPath, "f", "config") - p.IntVar(&port, "p", "port") - p.FloatVar(&floatVal, "ff", "float") + p.BoolVar(&versionFlag, "v", "version", nil) + p.StringVar(&configPath, "f", "config", nil) + p.IntVar(&port, "p", "port", nil) + p.FloatVar(&floatVal, "ff", "float", nil) + p.StringSliceVar(&sarr, "i", "item", nil) + // uncomment to test required arg missing + // p.IntVar(&missReq, "m", "miss", &argparse.Option{Require: true}) + p.Help("h", "help") - name = p.String("n", "name") + name = p.String("n", "name", nil) - p.Parse(opts) + err := p.Parse(opts) + if err != nil { + log.Fatal(err) + } - fmt.Printf("Config: %s\nVersionFlag: %v\nPort: %d\nFloatVal: %f\nName: %s\n", configPath, versionFlag, port, floatVal, *name) + fmt.Printf( + "Config: %s\nVersionFlag: %v\nPort: %d\nFloatVal: %f\nName: %s\nItems: %v\n", + configPath, + versionFlag, + port, + floatVal, + *name, + sarr, + ) }