Skip to content
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

Add typed list #47 #48

Merged
merged 8 commits into from
Dec 6, 2019
Merged
23 changes: 23 additions & 0 deletions argparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,29 @@ func (o *Command) FloatList(short string, long string, opts *Options) *[]float64
return &result
}

// FileList creates new file list argument. This is the argument that is allowed to be present multiple times on CLI.
// All appearances of this argument on CLI will be collected into the list of os.File values. If no argument
// provided, then the list is empty. Takes same parameters as File
// Returns a pointer the list of os.File values.
func (o *Command) FileList(short string, long string, flag int, perm os.FileMode, opts *Options) *[]os.File {
result := make([]os.File, 0)

a := &arg{
result: &result,
sname: short,
lname: long,
size: 2,
opts: opts,
unique: false,
fileFlag: flag,
filePerm: perm,
}

o.addArg(a)

return &result
}

// Selector creates a selector argument. Selector argument works in the same way as String argument, with
// the difference that the string value must be from the list of options provided by the program.
// Takes short and long names, argument options and a slice of strings which are allowed values
Expand Down
116 changes: 114 additions & 2 deletions argparse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,13 +511,13 @@ func TestFileSimple1(t *testing.T) {
t.Errorf("Test %s failed with error: %s", t.Name(), err.Error())
return
}
defer file1.Close()
akamensky marked this conversation as resolved.
Show resolved Hide resolved

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please leave 1 empty line between logical blocks for better readability. Too dense code is very hard to read

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

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

defer file1.Close()

testString := "Test"
recSlice := make([]byte, 4)
_, err = file1.WriteString(testString)
Expand All @@ -537,6 +537,55 @@ func TestFileSimple1(t *testing.T) {
}
}

func TestFileListSimple1(t *testing.T) {
// Test files location
fpaths := []string{"./test1.tmp", "./test2.tmp"}
// Create test files
for _, fpath := range fpaths {
f, err := os.Create(fpath)
if err != nil {
t.Error(err)
return
}
f.Close()
defer os.Remove(fpath)
}

testArgs := []string{"progname", "-f", fpaths[0], "--file", fpaths[1]}

p := NewParser("", "")

files := p.FileList("f", "file", os.O_RDWR, 0666, &Options{Default: []string{"./non-existent-file1.tmp", "./non-existent-file2.tmp"}})

err := p.Parse(testArgs)
switch {
case err != nil:
t.Errorf("Test %s failed with error: %s", t.Name(), err.Error())
case files == nil:
t.Errorf("Test %s failed with l1 being nil pointer", t.Name())
}
for i, file := range *files {
defer file.Close()
testString := "Test"
recSlice := make([]byte, 4)
_, err = file.WriteString(testString)
if err != nil {
t.Errorf("Test %s write operation with file: %s failed with error: %s", t.Name(), fpaths[i], err.Error())
return
}
file.Seek(0, 0)
n, err := file.Read(recSlice)
if err != nil {
t.Errorf("Test %s read operation with file: %s failed with error: %s", t.Name(), fpaths[i], err.Error())
return
}
if n != 4 || string(recSlice) != testString {
t.Errorf("Test %s failed with file: %s on read operation", t.Name(), fpaths[i])
return
}
}
}

func TestFloatListSimple1(t *testing.T) {
testArgs := []string{"progname", "--flag-arg1", "12", "--flag-arg1", "-10.1", "--flag-arg1", "+10"}
list1Expect := []float64{12, -10.1, 10}
Expand Down Expand Up @@ -1247,6 +1296,53 @@ func TestFileDefaultValueFail(t *testing.T) {
defer file1.Close()
}

func TestFileListDefaultValuePass(t *testing.T) {
testArgs := []string{"progname"}
// Test files location
fpaths := []string{"./test1.tmp", "./test2.tmp"}
// Create test files
for _, fpath := range fpaths {
f, err := os.Create(fpath)
if err != nil {
t.Error(err)
return
}
f.Close()
defer os.Remove(fpath)
}

p := NewParser("progname", "Prog description")

files := p.FileList("f", "float", os.O_RDWR, 0666, &Options{Default: fpaths})

err := p.Parse(testArgs)

if err != nil {
t.Error(err.Error())
}
for i, file := range *files {
defer file.Close()
testString := "Test"
recSlice := make([]byte, 4)
_, err = file.WriteString(testString)
if err != nil {
t.Errorf("Test %s write operation with file: %s failed with error: %s", t.Name(), fpaths[i], err.Error())
return
}
file.Seek(0, 0)
n, err := file.Read(recSlice)
if err != nil {
t.Errorf("Test %s read operation with file: %s failed with error: %s", t.Name(), fpaths[i], err.Error())
return
}
if n != 4 || string(recSlice) != testString {
t.Errorf("Test %s failed with file: %s on read operation", t.Name(), fpaths[i])
return
}
}

}

func TestFloatListDefaultValuePass(t *testing.T) {
testArgs := []string{"progname"}
testList := []float64{12.0, -10}
Expand Down Expand Up @@ -1328,6 +1424,22 @@ func TestListDefaultValuePass(t *testing.T) {
}
}

func TestFileListDefaultValueFail(t *testing.T) {
testArgs := []string{"progname"}

p := NewParser("progname", "Prog description")

_ = p.FileList("f", "float", os.O_RDWR, 0666, &Options{Default: false})

err := p.Parse(testArgs)

// Should pass on failure
failureMessage := "cannot use default type [bool] as type [[]string]"
if err == nil || err.Error() != failureMessage {
t.Errorf("Test %s failed: expected error [%s], got error [%+v]", t.Name(), failureMessage, err)
}
}

func TestFloatListDefaultValueFail(t *testing.T) {
testArgs := []string{"progname"}

Expand Down
30 changes: 30 additions & 0 deletions argument.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,20 @@ func (o *arg) parse(args []string, argCount int) error {
}
*o.result.(*[]float64) = append(*o.result.(*[]float64), val)
o.parsed = true
//data of []os.File type is for FileList argument with set of int parameters
case *[]os.File:
switch {
case len(args) < 1:
return fmt.Errorf("[%s] must be followed by a string representation of integer", o.name())
case len(args) > 1:
return fmt.Errorf("[%s] followed by too many arguments", o.name())
}
f, err := os.OpenFile(args[0], o.fileFlag, o.filePerm)
if err != nil {
return err
}
*o.result.(*[]os.File) = append(*o.result.(*[]os.File), *f)
o.parsed = true
default:
return fmt.Errorf("unsupported type [%t]", o.result)
}
Expand Down Expand Up @@ -358,6 +372,22 @@ func (o *arg) setDefault() error {
return fmt.Errorf("cannot use default type [%T] as type [[]float64]", o.opts.Default)
}
*o.result.(*[]float64) = o.opts.Default.([]float64)
case *[]os.File:
// In case of FileList we should get []string as default value
var files []os.File
if fileNames, ok := o.opts.Default.([]string); ok {
files = make([]os.File, 0, len(fileNames))
for _, v := range fileNames {
f, err := os.OpenFile(v, o.fileFlag, o.filePerm)
if err != nil {
return err
}
files = append(files, *f)
}
} else {
return fmt.Errorf("cannot use default type [%T] as type [[]string]", o.opts.Default)
}
*o.result.(*[]os.File) = files
}
}

Expand Down