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

Reorder args #159

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 40 additions & 1 deletion goflags.go
Expand Up @@ -109,7 +109,8 @@ func (flagSet *FlagSet) MergeConfigFile(file string) error {
func (flagSet *FlagSet) Parse() error {
flagSet.CommandLine.SetOutput(os.Stdout)
flagSet.CommandLine.Usage = flagSet.usageFunc
_ = flagSet.CommandLine.Parse(os.Args[1:])
args := flagSet.reOrderArgs(os.Args[1:])
_ = flagSet.CommandLine.Parse(args)
configFilePath, _ := flagSet.GetConfigFilePath()

// migrate data from old config dir to new one
Expand All @@ -132,6 +133,44 @@ func (flagSet *FlagSet) Parse() error {
return nil
}

// FIXME: This function is a workaround for the issue where Go's flag package
// stops parsing after the first non-flag argument. This function rearranges the
// arguments to ensure that all flags (including those after non-flag arguments)
// are parsed correctly. Consider revising this approach for a more robust solution
// in future iterations.
// References:
// https://github.com/golang/go/issues/3869
// https://stackoverflow.com/questions/69384797/is-a-bug-flag-parse-could-not-get-values-after-bool-arg
func (flagSet *FlagSet) reOrderArgs(args []string) []string {
var reorderedArgs []string
var nonFlagArgs []string

for i := 0; i < len(args); i++ {
arg := args[i]

if !strings.HasPrefix(arg, "-") {
nonFlagArgs = append(nonFlagArgs, arg)
continue
}

reorderedArgs = append(reorderedArgs, arg)
if fl := flagSet.CommandLine.Lookup(strings.TrimPrefix(arg, "-")); fl != nil {
if _, isBoolFlag := fl.Value.(interface{ IsBoolFlag() bool }); isBoolFlag {
if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
nonFlagArgs = append(nonFlagArgs, args[i+1])
i++
}
} else if i+1 < len(args) {
reorderedArgs = append(reorderedArgs, args[i+1])
i++
}
}
}

reorderedArgs = append(reorderedArgs, nonFlagArgs...)
return reorderedArgs
}

// AttemptConfigMigration attempts to migrate config from old config dir to new one
// migration condition
// 1. old config dir exists
Expand Down
58 changes: 58 additions & 0 deletions goflags_test.go
Expand Up @@ -417,6 +417,64 @@ func TestCaseSensitiveFlagSet(t *testing.T) {
assert.Equal(t, expected, actual)
}

func TestReArrangeArgs(t *testing.T) {
fs := NewFlagSet()
fs.CommandLine = flag.NewFlagSet("test", flag.ContinueOnError)
var boolVar bool
var stringVar string
fs.BoolVar(&boolVar, "bool", false, "a bool flag")
fs.StringVar(&stringVar, "string", "default", "a string flag")

// Define test cases
tests := []struct {
name string
args []string
expected []string
}{
{
name: "No flags",
args: []string{"arg1", "arg2"},
expected: []string{"arg1", "arg2"},
},
{
name: "Boolean flag",
args: []string{"-bool", "arg1"},
expected: []string{"-bool", "arg1"},
},
{
name: "String flag",
args: []string{"-string", "value", "arg1"},
expected: []string{"-string", "value", "arg1"},
},
{
name: "Mixed flags and args",
args: []string{"-bool", "arg1", "-string", "value", "arg2"},
expected: []string{"-bool", "-string", "value", "arg1", "arg2"},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result := fs.reOrderArgs(tc.args)
if !equalSlices(result, tc.expected) {
t.Errorf("Test %s failed: expected %v, got %v", tc.name, tc.expected, result)
}
})
}
}

func equalSlices(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}

func tearDown(uniqueValue string) {
flag.CommandLine = flag.NewFlagSet(uniqueValue, flag.ContinueOnError)
flag.CommandLine.Usage = flag.Usage
Expand Down