Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,16 @@ func catchPanic(err any) {

func preprocessArgs() {
rearrangeArgs()
argsBeforeDelimiter := os.Args
if i := slices.Index(os.Args, "--"); i >= 0 {
argsBeforeDelimiter = os.Args[:i]
}
// normal logic
// load config if the args do not contains -no-config
if !slices.ContainsFunc(os.Args, hasNoConfig) {
if !slices.ContainsFunc(argsBeforeDelimiter, hasNoConfig) {
defaultArgs, err := config.Load()
// if successfully load config and **the config.Args do not contain -no-config**
if err == nil && !slices.ContainsFunc(defaultArgs.Args, hasNoConfig) {
if err == nil && defaultArgs != nil && !slices.ContainsFunc(defaultArgs.Args, hasNoConfig) {
os.Args = slices.Insert(os.Args, 1, defaultArgs.Args...)
} else if err != nil { // if failed to load config
// if it's read error
Expand All @@ -66,7 +70,7 @@ func preprocessArgs() {
} else {
// contains -no-config
// remove it before the cli.G starts
os.Args = slices.DeleteFunc(os.Args, hasNoConfig)
os.Args = append(slices.DeleteFunc(argsBeforeDelimiter, hasNoConfig), os.Args[len(argsBeforeDelimiter):]...)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Modifying argsBeforeDelimiter in-place using slices.DeleteFunc is risky and hard to read because argsBeforeDelimiter is a slice of os.Args (sharing the same underlying array). This directly mutates the prefix of os.Args in-place before appending the rest of the arguments, which relies on implicit overlapping slice copy behavior in append.\n\nConstructing a new slice is much safer, clearer, and has negligible performance overhead since os.Args is very small.

Suggested change
os.Args = append(slices.DeleteFunc(argsBeforeDelimiter, hasNoConfig), os.Args[len(argsBeforeDelimiter):]...)
filtered := make([]string, 0, len(argsBeforeDelimiter))
for _, arg := range argsBeforeDelimiter {
if !hasNoConfig(arg) {
filtered = append(filtered, arg)
}
}
os.Args = append(filtered, os.Args[len(argsBeforeDelimiter):]...)

}
}

Expand All @@ -86,11 +90,8 @@ func separateArgs(args []string) (flags, paths []string) {
arg := args[i]
if arg == "--" {
hasDoubleDash = true
if i+1 < len(args) {
paths = append(paths, args[i+1])
i++
}
continue
paths = append(paths, args[i+1:]...)
break
}
if strings.HasPrefix(arg, "--") {
i = handleLongFlag(arg, args, i, &flags, &expectValue, flagsWithArgs)
Expand Down
65 changes: 63 additions & 2 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,61 @@ func Test_preprocessArgs(t *testing.T) {
assert.Equal(t, 2, len(os.Args))
}

func TestPreprocessArgs_DelimiterProtectsNoConfigPaths(t *testing.T) {
originalArgs := os.Args
t.Cleanup(func() { os.Args = originalArgs })
tests := []struct {
name string
args []string
expectConfigLoad bool
expectedArgs []string
}{
{
name: "suffix paths do not disable config",
args: []string{"g", "--", "--no-config", "-no-config"},
expectConfigLoad: true,
expectedArgs: []string{"g", "--", "--no-config", "-no-config"},
},
{
name: "prefix flag disables config without removing suffix paths",
args: []string{"g", "--no-config", "--", "--no-config", "-no-config"},
expectConfigLoad: false,
expectedArgs: []string{"g", "--", "--no-config", "-no-config"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
configLoaded := false
patch := gomonkey.ApplyFunc(config.Load, func() (*config.Config, error) {
configLoaded = true
return &config.Config{}, nil
})
defer patch.Reset()
os.Args = append([]string{}, tt.args...)

preprocessArgs()

assert.Equal(t, tt.expectConfigLoad, configLoaded)
require.Equal(t, tt.expectedArgs, os.Args)
})
}
}

func TestPreprocessArgs_NilConfigKeepsDelimiterPaths(t *testing.T) {
originalArgs := os.Args
t.Cleanup(func() { os.Args = originalArgs })
patch := gomonkey.ApplyFunc(config.Load, func() (*config.Config, error) {
return nil, nil
})
t.Cleanup(patch.Reset)
os.Args = []string{"g", "--", "--no-config"}

assert.NotPanics(t, preprocessArgs)

require.Equal(t, []string{"g", "--", "--no-config"}, os.Args)
}

func TestSeparateArgs(t *testing.T) {
originalFlags := cli.G.Flags
defer func() { cli.G.Flags = originalFlags }()
Expand Down Expand Up @@ -118,6 +173,12 @@ func TestSeparateArgs(t *testing.T) {
expectedFlags: []string{"--all", "--"},
expectedPaths: []string{"dir1", "--sort", "name"},
},
{
name: "Delimiter protects all following paths",
args: []string{"--", "a", "--bad"},
expectedFlags: []string{"--"},
expectedPaths: []string{"a", "--bad"},
},
{
name: "Short flags",
args: []string{"-a", "-s", "name", "dir1"},
Expand All @@ -133,8 +194,8 @@ func TestSeparateArgs(t *testing.T) {
{
name: "Complex case with double dash",
args: []string{"--all", "dir1", "--term-width", "100", "-s", "name", "--", "-a", "-a", "-l", "dir2", "--", "--fake-flag"},
expectedFlags: []string{"--all", "--term-width", "100", "-s", "name", "-a", "-l", "--"},
expectedPaths: []string{"dir1", "-a", "dir2", "--fake-flag"},
expectedFlags: []string{"--all", "--term-width", "100", "-s", "name", "--"},
expectedPaths: []string{"dir1", "-a", "-a", "-l", "dir2", "--", "--fake-flag"},
},
}

Expand Down