Repository: projectdiscovery/goflags
Pull request #54 (2022-06-08 05:44:30)

Preparing goflags@0.8 Release
## Description
This PR prepares the repository for goflags `0.8` release


Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 545cdea..9f533f8 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -23,16 +23,16 @@ jobs:
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v2
+      uses: actions/checkout@v3
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
+      uses: github/codeql-action/init@v2
       with:
         languages: ${{ matrix.language }}
 
     - name: Autobuild
-      uses: github/codeql-action/autobuild@v1
+      uses: github/codeql-action/autobuild@v2
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1
\ No newline at end of file
+      uses: github/codeql-action/analyze@v2
\ No newline at end of file
diff --git a/LICENSE b/LICENSE.md
similarity index 96%
rename from LICENSE
rename to LICENSE.md
index 11f3c35..b22968b 100644
--- a/LICENSE
+++ b/LICENSE.md
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2021 ProjectDiscovery
+Copyright (c) 2021 ProjectDiscovery, Inc.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 09a7722..e158fb9 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,12 @@
 # goflags
 
-A wrapper on go `flag` library adding some convenience wrappers like better Usage, short and long flags, config file support, etc.
+[![License](https://img.shields.io/github/license/projectdiscovery/goflags)](LICENSE.md)
+![Go version](https://img.shields.io/github/go-mod/go-version/projectdiscovery/nuclei?filename=v2%2Fgo.mod)
+[![Release](https://img.shields.io/github/release/projectdiscovery/goflags)](https://github.com/projectdiscovery/goflags/releases/)
+[![Checks](https://github.com/projectdiscovery/goflags/actions/workflows/build-test.yml/badge.svg)](https://github.com/projectdiscovery/goflags/actions/workflows/build-test.yml)
+
+An extension of the go `flag` library that adds convenience functions and functionalities like config file, better usage, short and long flag support, etc.
 
 ### Thanks
 
-1. spf13/cobra - The very nice usage template for the command line.
\ No newline at end of file
+1. spf13/cobra - For the very nice usage template for the command line.
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 2e9f4da..dcca109 100644
--- a/go.mod
+++ b/go.mod
@@ -1,11 +1,18 @@
 module github.com/projectdiscovery/goflags
 
-go 1.14
+go 1.17
 
 require (
 	github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08
 	github.com/pkg/errors v0.9.1
+	github.com/projectdiscovery/fileutil v0.0.0-20210928100737-cab279c5d4b5
 	github.com/projectdiscovery/stringsutil v0.0.0-20210804142656-fd3c28dbaafe
-	github.com/stretchr/testify v1.7.0
-	gopkg.in/yaml.v2 v2.4.0
+	github.com/stretchr/testify v1.7.2
+	gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/karrick/godirwalk v1.16.1 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 287000c..04f6f0d 100644
--- a/go.sum
+++ b/go.sum
@@ -1,19 +1,24 @@
 github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ=
 github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw=
+github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/projectdiscovery/fileutil v0.0.0-20210928100737-cab279c5d4b5 h1:2dbm7UhrAKnccZttr78CAmG768sSCd+MBn4ayLVDeqA=
+github.com/projectdiscovery/fileutil v0.0.0-20210928100737-cab279c5d4b5/go.mod h1:U+QCpQnX8o2N2w0VUGyAzjM3yBAe4BKedVElxiImsx0=
 github.com/projectdiscovery/stringsutil v0.0.0-20210804142656-fd3c28dbaafe h1:tQTgf5XLBgZbkJDPtnV3SfdP9tzz5ZWeDBwv8WhnH9Q=
 github.com/projectdiscovery/stringsutil v0.0.0-20210804142656-fd3c28dbaafe/go.mod h1:oTRc18WBv9t6BpaN9XBY+QmG28PUpsyDzRht56Qf49I=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
+github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/goflags.go b/goflags.go
index 01e9133..4e7f549 100644
--- a/goflags.go
+++ b/goflags.go
@@ -13,10 +13,11 @@ import (
 	"strconv"
 	"strings"
 	"text/tabwriter"
+	"time"
 
 	"github.com/cnf/structhash"
-	"github.com/pkg/errors"
-	"gopkg.in/yaml.v2"
+	"gopkg.in/yaml.v3"
+	"github.com/projectdiscovery/fileutil"
 )
 
 // FlagSet is a list of flags for an application
@@ -29,6 +30,7 @@ type FlagSet struct {
 
 	// OtherOptionsGroupName is the name for all flags not in a group
 	OtherOptionsGroupName string
+	configOnlyKeys        InsertionOrderedMap
 }
 
 type groupData struct {
@@ -43,6 +45,7 @@ type FlagData struct {
 	group        string // unused unless set later
 	defaultValue interface{}
 	skipMarshal  bool
+	field        flag.Value
 }
 
 // Group sets the group for a flag data
@@ -52,14 +55,16 @@ func (flagData *FlagData) Group(name string) {
 
 // NewFlagSet creates a new flagSet structure for the application
 func NewFlagSet() *FlagSet {
-	return &FlagSet{flagKeys: *newInsertionOrderedMap(), OtherOptionsGroupName: "other options", CommandLine: flag.NewFlagSet(os.Args[0], flag.ExitOnError)}
+	return &FlagSet{
+		flagKeys:              newInsertionOrderedMap(),
+		OtherOptionsGroupName: "other options",
+		CommandLine:           flag.NewFlagSet(os.Args[0], flag.ExitOnError),
+		configOnlyKeys:        newInsertionOrderedMap(),
+	}
 }
 
-func newInsertionOrderedMap() *InsertionOrderedMap {
-	return &InsertionOrderedMap{
-		values: make(map[string]*FlagData),
-		keys:   make([]string, 0),
-	}
+func newInsertionOrderedMap() InsertionOrderedMap {
+	return InsertionOrderedMap{values: make(map[string]*FlagData)}
 }
 
 // Hash returns the unique hash for a flagData structure
@@ -91,21 +96,16 @@ func (flagSet *FlagSet) Parse() error {
 	flagSet.CommandLine.Usage = flagSet.usageFunc
 	_ = flagSet.CommandLine.Parse(os.Args[1:])
 
-	appName := filepath.Base(os.Args[0])
-	// trim extension from app name
-	appName = strings.TrimSuffix(appName, filepath.Ext(appName))
-	homePath, err := os.UserHomeDir()
+	configFilePath, err := GetConfigFilePath()
 	if err != nil {
 		return err
 	}
-
-	config := filepath.Join(homePath, ".config", appName, "config.yaml")
-	_ = os.MkdirAll(filepath.Dir(config), os.ModePerm)
-	if _, err := os.Stat(config); os.IsNotExist(err) {
+	_ = os.MkdirAll(filepath.Dir(configFilePath), os.ModePerm)
+	if !fileutil.FileExists(configFilePath) {
 		configData := flagSet.generateDefaultConfig()
-		return ioutil.WriteFile(config, configData, os.ModePerm)
+		return ioutil.WriteFile(configFilePath, configData, os.ModePerm)
 	}
-	_ = flagSet.MergeConfigFile(config) // try to read default config after parsing flags
+	_ = flagSet.MergeConfigFile(configFilePath) // try to read default config after parsing flags
 	return nil
 }
 
@@ -153,9 +153,7 @@ func (flagSet *FlagSet) generateDefaultConfig() []byte {
 		case flag.Value:
 			configBuffer.WriteString(dv.String())
 		case StringSlice:
-			configBuffer.WriteString(dv.createStringArrayDefaultValue())
-		case NormalizedStringSlice:
-			configBuffer.WriteString(dv.createStringArrayDefaultValue())
+			configBuffer.WriteString(dv.String())
 		}
 
 		configBuffer.WriteString("\n\n")
@@ -164,6 +162,14 @@ func (flagSet *FlagSet) generateDefaultConfig() []byte {
 	return bytes.TrimSuffix(configBuffer.Bytes(), []byte("\n\n"))
 }
 
+// CreateGroup within the flagset
+func (flagSet *FlagSet) CreateGroup(groupName, description string, flags ...*FlagData) {
+	flagSet.SetGroup(groupName, description)
+	for _, currentFlag := range flags {
+		currentFlag.Group(groupName)
+	}
+}
+
 // readConfigFile reads the config file and returns any flags
 // that might have been set by the config file.
 //
@@ -171,20 +177,49 @@ func (flagSet *FlagSet) generateDefaultConfig() []byte {
 func (flagSet *FlagSet) readConfigFile(filePath string) error {
 	file, err := os.Open(filePath)
 	if err != nil {
-		return errors.Wrap(err, "could not open config file")
+		return err
 	}
 	defer file.Close()
 
 	data := make(map[string]interface{})
 	err = yaml.NewDecoder(file).Decode(&data)
 	if err != nil {
-		return errors.Wrap(err, "could not unmarshal config file")
+		return err
 	}
 	flagSet.CommandLine.VisitAll(func(fl *flag.Flag) {
 		item, ok := data[fl.Name]
 		value := fl.Value.String()
 
 		if strings.EqualFold(fl.DefValue, value) && ok {
+			switch itemValue := item.(type) {
+			case string:
+				_ = fl.Value.Set(itemValue)
+			case bool:
+				_ = fl.Value.Set(strconv.FormatBool(itemValue))
+			case int:
+				_ = fl.Value.Set(strconv.Itoa(itemValue))
+			case time.Duration:
+				_ = fl.Value.Set(itemValue.String())
+			case []interface{}:
+				for _, v := range itemValue {
+					vStr, ok := v.(string)
+					if ok {
+						_ = fl.Value.Set(vStr)
+					}
+				}
+			}
+		}
+	})
+
+	flagSet.configOnlyKeys.forEach(func(key string, flagData *FlagData) {
+		item, ok := data[key]
+		if ok {
+			fl := flag.Lookup(key)
+			if fl == nil {
+				flag.Var(flagData.field, key, flagData.usage)
+				fl = flag.Lookup(key)
+			}
+
 			switch data := item.(type) {
 			case string:
 				_ = fl.Value.Set(data)
@@ -207,31 +242,24 @@ func (flagSet *FlagSet) readConfigFile(filePath string) error {
 
 // VarP adds a Var flag with a shortname and longname
 func (flagSet *FlagSet) VarP(field flag.Value, long, short, usage string) *FlagData {
-	flagSet.CommandLine.Var(field, short, usage)
-	flagSet.CommandLine.Var(field, long, usage)
-
 	flagData := &FlagData{
 		usage:        usage,
-		short:        short,
 		long:         long,
 		defaultValue: field,
 	}
-	flagSet.flagKeys.Set(short, flagData)
+	if short != "" {
+		flagData.short = short
+		flagSet.CommandLine.Var(field, short, usage)
+		flagSet.flagKeys.Set(short, flagData)
+	}
+	flagSet.CommandLine.Var(field, long, usage)
 	flagSet.flagKeys.Set(long, flagData)
 	return flagData
 }
 
 // Var adds a Var flag with a longname
 func (flagSet *FlagSet) Var(field flag.Value, long, usage string) *FlagData {
-	flagSet.CommandLine.Var(field, long, usage)
-
-	flagData := &FlagData{
-		usage:        usage,
-		long:         long,
-		defaultValue: field,
-	}
-	flagSet.flagKeys.Set(long, flagData)
-	return flagData
+	return flagSet.VarP(field, long, "", usage)
 }
 
 // StringVarEnv adds a string flag with a shortname and longname with a default value read from env variable
@@ -245,206 +273,172 @@ func (flagSet *FlagSet) StringVarEnv(field *string, long, short, defaultValue, e
 
 // StringVarP adds a string flag with a shortname and longname
 func (flagSet *FlagSet) StringVarP(field *string, long, short, defaultValue, usage string) *FlagData {
-	flagSet.CommandLine.StringVar(field, short, defaultValue, usage)
-	flagSet.CommandLine.StringVar(field, long, defaultValue, usage)
-
 	flagData := &FlagData{
 		usage:        usage,
-		short:        short,
 		long:         long,
 		defaultValue: defaultValue,
 	}
-	flagSet.flagKeys.Set(short, flagData)
+	if short != "" {
+		flagData.short = short
+		flagSet.CommandLine.StringVar(field, short, defaultValue, usage)
+		flagSet.flagKeys.Set(short, flagData)
+	}
+	flagSet.CommandLine.StringVar(field, long, defaultValue, usage)
 	flagSet.flagKeys.Set(long, flagData)
 	return flagData
 }
 
 // StringVar adds a string flag with a longname
 func (flagSet *FlagSet) StringVar(field *string, long, defaultValue, usage string) *FlagData {
-	flagSet.CommandLine.StringVar(field, long, defaultValue, usage)
-
-	flagData := &FlagData{
-		usage:        usage,
-		long:         long,
-		defaultValue: defaultValue,
-	}
-	flagSet.flagKeys.Set(long, flagData)
-	return flagData
+	return flagSet.StringVarP(field, long, "", defaultValue, usage)
 }
 
 // BoolVarP adds a bool flag with a shortname and longname
 func (flagSet *FlagSet) BoolVarP(field *bool, long, short string, defaultValue bool, usage string) *FlagData {
-	flagSet.CommandLine.BoolVar(field, short, defaultValue, usage)
-	flagSet.CommandLine.BoolVar(field, long, defaultValue, usage)
-
 	flagData := &FlagData{
 		usage:        usage,
-		short:        short,
 		long:         long,
 		defaultValue: strconv.FormatBool(defaultValue),
 	}
-	flagSet.flagKeys.Set(short, flagData)
+	if short != "" {
+		flagData.short = short
+		flagSet.CommandLine.BoolVar(field, short, defaultValue, usage)
+		flagSet.flagKeys.Set(short, flagData)
+	}
+	flagSet.CommandLine.BoolVar(field, long, defaultValue, usage)
 	flagSet.flagKeys.Set(long, flagData)
 	return flagData
 }
 
 // BoolVar adds a bool flag with a longname
 func (flagSet *FlagSet) BoolVar(field *bool, long string, defaultValue bool, usage string) *FlagData {
-	flagSet.CommandLine.BoolVar(field, long, defaultValue, usage)
-
-	flagData := &FlagData{
-		usage:        usage,
-		long:         long,
-		defaultValue: strconv.FormatBool(defaultValue),
-	}
-	flagSet.flagKeys.Set(long, flagData)
-	return flagData
+	return flagSet.BoolVarP(field, long, "", defaultValue, usage)
 }
 
 // IntVarP adds a int flag with a shortname and longname
 func (flagSet *FlagSet) IntVarP(field *int, long, short string, defaultValue int, usage string) *FlagData {
-	flagSet.CommandLine.IntVar(field, short, defaultValue, usage)
-	flagSet.CommandLine.IntVar(field, long, defaultValue, usage)
-
 	flagData := &FlagData{
 		usage:        usage,
 		short:        short,
 		long:         long,
 		defaultValue: strconv.Itoa(defaultValue),
 	}
-	flagSet.flagKeys.Set(short, flagData)
+	if short != "" {
+		flagData.short = short
+		flagSet.CommandLine.IntVar(field, short, defaultValue, usage)
+		flagSet.flagKeys.Set(short, flagData)
+	}
+	flagSet.CommandLine.IntVar(field, long, defaultValue, usage)
 	flagSet.flagKeys.Set(long, flagData)
 	return flagData
 }
 
 // IntVar adds a int flag with a longname
 func (flagSet *FlagSet) IntVar(field *int, long string, defaultValue int, usage string) *FlagData {
-	flagSet.CommandLine.IntVar(field, long, defaultValue, usage)
-
-	flagData := &FlagData{
-		usage:        usage,
-		long:         long,
-		defaultValue: strconv.Itoa(defaultValue),
-	}
-	flagSet.flagKeys.Set(long, flagData)
-	return flagData
+	return flagSet.IntVarP(field, long, "", defaultValue, usage)
 }
 
-// NormalizedStringSliceVarP adds a path slice flag with a shortname and longname.
-// It supports comma separated values, that are normalized (lower-cased, stripped of any leading and trailing whitespaces and quotes)
-func (flagSet *FlagSet) NormalizedStringSliceVarP(field *NormalizedStringSlice, long, short string, defaultValue NormalizedStringSlice, usage string) *FlagData {
-	for _, item := range defaultValue {
-		_ = field.Set(item)
+// StringSliceVarP adds a string slice flag with a shortname and longname
+// Use options to customize the behavior
+func (flagSet *FlagSet) StringSliceVarP(field *StringSlice, long, short string, defaultValue StringSlice, usage string, options Options) *FlagData {
+	optionMap[field] = options
+	for _, defaultItem := range defaultValue {
+		_ = field.Set(defaultItem)
+		values, _ := toStringSlice(defaultItem, options)
+		for _, value := range values {
+			_ = field.Set(value)
+		}
 	}
-
-	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
-}
-
-// NormalizedStringSliceVar adds a path slice flag with a long name
-// It supports comma separated values, that are normalized (lower-cased, stripped of any leading and trailing whitespaces and quotes)
-func (flagSet *FlagSet) NormalizedStringSliceVar(field *NormalizedStringSlice, long string, defaultValue NormalizedStringSlice, usage string) *FlagData {
-	for _, item := range defaultValue {
-		_ = field.Set(item)
+	if short != "" {
+		flagData.short = short
+		flagSet.CommandLine.Var(field, short, usage)
+		flagSet.flagKeys.Set(short, flagData)
 	}
-
 	flagSet.CommandLine.Var(field, long, usage)
-
-	flagData := &FlagData{
-		usage:        usage,
-		long:         long,
-		defaultValue: defaultValue,
-	}
 	flagSet.flagKeys.Set(long, flagData)
 	return flagData
 }
 
-// StringSliceVarP adds a string slice flag with a shortname and longname
+// StringSliceVar adds a string slice flag with a longname
 // Supports ONE value at a time. Adding multiple values require repeating the argument (-flag value1 -flag value2)
 // No value normalization is happening.
-func (flagSet *FlagSet) StringSliceVarP(field *StringSlice, long, short string, defaultValue StringSlice, usage string) *FlagData {
+func (flagSet *FlagSet) StringSliceVar(field *StringSlice, long string, defaultValue []string, usage string, options Options) *FlagData {
+	return flagSet.StringSliceVarP(field, long, "", defaultValue, usage, options)
+}
+
+// StringSliceVarConfigOnly adds a string slice config value (without flag) with a longname
+func (flagSet *FlagSet) StringSliceVarConfigOnly(field *StringSlice, long string, defaultValue []string, 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,
+		field:        field,
 	}
-	flagSet.flagKeys.Set(short, flagData)
+	flagSet.configOnlyKeys.Set(long, flagData)
 	flagSet.flagKeys.Set(long, flagData)
 	return flagData
 }
 
-// StringSliceVar adds a string slice flag with a longname
-// Supports ONE value at a time. Adding multiple values require repeating the argument (-flag value1 -flag value2)
-// No value normalization is happening.
-func (flagSet *FlagSet) StringSliceVar(field *StringSlice, long string, defaultValue StringSlice, usage string) *FlagData {
+// RuntimeMapVarP adds a runtime only map flag with a longname
+func (flagSet *FlagSet) RuntimeMapVar(field *RuntimeMap, long string, defaultValue []string, usage string) *FlagData {
+	return flagSet.RuntimeMapVarP(field, long, "", defaultValue, usage)
+}
+
+// RuntimeMapVarP adds a runtime only map flag with a shortname and longname
+func (flagSet *FlagSet) RuntimeMapVarP(field *RuntimeMap, long, short string, defaultValue []string, usage string) *FlagData {
 	for _, item := range defaultValue {
 		_ = field.Set(item)
 	}
 
-	flagSet.CommandLine.Var(field, long, usage)
-
 	flagData := &FlagData{
 		usage:        usage,
 		long:         long,
 		defaultValue: defaultValue,
+		skipMarshal:  true,
+	}
+
+	if short != "" {
+		flagData.short = short
+		flagSet.CommandLine.Var(field, short, usage)
+		flagSet.flagKeys.Set(short, flagData)
 	}
+	flagSet.CommandLine.Var(field, long, usage)
 	flagSet.flagKeys.Set(long, flagData)
 	return flagData
 }
 
-// RuntimeMapVarP adds a runtime only map flag with a longname
-func (flagSet *FlagSet) RuntimeMapVar(field *RuntimeMap, long string, defaultValue []string, usage string) *FlagData {
-	for _, item := range defaultValue {
-		_ = field.Set(item)
-	}
-
-	flagSet.CommandLine.Var(field, long, usage)
+// DurationVarP adds a duration flag with a shortname and longname
+func (flagSet *FlagSet) DurationVarP(field *time.Duration, long, short string, defaultValue time.Duration, usage string) *FlagData {
+	flagSet.CommandLine.DurationVar(field, short, defaultValue, usage)
+	flagSet.CommandLine.DurationVar(field, long, defaultValue, usage)
 
 	flagData := &FlagData{
 		usage:        usage,
+		short:        short,
 		long:         long,
 		defaultValue: defaultValue,
-		skipMarshal:  true,
 	}
+	flagSet.flagKeys.Set(short, flagData)
 	flagSet.flagKeys.Set(long, flagData)
 	return flagData
 }
 
-// RuntimeMapVarP adds a runtime only map flag with a shortname and longname
-func (flagSet *FlagSet) RuntimeMapVarP(field *RuntimeMap, long, short string, defaultValue []string, usage string) *FlagData {
-	for _, item := range defaultValue {
-		_ = field.Set(item)
-	}
-
-	flagSet.CommandLine.Var(field, short, usage)
-	flagSet.CommandLine.Var(field, long, usage)
-
+// DurationVar adds a duration flag with a longname
+func (flagSet *FlagSet) DurationVar(field *time.Duration, long string, defaultValue time.Duration, usage string) *FlagData {
+	flagSet.CommandLine.DurationVar(field, long, defaultValue, usage)
 	flagData := &FlagData{
 		usage:        usage,
-		short:        short,
 		long:         long,
 		defaultValue: defaultValue,
-		skipMarshal:  true,
 	}
-	flagSet.flagKeys.Set(short, flagData)
 	flagSet.flagKeys.Set(long, flagData)
 	return flagData
 }
@@ -469,13 +463,13 @@ func (flagSet *FlagSet) usageFuncInternal(writer *tabwriter.Writer) {
 	uniqueDeduper := newUniqueDeduper()
 
 	flagSet.flagKeys.forEach(func(key string, data *FlagData) {
-		currentFlag := flagSet.CommandLine.Lookup(key)
-
-		if !uniqueDeduper.isUnique(data) {
-			return
+		if currentFlag := flagSet.CommandLine.Lookup(key); currentFlag != nil {
+			if !uniqueDeduper.isUnique(data) {
+				return
+			}
+			result := createUsageString(data, currentFlag)
+			fmt.Fprint(writer, result, "\n")
 		}
-		result := createUsageString(data, currentFlag)
-		fmt.Fprint(writer, result, "\n")
 	})
 	writer.Flush()
 }
@@ -489,24 +483,25 @@ func (flagSet *FlagSet) usageFuncForGroups(cliOutput io.Writer, writer *tabwrite
 		fmt.Fprintf(cliOutput, "%s:\n", normalizeGroupDescription(group.description))
 
 		flagSet.flagKeys.forEach(func(key string, data *FlagData) {
-			currentFlag := flagSet.CommandLine.Lookup(key)
+			if currentFlag := flagSet.CommandLine.Lookup(key); currentFlag != nil {
 
-			if data.group == "" {
+				if data.group == "" {
+					if !uniqueDeduper.isUnique(data) {
+						return
+					}
+					otherOptions = append(otherOptions, createUsageString(data, currentFlag))
+					return
+				}
+				// Ignore the flag if it's not in our intended group
+				if !strings.EqualFold(data.group, group.name) {
+					return
+				}
 				if !uniqueDeduper.isUnique(data) {
 					return
 				}
-				otherOptions = append(otherOptions, createUsageString(data, currentFlag))
-				return
-			}
-			// Ignore the flag if it's not in our intended group
-			if !strings.EqualFold(data.group, group.name) {
-				return
+				result := createUsageString(data, currentFlag)
+				fmt.Fprint(writer, result, "\n")
 			}
-			if !uniqueDeduper.isUnique(data) {
-				return
-			}
-			result := createUsageString(data, currentFlag)
-			fmt.Fprint(writer, result, "\n")
 		})
 		writer.Flush()
 		fmt.Printf("\n")
diff --git a/normalized_slice.go b/normalized_slice.go
deleted file mode 100644
index 0032fdb..0000000
--- a/normalized_slice.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package goflags
-
-import (
-	"strings"
-
-	"github.com/pkg/errors"
-)
-
-var quotes = []rune{'"', '\'', '`'}
-
-// NormalizedStringSlice is a slice of strings
-type NormalizedStringSlice []string
-
-func (normalizedStringSlice NormalizedStringSlice) String() string {
-	return normalizedStringSlice.createStringArrayDefaultValue()
-}
-
-//Set appends a value to the string slice.
-func (normalizedStringSlice *NormalizedStringSlice) Set(value string) error {
-	if slice, err := ToNormalizedStringSlice(value); err != nil {
-		return err
-	} else {
-		*normalizedStringSlice = append(*normalizedStringSlice, slice...)
-		return nil
-	}
-}
-
-func (normalizedStringSlice *NormalizedStringSlice) createStringArrayDefaultValue() string {
-	defaultBuilder := &strings.Builder{}
-	defaultBuilder.WriteString("[")
-	for i, k := range *normalizedStringSlice {
-		defaultBuilder.WriteString("\"")
-		defaultBuilder.WriteString(k)
-		defaultBuilder.WriteString("\"")
-		if i != len(*normalizedStringSlice)-1 {
-			defaultBuilder.WriteString(", ")
-		}
-	}
-	defaultBuilder.WriteString("]")
-	return defaultBuilder.String()
-}
-
-func ToNormalizedStringSlice(value string) ([]string, error) {
-	var result []string
-
-	addPartToResult := func(part string) {
-		if strings.TrimSpace(part) != "" {
-			result = append(result, strings.TrimSpace(strings.Trim(strings.TrimSpace(strings.ToLower(part)), string(quotes))))
-		}
-	}
-
-	index := 0
-	for index < len(value) {
-		char := rune(value[index])
-		if isQuote, quote := isQuote(char); isQuote {
-			quoteFound, part := searchPart(value[index+1:], quote)
-
-			if !quoteFound {
-				return nil, errors.New("Unclosed quote in path")
-			}
-
-			index += len(part) + 2
-
-			addPartToResult(part)
-		} else {
-			commaFound, part := searchPart(value[index:], ',')
-
-			if commaFound {
-				index += len(part) + 1
-			} else {
-				index += len(part)
-			}
-
-			addPartToResult(part)
-		}
-	}
-
-	return result, nil
-}
-
-func isQuote(char rune) (bool, rune) {
-	for _, quote := range quotes {
-		if quote == char {
-			return true, quote
-		}
-	}
-	return false, 0
-}
-
-func searchPart(value string, stop rune) (bool, string) {
-	var result string
-	for _, char := range value {
-		if char != stop {
-			result += string(char)
-		} else {
-			return true, result
-		}
-	}
-	return false, result
-}
diff --git a/path.go b/path.go
new file mode 100644
index 0000000..cc7cb4e
--- /dev/null
+++ b/path.go
@@ -0,0 +1,20 @@
+package goflags
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+// GetConfigFilePath returns the default config file path
+func GetConfigFilePath() (string, error) {
+	appName := filepath.Base(os.Args[0])
+	// trim extension from app name
+	appName = strings.TrimSuffix(appName, filepath.Ext(appName))
+	homePath, err := os.UserHomeDir()
+	if err != nil {
+		return "", err
+	}
+
+	return filepath.Join(homePath, ".config", appName, "config.yaml"), nil
+}
diff --git a/slice_common.go b/slice_common.go
new file mode 100644
index 0000000..a2d398c
--- /dev/null
+++ b/slice_common.go
@@ -0,0 +1,121 @@
+package goflags
+
+import (
+	"strings"
+
+	"github.com/pkg/errors"
+	"github.com/projectdiscovery/fileutil"
+)
+
+var quotes = []rune{'"', '\'', '`'}
+
+func isQuote(char rune) (bool, rune) {
+	for _, quote := range quotes {
+		if quote == char {
+			return true, quote
+		}
+	}
+	return false, 0
+}
+
+func searchPart(value string, stop rune) (bool, string) {
+	var result string
+	for _, char := range value {
+		if char != stop {
+			result += string(char)
+		} else {
+			return true, result
+		}
+	}
+	return false, result
+}
+
+func ToString(slice []string) string {
+	defaultBuilder := &strings.Builder{}
+	defaultBuilder.WriteString("[")
+	for i, k := range slice {
+		defaultBuilder.WriteString("\"")
+		defaultBuilder.WriteString(k)
+		defaultBuilder.WriteString("\"")
+		if i != len(slice)-1 {
+			defaultBuilder.WriteString(", ")
+		}
+	}
+	defaultBuilder.WriteString("]")
+	return defaultBuilder.String()
+}
+
+type Options struct {
+	IsFromFile func(string) bool
+	IsEmpty    func(string) bool
+	Normalize  func(string) string
+}
+
+func toStringSlice(value string, options Options) ([]string, error) {
+	var result []string
+	if options.IsEmpty == nil && options.IsFromFile == nil && options.Normalize == nil {
+		return []string{value}, nil
+	}
+
+	addPartToResult := func(part string) {
+		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) {
+		linesChan, err := fileutil.ReadFile(value)
+		if err != nil {
+			return nil, err
+		}
+		for line := range linesChan {
+			addPartToResult(line)
+		}
+	} else {
+		index := 0
+		for index < len(value) {
+			char := rune(value[index])
+			if isQuote, quote := isQuote(char); isQuote {
+				quoteFound, part := searchPart(value[index+1:], quote)
+
+				if !quoteFound {
+					return nil, errors.New("Unclosed quote in path")
+				}
+
+				index += len(part) + 2
+
+				addPartToResult(part)
+			} else {
+				commaFound, part := searchPart(value[index:], ',')
+
+				if commaFound {
+					index += len(part) + 1
+				} else {
+					index += len(part)
+				}
+
+				addPartToResult(part)
+			}
+		}
+	}
+
+	return result, nil
+}
+
+func isEmpty(s string) bool {
+	return strings.TrimSpace(s) == ""
+}
+
+func normalizeTrailingParts(s string) string {
+	return strings.TrimSpace(s)
+}
+
+func normalize(s string) string {
+	return strings.TrimSpace(strings.Trim(strings.TrimSpace(s), string(quotes)))
+}
+
+func normalizeLowercase(s string) string {
+	return strings.TrimSpace(strings.Trim(strings.TrimSpace(strings.ToLower(s)), string(quotes)))
+}
diff --git a/string_slice.go b/string_slice.go
index 54208dd..b8e7e03 100644
--- a/string_slice.go
+++ b/string_slice.go
@@ -1,33 +1,28 @@
 package goflags
 
-import (
-	"strings"
-)
+var optionMap map[*StringSlice]Options
+
+func init() {
+	optionMap = make(map[*StringSlice]Options)
+}
 
 // StringSlice is a slice of strings
 type StringSlice []string
 
-func (stringSlice StringSlice) String() string {
-	return stringSlice.createStringArrayDefaultValue()
-}
-
 // Set appends a value to the string slice.
 func (stringSlice *StringSlice) Set(value string) error {
-	*stringSlice = append(*stringSlice, value)
+	option, ok := optionMap[stringSlice]
+	if !ok {
+		option = StringSliceOptions
+	}
+	values, err := toStringSlice(value, option)
+	if err != nil {
+		return err
+	}
+	*stringSlice = append(*stringSlice, values...)
 	return nil
 }
 
-func (stringSlice *StringSlice) createStringArrayDefaultValue() string {
-	defaultBuilder := &strings.Builder{}
-	defaultBuilder.WriteString("[")
-	for i, k := range *stringSlice {
-		defaultBuilder.WriteString("\"")
-		defaultBuilder.WriteString(k)
-		defaultBuilder.WriteString("\"")
-		if i != len(*stringSlice)-1 {
-			defaultBuilder.WriteString(", ")
-		}
-	}
-	defaultBuilder.WriteString("]")
-	return defaultBuilder.String()
+func (stringSlice StringSlice) String() string {
+	return ToString(stringSlice)
 }
diff --git a/string_slice_options.go b/string_slice_options.go
new file mode 100644
index 0000000..1b082ca
--- /dev/null
+++ b/string_slice_options.go
@@ -0,0 +1,65 @@
+package goflags
+
+// StringSliceOptions represents the default string slice (list of items)
+// Tokenization: None
+// Normalization: None
+// Type: []string
+// Example: -flag value1 -flag value2 => {"value1", "value2"}
+var StringSliceOptions = Options{}
+
+// CommaSeparatedStringSliceOptions represents a list of comma separated items
+// Tokenization: Comma
+// Normalization: None
+// Type: []string
+// Example: -flag value1,value2 => {"value1", "value2"}
+var CommaSeparatedStringSliceOptions = Options{
+	IsEmpty: isEmpty,
+}
+
+// FileCommaSeparatedStringSliceOptions represents a list of comma separated files containing items
+// Tokenization: Comma
+// Normalization: None
+// Type: []string
+// Example: -flag test.txt # with test.txt containing on multiple lines value1 and nvalue2 => {"value1", "value2"}
+var FileCommaSeparatedStringSliceOptions = Options{
+	IsEmpty:    isEmpty,
+	IsFromFile: func(s string) bool { return true },
+}
+
+// NormalizedOriginalStringSliceOptions represents a list of items
+// Tokenization: None
+// Normalization: Standard
+// Type: []string
+// Example: -flag /value/1 -flag value2 => {"/value/1", "value2"}
+var NormalizedOriginalStringSliceOptions = Options{
+	IsEmpty:   isEmpty,
+	Normalize: normalize,
+}
+
+// FileNormalizedStringSliceOptions represents a list of path items
+// Tokenization: Comma
+// Normalization: Standard
+// Type: []string
+// Example: -flag /value/1 -flag value2 => {"/value/1", "value2"}
+var FileNormalizedStringSliceOptions = Options{
+	IsEmpty:    isEmpty,
+	Normalize:  normalizeLowercase,
+	IsFromFile: func(s string) bool { return true },
+}
+
+// FileStringSliceOptions represents a list of items stored in a file
+// Tokenization: Standard
+// Normalization: Standard
+var FileStringSliceOptions = Options{
+	IsEmpty:    isEmpty,
+	Normalize:  normalizeTrailingParts,
+	IsFromFile: func(s string) bool { return true },
+}
+
+// NormalizedStringSliceOptions represents a list of items
+// Tokenization: Comma
+// Normalization: Standard
+var NormalizedStringSliceOptions = Options{
+	IsEmpty:   isEmpty,
+	Normalize: normalizeLowercase,
+}


Test patch:
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 32778c1..f0e8ba8 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -11,12 +11,12 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Set up Go
-        uses: actions/setup-go@v2
+        uses: actions/setup-go@v3
         with:
-          go-version: 1.15
+          go-version: 1.18
 
       - name: Check out code
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
 
       - name: Test
         run: go test ./...
diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml
index 794d073..2059f5b 100644
--- a/.github/workflows/lint-test.yml
+++ b/.github/workflows/lint-test.yml
@@ -10,10 +10,14 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout code
-        uses: actions/checkout@v2
+        uses: actions/checkout@v3
+      - name: Set up Go
+        uses: actions/setup-go@v3
+        with:
+          go-version: 1.18
       - name: Run golangci-lint
-        uses: golangci/golangci-lint-action@v2
+        uses: golangci/golangci-lint-action@v3.2.0
         with:
           version: latest
           args: --timeout 5m
-          working-directory: .
\ No newline at end of file
+          working-directory: .
diff --git a/goflags_test.go b/goflags_test.go
index 98d4c27..0856765 100644
--- a/goflags_test.go
+++ b/goflags_test.go
@@ -9,6 +9,7 @@ import (
 	"strconv"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/assert"
 
@@ -29,7 +30,7 @@ func TestGenerateDefaultConfig(t *testing.T) {
 	var data string
 	var data2 StringSlice
 	flagSet.StringVar(&data, "test", "test-default-value", "Default value for a test flag example")
-	flagSet.StringSliceVar(&data2, "slice", []string{"item1", "item2"}, "String slice flag example value")
+	flagSet.StringSliceVar(&data2, "slice", []string{"item1", "item2"}, "String slice flag example value", StringSliceOptions)
 	defaultConfig := string(flagSet.generateDefaultConfig())
 	parts := strings.SplitN(defaultConfig, "\n", 2)
 
@@ -43,11 +44,13 @@ func TestConfigFileDataTypes(t *testing.T) {
 	var data2 StringSlice
 	var data3 int
 	var data4 bool
+	var data5 time.Duration
 
 	flagSet.StringVar(&data, "string-value", "", "Default value for a test flag example")
-	flagSet.StringSliceVar(&data2, "slice-value", []string{}, "String slice flag example value")
+	flagSet.StringSliceVar(&data2, "slice-value", []string{}, "String slice flag example value", StringSliceOptions)
 	flagSet.IntVar(&data3, "int-value", 0, "Int value example")
 	flagSet.BoolVar(&data4, "bool-value", false, "Bool value example")
+	flagSet.DurationVar(&data5, "duration-value", time.Hour, "Bool value example")
 
 	configFileData := `
 string-value: test
@@ -58,7 +61,8 @@ severity:
  - info
  - high
 int-value: 543
-bool-value: true`
+bool-value: true
+duration-value: 1h`
 	err := ioutil.WriteFile("test.yaml", []byte(configFileData), os.ModePerm)
 	require.Nil(t, err, "could not write temporary config")
 	defer os.Remove("test.yaml")
@@ -70,6 +74,7 @@ bool-value: true`
 	require.Equal(t, StringSlice{"test", "test2"}, data2, "could not get correct string slice")
 	require.Equal(t, 543, data3, "could not get correct int")
 	require.Equal(t, true, data4, "could not get correct bool")
+	require.Equal(t, time.Hour, data5, "could not get correct duration")
 
 	tearDown(t.Name())
 }
@@ -90,10 +95,10 @@ func TestUsageOrder(t *testing.T) {
 	flagSet.StringVarP(&stringData, "string-with-default-value2", "ts", "test-string", "String with default value example #2").Group("String")
 
 	flagSet.SetGroup("StringSlice", "StringSlice")
-	flagSet.StringSliceVar(&stringSliceData, "slice-value", []string{}, "String slice flag example value").Group("StringSlice")
-	flagSet.StringSliceVarP(&stringSliceData, "slice-value2", "sv", []string{}, "String slice flag example value #2").Group("StringSlice")
-	flagSet.StringSliceVar(&stringSliceData, "slice-with-default-value", []string{"a", "b", "c"}, "String slice flag with default example values").Group("StringSlice")
-	flagSet.StringSliceVarP(&stringSliceData2, "slice-with-default-value2", "swdf", []string{"a", "b", "c"}, "String slice flag with default example values #2").Group("StringSlice")
+	flagSet.StringSliceVar(&stringSliceData, "slice-value", []string{}, "String slice flag example value", StringSliceOptions).Group("StringSlice")
+	flagSet.StringSliceVarP(&stringSliceData, "slice-value2", "sv", []string{}, "String slice flag example value #2", StringSliceOptions).Group("StringSlice")
+	flagSet.StringSliceVar(&stringSliceData, "slice-with-default-value", []string{"a", "b", "c"}, "String slice flag with default example values", StringSliceOptions).Group("StringSlice")
+	flagSet.StringSliceVarP(&stringSliceData2, "slice-with-default-value2", "swdf", []string{"a", "b", "c"}, "String slice flag with default example values #2", StringSliceOptions).Group("StringSlice")
 
 	flagSet.SetGroup("Integer", "Integer")
 	flagSet.IntVar(&intData, "int-value", 0, "Int value example").Group("Integer")
@@ -210,14 +215,14 @@ func TestParseStringSlice(t *testing.T) {
 	flagSet := NewFlagSet()
 
 	var stringSlice StringSlice
-	flagSet.StringSliceVarP(&stringSlice, "header", "H", []string{}, "Header values. Expected usage: -H \"header1\":\"value1\" -H \"header2\":\"value2\"")
+	flagSet.StringSliceVarP(&stringSlice, "header", "H", []string{}, "Header values. Expected usage: -H \"header1\":\"value1\" -H \"header2\":\"value2\"", StringSliceOptions)
 
 	header1 := "\"header1:value1\""
 	header2 := "\" HEADER 2: VALUE2  \""
 	header3 := "\"header3\":\"value3, value4\""
 
 	os.Args = []string{
-		"./appName",
+		os.Args[0],
 		"-H", header1,
 		"-header", header2,
 		"-H", header3,
@@ -226,7 +231,86 @@ func TestParseStringSlice(t *testing.T) {
 	err := flagSet.Parse()
 	assert.Nil(t, err)
 
-	assert.Equal(t, stringSlice, StringSlice{header1, header2, header3})
+	assert.Equal(t, StringSlice{header1, header2, header3}, stringSlice)
+	tearDown(t.Name())
+
+}
+
+func TestParseCommaSeparatedStringSlice(t *testing.T) {
+	flagSet := NewFlagSet()
+
+	var csStringSlice StringSlice
+	flagSet.StringSliceVarP(&csStringSlice, "cs-value", "CSV", []string{}, "Comma Separated Values. Expected usage: -CSV value1,value2,value3", CommaSeparatedStringSliceOptions)
+
+	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, StringSlice{value1, value2, value3})
+	tearDown(t.Name())
+}
+
+func TestParseFileCommaSeparatedStringSlice(t *testing.T) {
+	flagSet := NewFlagSet()
+
+	var csStringSlice StringSlice
+	flagSet.StringSliceVarP(&csStringSlice, "cs-value", "CSV", []string{}, "Comma Separated Values. Expected usage: -CSV path/to/file", FileCommaSeparatedStringSliceOptions)
+
+	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, StringSlice{value1, value2, value3})
+	tearDown(t.Name())
+}
+
+func TestConfigOnlyDataTypes(t *testing.T) {
+	flagSet := NewFlagSet()
+	var data StringSlice
+
+	flagSet.StringSliceVarConfigOnly(&data, "config-only", []string{}, "String slice config only flag example")
+
+	require.Nil(t, flagSet.CommandLine.Lookup("config-only"), "config-only flag should not be registered")
+
+	configFileData := `
+config-only:
+ - test
+ - test2
+ `
+	err := ioutil.WriteFile("test.yaml", []byte(configFileData), os.ModePerm)
+	require.Nil(t, err, "could not write temporary config")
+	defer os.Remove("test.yaml")
+
+	err = flagSet.MergeConfigFile("test.yaml")
+	require.Nil(t, err, "could not merge temporary config")
+
+	require.Equal(t, StringSlice{"test", "test2"}, data, "could not get correct string slice")
+	tearDown(t.Name())
 }
 
 func tearDown(uniqueValue string) { // sadly there is no official support for setup/teardown/test
diff --git a/normalized_slice_test.go b/string_slice_test.go
similarity index 79%
rename from normalized_slice_test.go
rename to string_slice_test.go
index 9ce98c7..1d7f4ef 100644
--- a/normalized_slice_test.go
+++ b/string_slice_test.go
@@ -44,7 +44,7 @@ func TestNormalizedStringSlicePositive(t *testing.T) {
 	}
 
 	for value, expected := range slices {
-		result, err := ToNormalizedStringSlice(value)
+		result, err := toStringSlice(value, NormalizedStringSliceOptions)
 		fmt.Println(result)
 		assert.Nil(t, err)
 		assert.Equal(t, result, expected)
@@ -61,8 +61,18 @@ func TestNormalizedStringSliceNegative(t *testing.T) {
 	}
 
 	for _, value := range slices {
-		result, err := ToNormalizedStringSlice(value)
+		result, err := toStringSlice(value, NormalizedStringSliceOptions)
 		assert.Nil(t, result)
 		assert.NotNil(t, err)
 	}
 }
+
+func TestNormalizedOriginalStringSlice(t *testing.T) {
+	result, err := toStringSlice("/Users/Home/Test/test.yaml", NormalizedOriginalStringSliceOptions)
+	assert.Nil(t, err)
+	assert.Equal(t, []string{"/Users/Home/Test/test.yaml"}, result, "could not get correct path")
+
+	result, err = toStringSlice("'test user'", NormalizedOriginalStringSliceOptions)
+	assert.Nil(t, err)
+	assert.Equal(t, []string{"test user"}, result, "could not get correct path")
+}