diff --git a/README.md b/README.md index e8b62a4..8980ba1 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,21 @@ $ NUM_WORKERS=4 ./example Workers: 4 ``` +You can provide multiple values using the CSV (RFC 4180) format: + +```go +var args struct { + Workers []int `arg:"env"` +} +arg.MustParse(&args) +fmt.Println("Workers:", args.Workers) +``` + +``` +$ WORKERS='1,99' ./example +Workers: [1 99] +``` + ### Usage strings ```go var args struct { diff --git a/parse.go b/parse.go index 1416223..3c682f5 100644 --- a/parse.go +++ b/parse.go @@ -2,6 +2,7 @@ package arg import ( "encoding" + "encoding/csv" "errors" "fmt" "os" @@ -275,9 +276,28 @@ func process(specs []*spec, args []string) error { } if spec.env != "" { if value, found := os.LookupEnv(spec.env); found { - err := scalar.ParseValue(spec.dest, value) - if err != nil { - return fmt.Errorf("error processing environment variable %s: %v", spec.env, err) + if spec.multiple { + // expect a CSV string in an environment + // variable in the case of multiple values + values, err := csv.NewReader(strings.NewReader(value)).Read() + if err != nil { + return fmt.Errorf( + "error reading a CSV string from environment variable %s with multiple values: %v", + spec.env, + err, + ) + } + if err = setSlice(spec.dest, values, !spec.separate); err != nil { + return fmt.Errorf( + "error processing environment variable %s with multiple values: %v", + spec.env, + err, + ) + } + } else { + if err := scalar.ParseValue(spec.dest, value); err != nil { + return fmt.Errorf("error processing environment variable %s: %v", spec.env, err) + } } spec.wasPresent = true } diff --git a/parse_test.go b/parse_test.go index 1461c02..0bc97e3 100644 --- a/parse_test.go +++ b/parse_test.go @@ -580,6 +580,60 @@ func TestEnvironmentVariableRequired(t *testing.T) { assert.Equal(t, "bar", args.Foo) } +func TestEnvironmentVariableSliceArgumentString(t *testing.T) { + var args struct { + Foo []string `arg:"env"` + } + setenv(t, "FOO", "bar,\"baz, qux\"") + MustParse(&args) + assert.Equal(t, []string{"bar", "baz, qux"}, args.Foo) +} + +func TestEnvironmentVariableSliceArgumentInteger(t *testing.T) { + var args struct { + Foo []int `arg:"env"` + } + setenv(t, "FOO", "1,99") + MustParse(&args) + assert.Equal(t, []int{1, 99}, args.Foo) +} + +func TestEnvironmentVariableSliceArgumentFloat(t *testing.T) { + var args struct { + Foo []float32 `arg:"env"` + } + setenv(t, "FOO", "1.1,99.9") + MustParse(&args) + assert.Equal(t, []float32{1.1, 99.9}, args.Foo) +} + +func TestEnvironmentVariableSliceArgumentBool(t *testing.T) { + var args struct { + Foo []bool `arg:"env"` + } + setenv(t, "FOO", "true,false,0,1") + MustParse(&args) + assert.Equal(t, []bool{true, false, false, true}, args.Foo) +} + +func TestEnvironmentVariableSliceArgumentWrongCsv(t *testing.T) { + var args struct { + Foo []int `arg:"env"` + } + setenv(t, "FOO", "1,99\"") + err := Parse(&args) + assert.Error(t, err) +} + +func TestEnvironmentVariableSliceArgumentWrongType(t *testing.T) { + var args struct { + Foo []bool `arg:"env"` + } + setenv(t, "FOO", "one,two") + err := Parse(&args) + assert.Error(t, err) +} + type textUnmarshaler struct { val int }