add new slice type parser, add help flag
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Jay 2020-02-12 14:55:03 +00:00
parent c1f8fa376a
commit b619f2f11e
5 changed files with 685 additions and 41 deletions

View File

@ -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)
}

View File

@ -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)
})
}
}

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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,
)
}