Skip to content

Commit 03c7b59

Browse files
committed
Add test cases and implementation for removing flag and value
1 parent 08d316f commit 03c7b59

File tree

2 files changed

+141
-8
lines changed

2 files changed

+141
-8
lines changed

pkg/utils/slice_test.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,105 @@ func TestSliceRemoveFlag(t *testing.T) {
150150
})
151151
}
152152
}
153+
154+
func TestSliceRemoveFlagAndValue(t *testing.T) {
155+
testCases := []struct {
156+
name string
157+
input []string
158+
flagName string
159+
expected []string
160+
}{
161+
{
162+
name: "remove flag without value",
163+
input: []string{"--flag1", "value1", "--flag2", "value2"},
164+
flagName: "flag1",
165+
expected: []string{"--flag2", "value2"},
166+
},
167+
{
168+
name: "remove flag with spaced value",
169+
input: []string{"--flag1", "value1", "--flag2", "value2"},
170+
flagName: "flag1",
171+
expected: []string{"--flag2", "value2"},
172+
},
173+
{
174+
name: "remove flag with equals value",
175+
input: []string{"--flag1=value1", "--flag2", "value2"},
176+
flagName: "flag1",
177+
expected: []string{"--flag2", "value2"},
178+
},
179+
{
180+
name: "remove flag with spaced value followed by another flag",
181+
input: []string{"--flag1", "value1", "--flag2", "value2"},
182+
flagName: "flag1",
183+
expected: []string{"--flag2", "value2"},
184+
},
185+
{
186+
name: "remove flag with spaced value followed by non-flag",
187+
input: []string{"--flag1", "value1", "other", "args"},
188+
flagName: "flag1",
189+
expected: []string{"other", "args"},
190+
},
191+
{
192+
name: "remove flag with spaced value followed by another flag",
193+
input: []string{"--flag1", "value1", "--flag2", "value2"},
194+
flagName: "flag1",
195+
expected: []string{"--flag2", "value2"},
196+
},
197+
{
198+
name: "remove multiple occurrences",
199+
input: []string{"--flag1", "value1", "--flag1", "value2", "other"},
200+
flagName: "flag1",
201+
expected: []string{"other"},
202+
},
203+
{
204+
name: "flag not present",
205+
input: []string{"--flag1", "value1", "--flag2", "value2"},
206+
flagName: "flag3",
207+
expected: []string{"--flag1", "value1", "--flag2", "value2"},
208+
},
209+
{
210+
name: "empty slice",
211+
input: []string{},
212+
flagName: "flag1",
213+
expected: []string{},
214+
},
215+
{
216+
name: "nil slice",
217+
input: nil,
218+
flagName: "flag1",
219+
expected: nil,
220+
},
221+
{
222+
name: "empty flag name",
223+
input: []string{"--flag1", "value1"},
224+
flagName: "",
225+
expected: []string{"--flag1", "value1"},
226+
},
227+
{
228+
name: "flag at end without value",
229+
input: []string{"other", "--flag1"},
230+
flagName: "flag1",
231+
expected: []string{"other"},
232+
},
233+
{
234+
name: "flag at end with value",
235+
input: []string{"other", "--flag1", "value1"},
236+
flagName: "flag1",
237+
expected: []string{"other"},
238+
},
239+
{
240+
name: "mixed flag forms",
241+
input: []string{"--flag1", "value1", "--flag1=value2", "other"},
242+
flagName: "flag1",
243+
expected: []string{"other"},
244+
},
245+
}
246+
247+
for _, tc := range testCases {
248+
tc := tc // rebind to avoid range-variable capture
249+
t.Run(tc.name, func(t *testing.T) {
250+
result := SliceRemoveFlagAndValue(tc.input, tc.flagName)
251+
assert.Equal(t, tc.expected, result)
252+
})
253+
}
254+
}

pkg/utils/slice_utils.go

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import (
66
"strings"
77
)
88

9-
// Package-level sentinel errors for slice operations
9+
// Package-level sentinel errors for slice operations.
1010
var (
1111
ErrNilInput = errors.New("input must not be nil")
1212
ErrNonStringElement = errors.New("element is not a string")
1313
)
1414

15-
// SliceContainsString checks if a string is present in a slice
15+
// SliceContainsString checks if a string is present in a slice.
1616
func SliceContainsString(s []string, str string) bool {
1717
for _, v := range s {
1818
if v == str {
@@ -22,7 +22,7 @@ func SliceContainsString(s []string, str string) bool {
2222
return false
2323
}
2424

25-
// SliceContainsInt checks if an int is present in a slice
25+
// SliceContainsInt checks if an int is present in a slice.
2626
func SliceContainsInt(s []int, i int) bool {
2727
for _, v := range s {
2828
if v == i {
@@ -32,7 +32,7 @@ func SliceContainsInt(s []int, i int) bool {
3232
return false
3333
}
3434

35-
// SliceContainsStringStartsWith checks if a slice contains a string that the given string begins with
35+
// SliceContainsStringStartsWith checks if a slice contains a string that the given string begins with.
3636
func SliceContainsStringStartsWith(s []string, str string) bool {
3737
for _, v := range s {
3838
if strings.HasPrefix(str, v) {
@@ -42,7 +42,7 @@ func SliceContainsStringStartsWith(s []string, str string) bool {
4242
return false
4343
}
4444

45-
// SliceContainsStringHasPrefix checks if a slice contains a string that begins with the given prefix
45+
// SliceContainsStringHasPrefix checks if a slice contains a string that begins with the given prefix.
4646
func SliceContainsStringHasPrefix(s []string, prefix string) bool {
4747
for _, v := range s {
4848
if strings.HasPrefix(v, prefix) {
@@ -95,7 +95,10 @@ func SliceOfInterfacesToSliceOfStringsWithTypeAssertion(input []any) ([]string,
9595
func SliceRemoveString(slice []string, str string) []string {
9696
for i, v := range slice {
9797
if v == str {
98-
return append(slice[:i], slice[i+1:]...)
98+
// Avoid retaining reference to the removed element.
99+
copy(slice[i:], slice[i+1:])
100+
slice[len(slice)-1] = ""
101+
return slice[:len(slice)-1]
99102
}
100103
}
101104
return slice
@@ -108,16 +111,20 @@ func SliceRemoveFlag(slice []string, flagName string) []string {
108111
if slice == nil {
109112
return nil
110113
}
114+
if flagName == "" {
115+
// No-op for empty flag names.
116+
return append([]string(nil), slice...)
117+
}
111118

112119
result := make([]string, 0, len(slice))
113120
flagPrefix := "--" + flagName + "="
114121

115122
for _, item := range slice {
116-
// Skip exact flag matches (--flag)
123+
// Skip exact flag matches (--flag).
117124
if item == "--"+flagName {
118125
continue
119126
}
120-
// Skip flag with value matches (--flag=value)
127+
// Skip flag with value matches (--flag=value).
121128
if strings.HasPrefix(item, flagPrefix) {
122129
continue
123130
}
@@ -126,3 +133,27 @@ func SliceRemoveFlag(slice []string, flagName string) []string {
126133

127134
return result
128135
}
136+
137+
// SliceRemoveFlagAndValue removes --flag and an optional following value (if the next arg
138+
// does not start with "-"). It preserves order of remaining args.
139+
func SliceRemoveFlagAndValue(args []string, flagName string) []string {
140+
if args == nil || flagName == "" {
141+
return append([]string(nil), args...)
142+
}
143+
out := make([]string, 0, len(args))
144+
for i := 0; i < len(args); i++ {
145+
arg := args[i]
146+
if arg == "--"+flagName {
147+
// Skip the flag and (optionally) its value.
148+
if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
149+
i++
150+
}
151+
continue
152+
}
153+
if strings.HasPrefix(arg, "--"+flagName+"=") {
154+
continue
155+
}
156+
out = append(out, arg)
157+
}
158+
return out
159+
}

0 commit comments

Comments
 (0)