Repository: projectdiscovery/goflags
Pull request #39 (2022-03-04 06:45:35)

Comma Separated StringSlice


Patch:
diff --git a/comma_separated_string_slice.go b/comma_separated_string_slice.go
new file mode 100644
index 0000000..872b13d
--- /dev/null
+++ b/comma_separated_string_slice.go
@@ -0,0 +1,52 @@
+package goflags
+
+// CommaSeparatedStringSlice is a slice of strings
+type CommaSeparatedStringSlice []string
+
+// Set appends a value to the string slice.
+func (commaSeparatedStringSlice *CommaSeparatedStringSlice) Set(value string) error {
+	slice, err := ToCommaSeparatedStringSlice(value)
+	if err != nil {
+		return err
+	}
+	*commaSeparatedStringSlice = append(*commaSeparatedStringSlice, slice...)
+	return nil
+}
+
+func (commaSeparatedStringSlice CommaSeparatedStringSlice) String() string {
+	return ToString(commaSeparatedStringSlice)
+}
+
+func ToCommaSeparatedStringSlice(value string) ([]string, error) {
+	return toStringSlice(value, DefaultCommaSeparatedStringSliceOptions)
+}
+
+var DefaultCommaSeparatedStringSliceOptions = Options{
+	IsEmpty: isEmpty,
+}
+
+// FileCommaSeparatedStringSlice is a slice of strings
+type FileCommaSeparatedStringSlice []string
+
+// Set appends a value to the string slice.
+func (fileCommaSeparatedStringSlice *FileCommaSeparatedStringSlice) Set(value string) error {
+	slice, err := ToFileCommaSeparatedStringSlice(value)
+	if err != nil {
+		return err
+	}
+	*fileCommaSeparatedStringSlice = append(*fileCommaSeparatedStringSlice, slice...)
+	return nil
+}
+
+func (fileCommaSeparatedStringSlice FileCommaSeparatedStringSlice) String() string {
+	return ToString(fileCommaSeparatedStringSlice)
+}
+
+var DefaultFileCommaSeparatedStringSliceOptions = Options{
+	IsEmpty:    isEmpty,
+	IsFromFile: func(s string) bool { return true },
+}
+
+func ToFileCommaSeparatedStringSlice(value string) ([]string, error) {
+	return toStringSlice(value, DefaultFileCommaSeparatedStringSliceOptions)
+}
diff --git a/goflags.go b/goflags.go
index b92240e..94679af 100644
--- a/goflags.go
+++ b/goflags.go
@@ -557,6 +557,86 @@ func (flagSet *FlagSet) StringSliceVar(field *StringSlice, long string, defaultV
 	return flagData
 }
 
+
+// CommaSeparatedStringSliceVar adds a string slice flag with a longname
+// No value normalization is happening.
+func (flagSet *FlagSet) CommaSeparatedStringSliceVar(field *CommaSeparatedStringSlice, long string, defaultValue CommaSeparatedStringSlice, usage string) *FlagData {
+	for _, item := range defaultValue {
+		_ = field.Set(item)
+	}
+
+	flagSet.CommandLine.Var(field, long, usage)
+
+	flagData := &FlagData{
+		usage:        usage,
+		long:         long,
+		defaultValue: defaultValue,
+	}
+	flagSet.flagKeys.Set(long, flagData)
+	return flagData
+}
+
+// CommaSeparatedStringSliceVarP adds a string slice flag with a shortname and longname
+// No value normalization is happening.
+func (flagSet *FlagSet) CommaSeparatedStringSliceVarP(field *CommaSeparatedStringSlice, long, short string, defaultValue CommaSeparatedStringSlice, usage string) *FlagData {
+	for _, item := range defaultValue {
+		_ = field.Set(item)
+	}
+
+	flagSet.CommandLine.Var(field, short, usage)
+	flagSet.CommandLine.Var(field, long, usage)
+
+	flagData := &FlagData{
+		usage:        usage,
+		short:        short,
+		long:         long,
+		defaultValue: defaultValue,
+	}
+	flagSet.flagKeys.Set(short, flagData)
+	flagSet.flagKeys.Set(long, flagData)
+	return flagData
+}
+
+
+// FileCommaSeparatedStringSliceVar adds a string slice flag with a longname
+// No value normalization is happening.
+func (flagSet *FlagSet) FileCommaSeparatedStringSliceVar(field *FileCommaSeparatedStringSlice, long string, defaultValue FileCommaSeparatedStringSlice, usage string) *FlagData {
+	for _, item := range defaultValue {
+		_ = field.Set(item)
+	}
+
+	flagSet.CommandLine.Var(field, long, usage)
+
+	flagData := &FlagData{
+		usage:        usage,
+		long:         long,
+		defaultValue: defaultValue,
+	}
+	flagSet.flagKeys.Set(long, flagData)
+	return flagData
+}
+
+// FileCommaSeparatedStringSliceVarP adds a string slice flag with a shortname and longname
+// No value normalization is happening.
+func (flagSet *FlagSet) FileCommaSeparatedStringSliceVarP(field *FileCommaSeparatedStringSlice, long, short string, defaultValue FileCommaSeparatedStringSlice, usage string) *FlagData {
+	for _, item := range defaultValue {
+		_ = field.Set(item)
+	}
+
+	flagSet.CommandLine.Var(field, short, usage)
+	flagSet.CommandLine.Var(field, long, usage)
+
+	flagData := &FlagData{
+		usage:        usage,
+		short:        short,
+		long:         long,
+		defaultValue: defaultValue,
+	}
+	flagSet.flagKeys.Set(short, flagData)
+	flagSet.flagKeys.Set(long, flagData)
+	return flagData
+}
+
 // StringSliceVarConfigOnly adds a string slice config value (without flag) with a longname
 func (flagSet *FlagSet) StringSliceVarConfigOnly(field *StringSlice, long string, defaultValue StringSlice, usage string) *FlagData {
 	for _, item := range defaultValue {
diff --git a/slice_common.go b/slice_common.go
index 78985aa..c545839 100644
--- a/slice_common.go
+++ b/slice_common.go
@@ -55,8 +55,11 @@ func toStringSlice(value string, options Options) ([]string, error) {
 	var result []string
 
 	addPartToResult := func(part string) {
-		if options.IsEmpty(part) {
-			result = append(result, options.Normalize(part))
+		if !options.IsEmpty(part) {
+			if options.Normalize != nil {
+				part = options.Normalize(part)
+			}
+			result = append(result, part)
 		}
 	}
 	if fileutil.FileExists(value) && options.IsFromFile != nil && options.IsFromFile(value) {
@@ -99,7 +102,7 @@ func toStringSlice(value string, options Options) ([]string, error) {
 }
 
 func isEmpty(s string) bool {
-	return strings.TrimSpace(s) != ""
+	return strings.TrimSpace(s) == ""
 }
 
 func normalize(s string) string {


Test patch:
diff --git a/goflags_test.go b/goflags_test.go
index be3170b..1674ced 100644
--- a/goflags_test.go
+++ b/goflags_test.go
@@ -230,6 +230,58 @@ func TestParseStringSlice(t *testing.T) {
 	tearDown(t.Name())
 
 }
+func TestParseCommaSeparatedStringSlice(t *testing.T) {
+	flagSet := NewFlagSet()
+
+	var csStringSlice CommaSeparatedStringSlice
+	flagSet.CommaSeparatedStringSliceVarP(&csStringSlice, "cs-value", "CSV", []string{}, "Comma Separated Values. Expected usage: -CSV value1,value2,value3")
+
+	valueCommon := `value1,Value2 ",value3`
+	value1 := `value1`
+	value2 := `Value2 "`
+	value3 := `value3`
+
+	os.Args = []string{
+		os.Args[0],
+		"-CSV", valueCommon,
+	}
+
+	err := flagSet.Parse()
+	assert.Nil(t, err)
+
+	assert.Equal(t, csStringSlice, CommaSeparatedStringSlice{value1, value2, value3})
+	tearDown(t.Name())
+}
+
+func TestParseFileCommaSeparatedStringSlice(t *testing.T) {
+	flagSet := NewFlagSet()
+
+	var csStringSlice FileCommaSeparatedStringSlice
+	flagSet.FileCommaSeparatedStringSliceVarP(&csStringSlice, "cs-value", "CSV", []string{}, "Comma Separated Values. Expected usage: -CSV path/to/file")
+
+	testFile := "test.txt"
+	value1 := `value1`
+	value2 := `Value2 "`
+	value3 := `value3`
+
+	testFileData := `value1
+Value2 "
+value3`
+	err := ioutil.WriteFile(testFile, []byte(testFileData), os.ModePerm)
+	require.Nil(t, err, "could not write temporary values file")
+	defer os.Remove(testFile)
+
+	os.Args = []string{
+		os.Args[0],
+		"-CSV", testFile,
+	}
+
+	err = flagSet.Parse()
+	assert.Nil(t, err)
+
+	assert.Equal(t, csStringSlice, FileCommaSeparatedStringSlice{value1, value2, value3})
+	tearDown(t.Name())
+}
 
 func TestConfigOnlyDataTypes(t *testing.T) {
 	flagSet := NewFlagSet()