Repository: projectdiscovery/goflags
Pull request #14 (2021-07-02 17:54:32)

RES-84 # Improve Nuclei CI interface
* Modify goflags not to automatically sort the CLI arguments, so that they can be displayed in order of importance


Patch:
diff --git a/goflags.go b/goflags.go
index 5c0df57..16d84fd 100644
--- a/goflags.go
+++ b/goflags.go
@@ -22,7 +22,7 @@ import (
 type FlagSet struct {
 	Marshal     bool
 	description string
-	flagKeys    map[string]*flagData
+	flagKeys    InsertionOrderedMap
 }
 
 type flagData struct {
@@ -32,53 +32,60 @@ type flagData struct {
 	defaultValue interface{}
 }
 
+// NewFlagSet creates a new flagSet structure for the application
+func NewFlagSet() *FlagSet {
+	return &FlagSet{flagKeys: *newInsertionOrderedMap()}
+}
+
+func newInsertionOrderedMap() *InsertionOrderedMap {
+	return &InsertionOrderedMap{
+		values: make(map[string]*flagData),
+		keys:   make([]string, 0, 0),
+	}
+}
+
 // Hash returns the unique hash for a flagData structure
 // NOTE: Hash panics when the structure cannot be hashed.
-func (f *flagData) Hash() string {
-	hash, _ := structhash.Hash(f, 1)
+func (flagSet *flagData) Hash() string {
+	hash, _ := structhash.Hash(flagSet, 1)
 	return hash
 }
 
-// New creates a new flagset structure for the application
-func New() *FlagSet {
-	return &FlagSet{flagKeys: make(map[string]*flagData)}
-}
-
-// SetDescription sets the description field for a flagset to a value.
-func (f *FlagSet) SetDescription(description string) {
-	f.description = description
+// SetDescription sets the description field for a flagSet to a value.
+func (flagSet *FlagSet) SetDescription(description string) {
+	flagSet.description = description
 }
 
 // MergeConfigFile reads a config file to merge values from.
-func (f *FlagSet) MergeConfigFile(file string) error {
-	return f.readConfigFile(file)
+func (flagSet *FlagSet) MergeConfigFile(file string) error {
+	return flagSet.readConfigFile(file)
 }
 
 // Parse parses the flags provided to the library.
-func (f *FlagSet) Parse() error {
-	flag.CommandLine.Usage = f.usageFunc
+func (flagSet *FlagSet) Parse() error {
+	flag.CommandLine.Usage = flagSet.usageFunc
 	flag.Parse()
 
 	appName := filepath.Base(os.Args[0])
 	// trim extension from app name
 	appName = strings.TrimSuffix(appName, filepath.Ext(appName))
-	homepath, err := os.UserHomeDir()
+	homePath, err := os.UserHomeDir()
 	if err != nil {
 		return err
 	}
 
-	config := filepath.Join(homepath, ".config", appName, "config.yaml")
+	config := filepath.Join(homePath, ".config", appName, "config.yaml")
 	_ = os.MkdirAll(filepath.Dir(config), os.ModePerm)
 	if _, err := os.Stat(config); os.IsNotExist(err) {
-		configData := f.generateDefaultConfig()
+		configData := flagSet.generateDefaultConfig()
 		return ioutil.WriteFile(config, configData, os.ModePerm)
 	}
-	f.MergeConfigFile(config) // try to read default config after parsing flags
+	flagSet.MergeConfigFile(config) // try to read default config after parsing flags
 	return nil
 }
 
 // generateDefaultConfig generates a default YAML config file for a flagset.
-func (f *FlagSet) generateDefaultConfig() []byte {
+func (flagSet *FlagSet) generateDefaultConfig() []byte {
 	hashes := make(map[string]struct{})
 	configBuffer := &bytes.Buffer{}
 	configBuffer.WriteString("# ")
@@ -86,49 +93,50 @@ func (f *FlagSet) generateDefaultConfig() []byte {
 	configBuffer.WriteString(" config file\n# generated by https://github.com/projectdiscovery/goflags\n\n")
 
 	// Attempts to marshal natively if proper flag is set, in case of errors fallback to normal mechanism
-	if f.Marshal {
-		flagset := make(map[string]interface{})
-		for k, v := range f.flagKeys {
-			flagset[k] = v.defaultValue
-		}
+	if flagSet.Marshal {
+		flagsToMarshall := make(map[string]interface{})
+
+		flagSet.flagKeys.forEach(func(key string, data *flagData) {
+			flagsToMarshall[key] = data.defaultValue
+		})
 
-		flagsetb, err := yaml.Marshal(flagset)
+		flagSetBytes, err := yaml.Marshal(flagsToMarshall)
 		if err == nil {
-			configBuffer.Write(flagsetb)
+			configBuffer.Write(flagSetBytes)
 			return configBuffer.Bytes()
 		}
 	}
 
-	for _, v := range f.flagKeys {
-		dataHash := v.Hash()
+	flagSet.flagKeys.forEach(func(key string, data *flagData) {
+		dataHash := data.Hash()
 		if _, ok := hashes[dataHash]; ok {
-			continue
+			return
 		}
 		hashes[dataHash] = struct{}{}
 
 		configBuffer.WriteString("# ")
-		configBuffer.WriteString(strings.ToLower(v.usage))
+		configBuffer.WriteString(strings.ToLower(data.usage))
 		configBuffer.WriteString("\n")
 		configBuffer.WriteString("#")
-		configBuffer.WriteString(v.long)
+		configBuffer.WriteString(data.long)
 		configBuffer.WriteString(": ")
-		if s, ok := v.defaultValue.(string); ok {
+		if s, ok := data.defaultValue.(string); ok {
 			configBuffer.WriteString(s)
-		} else if dv, ok := v.defaultValue.(flag.Value); ok {
+		} else if dv, ok := data.defaultValue.(flag.Value); ok {
 			configBuffer.WriteString(dv.String())
 		}
 
-		configBuffer.WriteString("\n")
-		configBuffer.WriteString("\n")
-	}
+		configBuffer.WriteString("\n\n")
+	})
+
 	return bytes.TrimSuffix(configBuffer.Bytes(), []byte("\n\n"))
 }
 
 // readConfigFile reads the config file and returns any flags
 // that might have been set by the config file.
 //
-// Command line flags however always take prcedence over config file ones.
-func (f *FlagSet) readConfigFile(filePath string) error {
+// Command line flags however always take precedence over config file ones.
+func (flagSet *FlagSet) readConfigFile(filePath string) error {
 	file, err := os.Open(filePath)
 	if err != nil {
 		return errors.Wrap(err, "could not open config file")
@@ -166,7 +174,7 @@ func (f *FlagSet) readConfigFile(filePath string) error {
 }
 
 // VarP adds a Var flag with a shortname and longname
-func (f *FlagSet) VarP(field flag.Value, long, short, usage string) {
+func (flagSet *FlagSet) VarP(field flag.Value, long, short, usage string) {
 	flag.Var(field, short, usage)
 	flag.Var(field, long, usage)
 
@@ -176,12 +184,12 @@ func (f *FlagSet) VarP(field flag.Value, long, short, usage string) {
 		long:         long,
 		defaultValue: field,
 	}
-	f.flagKeys[short] = flagData
-	f.flagKeys[long] = flagData
+	flagSet.flagKeys.Set(short, flagData)
+	flagSet.flagKeys.Set(long, flagData)
 }
 
 // Var adds a Var flag with a longname
-func (f *FlagSet) Var(field flag.Value, long, usage string) {
+func (flagSet *FlagSet) Var(field flag.Value, long, usage string) {
 	flag.Var(field, long, usage)
 
 	flagData := &flagData{
@@ -189,21 +197,21 @@ func (f *FlagSet) Var(field flag.Value, long, usage string) {
 		long:         long,
 		defaultValue: field,
 	}
-	f.flagKeys[long] = flagData
+	flagSet.flagKeys.Set(long, flagData)
 }
 
 // StringVarEnv adds a string flag with a shortname and longname with a default value read from env variable
 // with a default value fallback
-func (f *FlagSet) StringVarEnv(field *string, long, short, defaultValue, envName, usage string) {
+func (flagSet *FlagSet) StringVarEnv(field *string, long, short, defaultValue, envName, usage string) {
 	if envValue, exists := os.LookupEnv(envName); exists {
 		defaultValue = envValue
 	}
 
-	f.StringVarP(field, long, short, defaultValue, usage)
+	flagSet.StringVarP(field, long, short, defaultValue, usage)
 }
 
 // StringVarP adds a string flag with a shortname and longname
-func (f *FlagSet) StringVarP(field *string, long, short, defaultValue, usage string) {
+func (flagSet *FlagSet) StringVarP(field *string, long, short, defaultValue, usage string) {
 	flag.StringVar(field, short, defaultValue, usage)
 	flag.StringVar(field, long, defaultValue, usage)
 
@@ -213,12 +221,12 @@ func (f *FlagSet) StringVarP(field *string, long, short, defaultValue, usage str
 		long:         long,
 		defaultValue: defaultValue,
 	}
-	f.flagKeys[short] = flagData
-	f.flagKeys[long] = flagData
+	flagSet.flagKeys.Set(short, flagData)
+	flagSet.flagKeys.Set(long, flagData)
 }
 
 // StringVar adds a string flag with a longname
-func (f *FlagSet) StringVar(field *string, long, defaultValue, usage string) {
+func (flagSet *FlagSet) StringVar(field *string, long, defaultValue, usage string) {
 	flag.StringVar(field, long, defaultValue, usage)
 
 	flagData := &flagData{
@@ -226,11 +234,11 @@ func (f *FlagSet) StringVar(field *string, long, defaultValue, usage string) {
 		long:         long,
 		defaultValue: defaultValue,
 	}
-	f.flagKeys[long] = flagData
+	flagSet.flagKeys.Set(long, flagData)
 }
 
 // BoolVarP adds a bool flag with a shortname and longname
-func (f *FlagSet) BoolVarP(field *bool, long, short string, defaultValue bool, usage string) {
+func (flagSet *FlagSet) BoolVarP(field *bool, long, short string, defaultValue bool, usage string) {
 	flag.BoolVar(field, short, defaultValue, usage)
 	flag.BoolVar(field, long, defaultValue, usage)
 
@@ -240,12 +248,12 @@ func (f *FlagSet) BoolVarP(field *bool, long, short string, defaultValue bool, u
 		long:         long,
 		defaultValue: strconv.FormatBool(defaultValue),
 	}
-	f.flagKeys[short] = flagData
-	f.flagKeys[long] = flagData
+	flagSet.flagKeys.Set(short, flagData)
+	flagSet.flagKeys.Set(long, flagData)
 }
 
 // BoolVar adds a bool flag with a longname
-func (f *FlagSet) BoolVar(field *bool, long string, defaultValue bool, usage string) {
+func (flagSet *FlagSet) BoolVar(field *bool, long string, defaultValue bool, usage string) {
 	flag.BoolVar(field, long, defaultValue, usage)
 
 	flagData := &flagData{
@@ -253,11 +261,11 @@ func (f *FlagSet) BoolVar(field *bool, long string, defaultValue bool, usage str
 		long:         long,
 		defaultValue: strconv.FormatBool(defaultValue),
 	}
-	f.flagKeys[long] = flagData
+	flagSet.flagKeys.Set(long, flagData)
 }
 
 // IntVarP adds a int flag with a shortname and longname
-func (f *FlagSet) IntVarP(field *int, long, short string, defaultValue int, usage string) {
+func (flagSet *FlagSet) IntVarP(field *int, long, short string, defaultValue int, usage string) {
 	flag.IntVar(field, short, defaultValue, usage)
 	flag.IntVar(field, long, defaultValue, usage)
 
@@ -267,12 +275,12 @@ func (f *FlagSet) IntVarP(field *int, long, short string, defaultValue int, usag
 		long:         long,
 		defaultValue: strconv.Itoa(defaultValue),
 	}
-	f.flagKeys[short] = flagData
-	f.flagKeys[long] = flagData
+	flagSet.flagKeys.Set(short, flagData)
+	flagSet.flagKeys.Set(long, flagData)
 }
 
 // IntVar adds a int flag with a longname
-func (f *FlagSet) IntVar(field *int, long string, defaultValue int, usage string) {
+func (flagSet *FlagSet) IntVar(field *int, long string, defaultValue int, usage string) {
 	flag.IntVar(field, long, defaultValue, usage)
 
 	flagData := &flagData{
@@ -280,11 +288,11 @@ func (f *FlagSet) IntVar(field *int, long string, defaultValue int, usage string
 		long:         long,
 		defaultValue: strconv.Itoa(defaultValue),
 	}
-	f.flagKeys[long] = flagData
+	flagSet.flagKeys.Set(long, flagData)
 }
 
 // StringSliceVarP adds a string slice flag with a shortname and longname
-func (f *FlagSet) StringSliceVarP(field *StringSlice, long, short string, defaultValue []string, usage string) {
+func (flagSet *FlagSet) StringSliceVarP(field *StringSlice, long, short string, defaultValue []string, usage string) {
 	for _, item := range defaultValue {
 		_ = field.Set(item)
 	}
@@ -310,12 +318,12 @@ func (f *FlagSet) StringSliceVarP(field *StringSlice, long, short string, defaul
 		long:         long,
 		defaultValue: defaultBuilder.String(),
 	}
-	f.flagKeys[short] = flagData
-	f.flagKeys[long] = flagData
+	flagSet.flagKeys.Set(short, flagData)
+	flagSet.flagKeys.Set(long, flagData)
 }
 
 // StringSliceVar adds a string slice flag with a longname
-func (f *FlagSet) StringSliceVar(field *StringSlice, long string, defaultValue []string, usage string) {
+func (flagSet *FlagSet) StringSliceVar(field *StringSlice, long string, defaultValue []string, usage string) {
 	for _, item := range defaultValue {
 		_ = field.Set(item)
 	}
@@ -339,19 +347,21 @@ func (f *FlagSet) StringSliceVar(field *StringSlice, long string, defaultValue [
 		long:         long,
 		defaultValue: defaultBuilder.String(),
 	}
-	f.flagKeys[long] = flagData
+	flagSet.flagKeys.Set(long, flagData)
 }
 
-func (f *FlagSet) usageFunc() {
+func (flagSet *FlagSet) usageFunc() {
 	hashes := make(map[string]struct{})
 
-	fmt.Fprintf(flag.CommandLine.Output(), "%s\n\n", f.description)
-	fmt.Fprintf(flag.CommandLine.Output(), "Usage:\n  %s [flags]\n\n", os.Args[0])
-	fmt.Fprintf(flag.CommandLine.Output(), "Flags:\n")
+	cliOutput := flag.CommandLine.Output()
+	fmt.Fprintf(cliOutput, "%s\n\n", flagSet.description)
+	fmt.Fprintf(cliOutput, "Usage:\n  %s [flags]\n\n", os.Args[0])
+	fmt.Fprintf(cliOutput, "Flags:\n")
 
-	w := tabwriter.NewWriter(flag.CommandLine.Output(), 0, 0, 1, ' ', 0)
-	flag.CommandLine.VisitAll(func(fl *flag.Flag) {
-		data, _ := f.flagKeys[fl.Name]
+	writer := tabwriter.NewWriter(cliOutput, 0, 0, 1, ' ', 0)
+
+	flagSet.flagKeys.forEach(func(key string, data *flagData) {
+		currentFlag := flag.CommandLine.Lookup(key)
 
 		dataHash := data.Hash()
 		if _, ok := hashes[dataHash]; ok {
@@ -359,30 +369,78 @@ func (f *FlagSet) usageFunc() {
 		}
 		hashes[dataHash] = struct{}{}
 
-		var s string
-		if data.short != "" {
-			s = fmt.Sprintf("  \t-%s, -%s", data.short, data.long) // Two spaces before -; see next two comments.
-		} else {
-			s = fmt.Sprintf("  \t-%s", data.long)
-		}
-		name, usage := flag.UnquoteUsage(fl)
-		if len(name) > 0 {
-			s += " " + name
+		result := createUsageString(data, currentFlag)
+		fmt.Fprint(writer, result, "\n")
+	})
+	writer.Flush()
+}
+
+func isNotBlank(value string) bool {
+	return len(strings.TrimSpace(value)) != 0
+}
+
+func createUsageString(data *flagData, currentFlag *flag.Flag) string {
+	valueType := reflect.TypeOf(currentFlag.Value)
+
+	result := createUsageFlagNames(data)
+	result += createUsageTypeAndDescription(currentFlag, valueType)
+	result += createUsageDefaultValue(data, currentFlag, valueType)
+
+	return result
+}
+
+func createUsageDefaultValue(data *flagData, currentFlag *flag.Flag, valueType reflect.Type) string {
+	if !isZeroValue(currentFlag, currentFlag.DefValue) {
+		defaultValueTemplate := " (default "
+		switch valueType.String() { // ugly hack because "flag.stringValue" is not exported from the parent library
+		case "*flag.stringValue":
+			defaultValueTemplate += "%q"
+		default:
+			defaultValueTemplate += "%v"
 		}
-		s += "\t\t"
-		s += strings.ReplaceAll(usage, "\n", "\n    \t")
-
-		if !isZeroValue(fl, fl.DefValue) {
-			if _, ok := fl.Value.(*stringValue); ok {
-				// put quotes on the value
-				s += fmt.Sprintf(" (default %q)", fl.DefValue)
-			} else {
-				s += fmt.Sprintf(" (default %v)", fl.DefValue)
+		defaultValueTemplate += ")"
+		return fmt.Sprintf(defaultValueTemplate, data.defaultValue)
+	}
+	return ""
+}
+
+func createUsageTypeAndDescription(currentFlag *flag.Flag, valueType reflect.Type) string {
+	var result string
+	flagDisplayType, usage := flag.UnquoteUsage(currentFlag)
+	if len(flagDisplayType) > 0 {
+		if flagDisplayType == "value" {
+			var stringSlicePointerType *StringSlice
+			if valueType == reflect.TypeOf(stringSlicePointerType) { // refactor safe check
+				flagDisplayType = "string[]"
 			}
 		}
-		fmt.Fprint(w, s, "\n")
-	})
-	w.Flush()
+		result += " " + flagDisplayType
+	}
+
+	result += "\t\t"
+	result += strings.ReplaceAll(usage, "\n", "\n"+strings.Repeat(" ", 4)+"\t")
+	return result
+}
+
+func createUsageFlagNames(data *flagData) string {
+	flagNames := strings.Repeat(" ", 2) + "\t"
+
+	var validFlags []string
+	addValidParam := func(value string) {
+		if isNotBlank(value) {
+			validFlags = append(validFlags, fmt.Sprintf("-%s", value))
+		}
+	}
+
+	addValidParam(data.short)
+	addValidParam(data.long)
+
+	if len(validFlags) == 0 {
+		panic("CLI arguments cannot be empty.")
+	}
+
+	flagNames += strings.Join(validFlags, ", ")
+	return flagNames
 }
 
 // isZeroValue determines whether the string represents the zero
@@ -391,12 +449,12 @@ func isZeroValue(f *flag.Flag, value string) bool {
 	// Build a zero value of the flag's Value type, and see if the
 	// result of calling its String method equals the value passed in.
 	// This works unless the Value type is itself an interface type.
-	typ := reflect.TypeOf(f.Value)
-	var z reflect.Value
-	if typ.Kind() == reflect.Ptr {
-		z = reflect.New(typ.Elem())
+	valueType := reflect.TypeOf(f.Value)
+	var zeroValue reflect.Value
+	if valueType.Kind() == reflect.Ptr {
+		zeroValue = reflect.New(valueType.Elem())
 	} else {
-		z = reflect.Zero(typ)
+		zeroValue = reflect.Zero(valueType)
 	}
-	return value == z.Interface().(flag.Value).String()
+	return value == zeroValue.Interface().(flag.Value).String()
 }
diff --git a/insertionorderedmap.go b/insertionorderedmap.go
new file mode 100644
index 0000000..539c8a2
--- /dev/null
+++ b/insertionorderedmap.go
@@ -0,0 +1,20 @@
+package goflags
+
+type InsertionOrderedMap struct {
+	values map[string]*flagData
+	keys   []string `yaml:"-"`
+}
+
+func (insertionOrderedMap *InsertionOrderedMap) forEach(fn func(key string, data *flagData)) {
+	for _, key := range insertionOrderedMap.keys {
+		fn(key, insertionOrderedMap.values[key])
+	}
+}
+
+func (insertionOrderedMap *InsertionOrderedMap) Set(key string, value *flagData) {
+	_, present := insertionOrderedMap.values[key]
+	insertionOrderedMap.values[key] = value
+	if !present {
+		insertionOrderedMap.keys = append(insertionOrderedMap.keys, key)
+	}
+}
diff --git a/misc.go b/misc.go
index ef2c7c5..e8ba61f 100644
--- a/misc.go
+++ b/misc.go
@@ -2,32 +2,15 @@ package goflags
 
 import "strings"
 
-// -- string Value
-type stringValue string
-
-func newStringValue(val string, p *string) *stringValue {
-	*p = val
-	return (*stringValue)(p)
-}
-
-func (s *stringValue) Set(val string) error {
-	*s = stringValue(val)
-	return nil
-}
-
-func (s *stringValue) Get() interface{} { return string(*s) }
-
-func (s *stringValue) String() string { return string(*s) }
-
 // StringSlice is a slice of strings
 type StringSlice []string
 
-func (i *StringSlice) String() string {
-	return strings.Join(*i, " ")
+func (stringSlice *StringSlice) String() string {
+	return strings.Join(*stringSlice, " ")
 }
 
 // Set appends a value to the string slice.
-func (i *StringSlice) Set(value string) error {
-	*i = append(*i, value)
+func (stringSlice *StringSlice) Set(value string) error {
+	*stringSlice = append(*stringSlice, value)
 	return nil
 }


Test patch:
diff --git a/goflags_test.go b/goflags_test.go
index 986f6ac..a4a8419 100644
--- a/goflags_test.go
+++ b/goflags_test.go
@@ -1,15 +1,18 @@
 package goflags
 
 import (
+	"flag"
+	"github.com/stretchr/testify/assert"
 	"io/ioutil"
 	"os"
+	"strconv"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 )
 
 func TestGenerateDefaultConfig(t *testing.T) {
-	f := New()
+	flagSet := NewFlagSet()
 
 	example := `# goflags.test config file
 # generated by https://github.com/projectdiscovery/goflags
@@ -22,40 +25,122 @@ func TestGenerateDefaultConfig(t *testing.T) {
 
 	var data string
 	var data2 StringSlice
-	f.StringVar(&data, "test", "test-default-value", "Default value for a test flag example")
-	f.StringSliceVar(&data2, "slice", []string{"item1", "item2"}, "String slice flag example value")
-	require.Equal(t, example, string(f.generateDefaultConfig()), "could not get correct default config")
+	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")
+	require.Equal(t, example, string(flagSet.generateDefaultConfig()), "could not get correct default config")
+
+	tearDown(t.Name())
 }
 
 func TestConfigFileDataTypes(t *testing.T) {
-	f := New()
-
+	flagSet := NewFlagSet()
 	var data string
 	var data2 StringSlice
 	var data3 int
 	var data4 bool
 
-	f.StringVar(&data, "string-value", "", "Default value for a test flag example")
-	f.StringSliceVar(&data2, "slice-value", []string{}, "String slice flag example value")
-	f.IntVar(&data3, "int-value", 0, "Int value example")
-	f.BoolVar(&data4, "bool-value", false, "Bool value example")
+	flagSet.StringVar(&data, "string-value", "", "Default value for a test flag example")
+	flagSet.StringSliceVar(&data2, "slice-value", []string{}, "String slice flag example value")
+	flagSet.IntVar(&data3, "int-value", 0, "Int value example")
+	flagSet.BoolVar(&data4, "bool-value", false, "Bool value example")
 
 	configFileData := `
 string-value: test
-slice-value: 
-  - test
-  - test2
+slice-value:
+ - test
+ - test2
 int-value: 543
 bool-value: true`
 	err := ioutil.WriteFile("test.yaml", []byte(configFileData), os.ModePerm)
 	require.Nil(t, err, "could not write temporary config")
 	defer os.Remove("test.yaml")
 
-	err = f.MergeConfigFile("test.yaml")
+	err = flagSet.MergeConfigFile("test.yaml")
 	require.Nil(t, err, "could not merge temporary config")
 
 	require.Equal(t, "test", data, "could not get correct string")
 	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")
+
+	tearDown(t.Name())
+}
+
+func TestUsageOrder(t *testing.T) {
+	flagSet := NewFlagSet()
+
+	var stringData string
+	var stringSliceData StringSlice
+	var stringSliceData2 StringSlice
+	var intData int
+	var boolData bool
+
+	flagSet.StringVar(&stringData, "string-value", "", "String example value example")
+	flagSet.StringVarP(&stringData, "", "ts2", "test-string", "String with default value example #2")
+	flagSet.StringVar(&stringData, "string-with-default-value", "test-string", "String with default value example")
+	flagSet.StringVarP(&stringData, "string-with-default-value2", "ts", "test-string", "String with default value example #2")
+	flagSet.StringSliceVar(&stringSliceData, "slice-value", []string{}, "String slice flag example value")
+	flagSet.StringSliceVarP(&stringSliceData, "slice-value2", "sv", []string{}, "String slice flag example value #2")
+	flagSet.StringSliceVar(&stringSliceData, "slice-with-default-value", []string{"a", "b", "c"}, "String slice flag with default example values")
+	flagSet.StringSliceVarP(&stringSliceData2, "slice-with-default-value2", "swdf", []string{"a", "b", "c"}, "String slice flag with default example values #2")
+	flagSet.IntVar(&intData, "int-value", 0, "Int value example")
+	flagSet.IntVarP(&intData, "int-value2", "iv", 0, "Int value example #2")
+	flagSet.IntVar(&intData, "int-with-default-value", 12, "Int with default value example")
+	flagSet.IntVarP(&intData, "int-with-default-value2", "iwdv", 12, "Int with default value example #2")
+	flagSet.BoolVar(&boolData, "bool-value", false, "Bool value example")
+	flagSet.BoolVarP(&boolData, "bool-value2", "bv", false, "Bool value example #2")
+	flagSet.BoolVar(&boolData, "bool-with-default-value", true, "Bool with default value example")
+	flagSet.BoolVarP(&boolData, "bool-with-default-value2", "bwdv", true, "Bool with default value example #2")
+
+	flagSet.usageFunc()
+
+	tearDown(t.Name())
+}
+
+func TestIncorrectStringFlagsCausePanic(t *testing.T) {
+	flagSet := NewFlagSet()
+	var stringData string
+
+	flagSet.StringVar(&stringData, "", "test-string", "String with default value example")
+	assert.Panics(t, flagSet.usageFunc)
+
+	tearDown(t.Name())
+}
+
+func TestIncorrectFlagsCausePanic(t *testing.T) {
+	type flagPair struct {
+		Short, Long string
+	}
+
+	createTestParameters := func() []flagPair {
+		var result []flagPair
+		result = append(result, flagPair{"", ""})
+
+		badFlagNames := [...]string{" ", "\t", "\n"}
+		for _, badFlagName := range badFlagNames {
+			result = append(result, flagPair{"", badFlagName})
+			result = append(result, flagPair{badFlagName, ""})
+			result = append(result, flagPair{badFlagName, badFlagName})
+		}
+		return result
+	}
+
+	for index, tuple := range createTestParameters() {
+		uniqueName := strconv.Itoa(index)
+		t.Run(uniqueName, func(t *testing.T) {
+			assert.Panics(t, func() {
+				tearDown(uniqueName)
+
+				flagSet := NewFlagSet()
+				var stringData string
+
+				flagSet.StringVarP(&stringData, tuple.Short, tuple.Long, "test-string", "String with default value example")
+				flagSet.usageFunc()
+			})
+		})
+	}
+}
+
+func tearDown(uniqueValue string) { // sadly there is no official support for setup/teardown/test
+	flag.CommandLine = flag.NewFlagSet(uniqueValue, flag.PanicOnError)
 }