Skip to content

Commit a052808

Browse files
committed
Complete repeatable flag & option instances after the first in bash.
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
1 parent ab21374 commit a052808

File tree

3 files changed

+107
-97
lines changed

3 files changed

+107
-97
lines changed

Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift

Lines changed: 29 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,17 @@ extension CommandInfoV0 {
4848
#
4949
# required variables:
5050
#
51-
# - flags: the flags that the current (sub)command can accept
52-
# - options: the options that the current (sub)command can accept
51+
# - repeating_flags: the repeating flags that the current (sub)command can accept
52+
# - non_repeating_flags: the non-repeating flags that the current (sub)command can accept
53+
# - repeating_options: the repeating options that the current (sub)command can accept
54+
# - non_repeating_options: the non-repeating options that the current (sub)command can accept
5355
# - positional_number: value ignored
5456
# - unparsed_words: unparsed words from the current command line
5557
#
5658
# modified variables:
5759
#
58-
# - flags: remove flags for this (sub)command that are already on the command line
59-
# - options: remove options for this (sub)command that are already on the command line
60+
# - non_repeating_flags: remove flags for this (sub)command that are already on the command line
61+
# - non_repeating_options: remove options for this (sub)command that are already on the command line
6062
# - positional_number: set to the current positional number
6163
# - unparsed_words: remove all flags, options, and option values for this (sub)command
6264
\(offerFlagsOptionsFunctionName)() {
@@ -93,26 +95,26 @@ extension CommandInfoV0 {
9395
# ${word} is a flag or an option
9496
# If ${word} is an option, mark that the next word to be parsed is an option value
9597
local option
96-
for option in "${options[@]}"; do
98+
for option in "${repeating_options[@]}" "${non_repeating_options[@]}"; do
9799
[[ "${word}" = "${option}" ]] && is_parsing_option_value=true && break
98100
done
99101
100-
# Remove ${word} from ${flags} or ${options} so it isn't offered again
102+
# Remove ${word} from ${non_repeating_flags} or ${non_repeating_options} so it isn't offered again
101103
local not_found=true
102104
local -i index
103-
for index in "${!flags[@]}"; do
104-
if [[ "${flags[${index}]}" = "${word}" ]]; then
105-
unset "flags[${index}]"
106-
flags=("${flags[@]}")
105+
for index in "${!non_repeating_flags[@]}"; do
106+
if [[ "${non_repeating_flags[${index}]}" = "${word}" ]]; then
107+
unset "non_repeating_flags[${index}]"
108+
non_repeating_flags=("${non_repeating_flags[@]}")
107109
not_found=false
108110
break
109111
fi
110112
done
111113
if "${not_found}"; then
112-
for index in "${!options[@]}"; do
113-
if [[ "${options[${index}]}" = "${word}" ]]; then
114-
unset "options[${index}]"
115-
options=("${options[@]}")
114+
for index in "${!non_repeating_flags[@]}"; do
115+
if [[ "${non_repeating_flags[${index}]}" = "${word}" ]]; then
116+
unset "non_repeating_flags[${index}]"
117+
non_repeating_flags=("${non_repeating_flags[@]}")
116118
break
117119
fi
118120
done
@@ -147,7 +149,7 @@ extension CommandInfoV0 {
147149
&& ! "${is_parsing_option_value}"\\
148150
&& [[ ("${cur}" = -* && "${positional_number}" -ge 0) || "${positional_number}" -eq -1 ]]
149151
then
150-
COMPREPLY+=($(compgen -W "${flags[*]} ${options[*]}" -- "${cur}"))
152+
COMPREPLY+=($(compgen -W "${repeating_flags[*]} ${non_repeating_flags[*]} ${repeating_options[*]} ${non_repeating_options[*]}" -- "${cur}"))
151153
fi
152154
}
153155
@@ -211,12 +213,18 @@ extension CommandInfoV0 {
211213

212214
let positionalArguments = positionalArguments
213215

214-
let flagCompletions = flagCompletions
215-
let optionCompletions = optionCompletions
216-
if !flagCompletions.isEmpty || !optionCompletions.isEmpty {
216+
let arguments = arguments ?? []
217+
218+
let flags = arguments.filter { $0.kind == .flag }
219+
let options = arguments.filter { $0.kind == .option }
220+
if !flags.flatMap(\.completionWords).isEmpty
221+
|| !options.flatMap(\.completionWords).isEmpty
222+
{
217223
result += """
218-
\(declareTopLevelArray)flags=(\(flagCompletions.joined(separator: " ")))
219-
\(declareTopLevelArray)options=(\(optionCompletions.joined(separator: " ")))
224+
\(declareTopLevelArray)repeating_flags=(\(flags.filter(\.isRepeating).flatMap(\.completionWords).joined(separator: " ")))
225+
\(declareTopLevelArray)non_repeating_flags=(\(flags.filter { !$0.isRepeating }.flatMap(\.completionWords).joined(separator: " ")))
226+
\(declareTopLevelArray)repeating_options=(\(options.filter(\.isRepeating).flatMap(\.completionWords).joined(separator: " ")))
227+
\(declareTopLevelArray)non_repeating_options=(\(options.filter { !$0.isRepeating }.flatMap(\.completionWords).joined(separator: " ")))
220228
\(offerFlagsOptionsFunctionName) \
221229
\(positionalArguments.contains { $0.isRepeating } ? 9_223_372_036_854_775_807 : positionalArguments.count)
222230
@@ -226,7 +234,7 @@ extension CommandInfoV0 {
226234
// Generate the case pattern-matching statements for option values.
227235
// If there aren't any, skip the case block altogether.
228236
let optionHandlers =
229-
(arguments ?? []).compactMap { arg in
237+
arguments.compactMap { arg in
230238
guard arg.kind != .flag else { return nil }
231239
let words = arg.completionWords
232240
guard !words.isEmpty else { return nil }
@@ -320,30 +328,6 @@ extension CommandInfoV0 {
320328
"""
321329
}
322330

323-
/// Returns flag completions.
324-
private var flagCompletions: [String] {
325-
(arguments ?? []).flatMap {
326-
switch $0.kind {
327-
case .flag:
328-
return $0.completionWords
329-
default:
330-
return []
331-
}
332-
}
333-
}
334-
335-
/// Returns option completions.
336-
private var optionCompletions: [String] {
337-
(arguments ?? []).flatMap {
338-
switch $0.kind {
339-
case .option:
340-
return $0.completionWords
341-
default:
342-
return []
343-
}
344-
}
345-
}
346-
347331
/// Returns the completions that can follow the given argument's `--name`.
348332
private func valueCompletion(_ arg: ArgumentInfoV0) -> String {
349333
switch arg.completionKind {

Tests/ArgumentParserExampleTests/Snapshots/testMathBashCompletionScript().bash

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,17 @@ __math_cursor_index_in_current_word() {
2222
#
2323
# required variables:
2424
#
25-
# - flags: the flags that the current (sub)command can accept
26-
# - options: the options that the current (sub)command can accept
25+
# - repeating_flags: the repeating flags that the current (sub)command can accept
26+
# - non_repeating_flags: the non-repeating flags that the current (sub)command can accept
27+
# - repeating_options: the repeating options that the current (sub)command can accept
28+
# - non_repeating_options: the non-repeating options that the current (sub)command can accept
2729
# - positional_number: value ignored
2830
# - unparsed_words: unparsed words from the current command line
2931
#
3032
# modified variables:
3133
#
32-
# - flags: remove flags for this (sub)command that are already on the command line
33-
# - options: remove options for this (sub)command that are already on the command line
34+
# - non_repeating_flags: remove flags for this (sub)command that are already on the command line
35+
# - non_repeating_options: remove options for this (sub)command that are already on the command line
3436
# - positional_number: set to the current positional number
3537
# - unparsed_words: remove all flags, options, and option values for this (sub)command
3638
__math_offer_flags_options() {
@@ -67,26 +69,26 @@ __math_offer_flags_options() {
6769
# ${word} is a flag or an option
6870
# If ${word} is an option, mark that the next word to be parsed is an option value
6971
local option
70-
for option in "${options[@]}"; do
72+
for option in "${repeating_options[@]}" "${non_repeating_options[@]}"; do
7173
[[ "${word}" = "${option}" ]] && is_parsing_option_value=true && break
7274
done
7375

74-
# Remove ${word} from ${flags} or ${options} so it isn't offered again
76+
# Remove ${word} from ${non_repeating_flags} or ${non_repeating_options} so it isn't offered again
7577
local not_found=true
7678
local -i index
77-
for index in "${!flags[@]}"; do
78-
if [[ "${flags[${index}]}" = "${word}" ]]; then
79-
unset "flags[${index}]"
80-
flags=("${flags[@]}")
79+
for index in "${!non_repeating_flags[@]}"; do
80+
if [[ "${non_repeating_flags[${index}]}" = "${word}" ]]; then
81+
unset "non_repeating_flags[${index}]"
82+
non_repeating_flags=("${non_repeating_flags[@]}")
8183
not_found=false
8284
break
8385
fi
8486
done
8587
if "${not_found}"; then
86-
for index in "${!options[@]}"; do
87-
if [[ "${options[${index}]}" = "${word}" ]]; then
88-
unset "options[${index}]"
89-
options=("${options[@]}")
88+
for index in "${!non_repeating_flags[@]}"; do
89+
if [[ "${non_repeating_flags[${index}]}" = "${word}" ]]; then
90+
unset "non_repeating_flags[${index}]"
91+
non_repeating_flags=("${non_repeating_flags[@]}")
9092
break
9193
fi
9294
done
@@ -121,7 +123,7 @@ __math_offer_flags_options() {
121123
&& ! "${is_parsing_option_value}"\
122124
&& [[ ("${cur}" = -* && "${positional_number}" -ge 0) || "${positional_number}" -eq -1 ]]
123125
then
124-
COMPREPLY+=($(compgen -W "${flags[*]} ${options[*]}" -- "${cur}"))
126+
COMPREPLY+=($(compgen -W "${repeating_flags[*]} ${non_repeating_flags[*]} ${repeating_options[*]} ${non_repeating_options[*]}" -- "${cur}"))
125127
fi
126128
}
127129

@@ -158,8 +160,10 @@ _math() {
158160
local -i positional_number
159161
local -a unparsed_words=("${COMP_WORDS[@]:1:${COMP_CWORD}}")
160162

161-
local -a flags=(--version -h --help)
162-
local -a options=()
163+
local -a repeating_flags=()
164+
local -a non_repeating_flags=(--version -h --help)
165+
local -a repeating_options=()
166+
local -a non_repeating_options=()
163167
__math_offer_flags_options 0
164168

165169
# Offer subcommand / subcommand argument completions
@@ -179,20 +183,26 @@ _math() {
179183
}
180184

181185
_math_add() {
182-
flags=(--hex-output -x --version -h --help)
183-
options=()
186+
repeating_flags=()
187+
non_repeating_flags=(--hex-output -x --version -h --help)
188+
repeating_options=()
189+
non_repeating_options=()
184190
__math_offer_flags_options 9223372036854775807
185191
}
186192

187193
_math_multiply() {
188-
flags=(--hex-output -x --version -h --help)
189-
options=()
194+
repeating_flags=()
195+
non_repeating_flags=(--hex-output -x --version -h --help)
196+
repeating_options=()
197+
non_repeating_options=()
190198
__math_offer_flags_options 9223372036854775807
191199
}
192200

193201
_math_stats() {
194-
flags=(--version -h --help)
195-
options=()
202+
repeating_flags=()
203+
non_repeating_flags=(--version -h --help)
204+
repeating_options=()
205+
non_repeating_options=()
196206
__math_offer_flags_options 0
197207

198208
# Offer subcommand / subcommand argument completions
@@ -212,8 +222,10 @@ _math_stats() {
212222
}
213223

214224
_math_stats_average() {
215-
flags=(--version -h --help)
216-
options=(--kind)
225+
repeating_flags=()
226+
non_repeating_flags=(--version -h --help)
227+
repeating_options=()
228+
non_repeating_options=(--kind)
217229
__math_offer_flags_options 9223372036854775807
218230

219231
# Offer option value completions
@@ -226,14 +238,18 @@ _math_stats_average() {
226238
}
227239

228240
_math_stats_stdev() {
229-
flags=(--version -h --help)
230-
options=()
241+
repeating_flags=()
242+
non_repeating_flags=(--version -h --help)
243+
repeating_options=()
244+
non_repeating_options=()
231245
__math_offer_flags_options 9223372036854775807
232246
}
233247

234248
_math_stats_quantiles() {
235-
flags=(--version -h --help)
236-
options=(--file --directory --shell --custom --custom-deprecated)
249+
repeating_flags=()
250+
non_repeating_flags=(--version -h --help)
251+
repeating_options=()
252+
non_repeating_options=(--file --directory --shell --custom --custom-deprecated)
237253
__math_offer_flags_options 9223372036854775807
238254

239255
# Offer option value completions
@@ -278,8 +294,10 @@ _math_stats_quantiles() {
278294
}
279295

280296
_math_help() {
281-
flags=(--version)
282-
options=()
297+
repeating_flags=()
298+
non_repeating_flags=(--version)
299+
repeating_options=()
300+
non_repeating_options=()
283301
__math_offer_flags_options 9223372036854775807
284302
}
285303

Tests/ArgumentParserUnitTests/Snapshots/testBase_Bash().bash

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,17 @@ __base-test_cursor_index_in_current_word() {
2222
#
2323
# required variables:
2424
#
25-
# - flags: the flags that the current (sub)command can accept
26-
# - options: the options that the current (sub)command can accept
25+
# - repeating_flags: the repeating flags that the current (sub)command can accept
26+
# - non_repeating_flags: the non-repeating flags that the current (sub)command can accept
27+
# - repeating_options: the repeating options that the current (sub)command can accept
28+
# - non_repeating_options: the non-repeating options that the current (sub)command can accept
2729
# - positional_number: value ignored
2830
# - unparsed_words: unparsed words from the current command line
2931
#
3032
# modified variables:
3133
#
32-
# - flags: remove flags for this (sub)command that are already on the command line
33-
# - options: remove options for this (sub)command that are already on the command line
34+
# - non_repeating_flags: remove flags for this (sub)command that are already on the command line
35+
# - non_repeating_options: remove options for this (sub)command that are already on the command line
3436
# - positional_number: set to the current positional number
3537
# - unparsed_words: remove all flags, options, and option values for this (sub)command
3638
__base-test_offer_flags_options() {
@@ -67,26 +69,26 @@ __base-test_offer_flags_options() {
6769
# ${word} is a flag or an option
6870
# If ${word} is an option, mark that the next word to be parsed is an option value
6971
local option
70-
for option in "${options[@]}"; do
72+
for option in "${repeating_options[@]}" "${non_repeating_options[@]}"; do
7173
[[ "${word}" = "${option}" ]] && is_parsing_option_value=true && break
7274
done
7375

74-
# Remove ${word} from ${flags} or ${options} so it isn't offered again
76+
# Remove ${word} from ${non_repeating_flags} or ${non_repeating_options} so it isn't offered again
7577
local not_found=true
7678
local -i index
77-
for index in "${!flags[@]}"; do
78-
if [[ "${flags[${index}]}" = "${word}" ]]; then
79-
unset "flags[${index}]"
80-
flags=("${flags[@]}")
79+
for index in "${!non_repeating_flags[@]}"; do
80+
if [[ "${non_repeating_flags[${index}]}" = "${word}" ]]; then
81+
unset "non_repeating_flags[${index}]"
82+
non_repeating_flags=("${non_repeating_flags[@]}")
8183
not_found=false
8284
break
8385
fi
8486
done
8587
if "${not_found}"; then
86-
for index in "${!options[@]}"; do
87-
if [[ "${options[${index}]}" = "${word}" ]]; then
88-
unset "options[${index}]"
89-
options=("${options[@]}")
88+
for index in "${!non_repeating_flags[@]}"; do
89+
if [[ "${non_repeating_flags[${index}]}" = "${word}" ]]; then
90+
unset "non_repeating_flags[${index}]"
91+
non_repeating_flags=("${non_repeating_flags[@]}")
9092
break
9193
fi
9294
done
@@ -121,7 +123,7 @@ __base-test_offer_flags_options() {
121123
&& ! "${is_parsing_option_value}"\
122124
&& [[ ("${cur}" = -* && "${positional_number}" -ge 0) || "${positional_number}" -eq -1 ]]
123125
then
124-
COMPREPLY+=($(compgen -W "${flags[*]} ${options[*]}" -- "${cur}"))
126+
COMPREPLY+=($(compgen -W "${repeating_flags[*]} ${non_repeating_flags[*]} ${repeating_options[*]} ${non_repeating_options[*]}" -- "${cur}"))
125127
fi
126128
}
127129

@@ -158,8 +160,10 @@ _base-test() {
158160
local -i positional_number
159161
local -a unparsed_words=("${COMP_WORDS[@]:1:${COMP_CWORD}}")
160162

161-
local -a flags=(--one --two --custom-three --kind-counter -h --help)
162-
local -a options=(--name --kind --other-kind --path1 --path2 --path3 --rep1 -r --rep2)
163+
local -a repeating_flags=(--kind-counter)
164+
local -a non_repeating_flags=(--one --two --custom-three -h --help)
165+
local -a repeating_options=(--rep1 -r --rep2)
166+
local -a non_repeating_options=(--name --kind --other-kind --path1 --path2 --path3)
163167
__base-test_offer_flags_options 2
164168

165169
# Offer option value completions
@@ -224,14 +228,18 @@ _base-test() {
224228
}
225229

226230
_base-test_sub-command() {
227-
flags=(-h --help)
228-
options=()
231+
repeating_flags=()
232+
non_repeating_flags=(-h --help)
233+
repeating_options=()
234+
non_repeating_options=()
229235
__base-test_offer_flags_options 0
230236
}
231237

232238
_base-test_escaped-command() {
233-
flags=(-h --help)
234-
options=(--o:n[e)
239+
repeating_flags=()
240+
non_repeating_flags=(-h --help)
241+
repeating_options=()
242+
non_repeating_options=(--o:n[e)
235243
__base-test_offer_flags_options 1
236244

237245
# Offer option value completions

0 commit comments

Comments
 (0)