Skip to content

Commit 932e6cd

Browse files
authored
Refactor completion script generation to use ToolInfoV0 (#764)
* Nonexclusive flags implemented via an array of enum cases are now separate ArgumentInfoV0 instances, instead of different names for the same ArgumentInfoV0. * Improve ToolInfoV0 HelpCommand injection. * Add ArgumentInfoV0.ParsingStrategyV0 enum. * Refactor bash completions to use ToolInfoV0. * Refactor fish completions to use ToolInfoV0. * Refactor zsh completions to use ToolInfoV0. * Remove vestigial shellVariableNamePrefix. * Add .editorconfig files to prevent automatic whitespace changes to test snapshots. Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
1 parent ec562e5 commit 932e6cd

30 files changed

+793
-575
lines changed

Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift

Lines changed: 194 additions & 200 deletions
Large diffs are not rendered by default.

Sources/ArgumentParser/Completions/CompletionsGenerator.swift

Lines changed: 58 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12+
#if swift(>=6.0)
13+
internal import ArgumentParserToolInfo
14+
#else
15+
import ArgumentParserToolInfo
16+
#endif
17+
1218
/// A shell for which the parser can generate a completion script.
1319
public struct CompletionShell: RawRepresentable, Hashable, CaseIterable {
1420
public var rawValue: String
@@ -134,84 +140,81 @@ struct CompletionsGenerator {
134140
CompletionShell._requesting.withLock { $0 = shell }
135141
switch shell {
136142
case .zsh:
137-
return [command].zshCompletionScript
143+
return ToolInfoV0(commandStack: [command]).zshCompletionScript
138144
case .bash:
139-
return [command].bashCompletionScript
145+
return ToolInfoV0(commandStack: [command]).bashCompletionScript
140146
case .fish:
141-
return [command].fishCompletionScript
147+
return ToolInfoV0(commandStack: [command]).fishCompletionScript
142148
default:
143149
fatalError("Invalid CompletionShell: \(shell)")
144150
}
145151
}
146152
}
147153

148-
extension ArgumentDefinition {
149-
/// Returns a string with the arguments for the callback to generate custom completions for
150-
/// this argument.
151-
func customCompletionCall(_ commands: [ParsableCommand.Type]) -> String {
152-
let subcommandNames =
153-
commands.dropFirst().map { "\($0._commandName) " }.joined()
154-
let argumentName =
155-
names.preferredName?.synopsisString
156-
?? self.help.keys.first?.fullPathString
157-
?? "---"
158-
return "---completion \(subcommandNames)-- \(argumentName)"
154+
extension String {
155+
func shellEscapeForSingleQuotedString(iterationCount: UInt64 = 1) -> Self {
156+
iterationCount == 0
157+
? self
158+
: replacingOccurrences(of: "'", with: "'\\''")
159+
.shellEscapeForSingleQuotedString(iterationCount: iterationCount - 1)
159160
}
160-
}
161161

162-
extension ParsableCommand {
163-
fileprivate static var compositeCommandName: [String] {
164-
if let superCommandName = configuration._superCommandName {
165-
return [superCommandName]
166-
+ _commandName.split(separator: " ").map(String.init)
167-
} else {
168-
return _commandName.split(separator: " ").map(String.init)
169-
}
162+
func shellEscapeForVariableName() -> Self {
163+
replacingOccurrences(of: "-", with: "_")
170164
}
171165
}
172166

173-
extension [ParsableCommand.Type] {
174-
var positionalArguments: [ArgumentDefinition] {
175-
guard let command = last else {
176-
return []
177-
}
178-
return ArgumentSet(command, visibility: .default, parent: nil)
179-
.filter(\.isPositional)
167+
extension CommandInfoV0 {
168+
var commandContext: [String] {
169+
(superCommands ?? []) + [commandName]
180170
}
181171

182-
/// Include default 'help' subcommand in nonempty subcommand list if & only if
183-
/// no help subcommand already exists.
184-
mutating func addHelpSubcommandIfMissing() {
185-
if !isEmpty && !contains(where: { $0._commandName == "help" }) {
186-
append(HelpCommand.self)
187-
}
172+
var initialCommand: String {
173+
superCommands?.first ?? commandName
188174
}
189-
}
190175

191-
extension Sequence where Element == ParsableCommand.Type {
192-
func completionFunctionName() -> String {
193-
"_"
194-
+ self.flatMap { $0.compositeCommandName }
195-
.uniquingAdjacentElements()
196-
.joined(separator: "_")
176+
var positionalArguments: [ArgumentInfoV0] {
177+
(arguments ?? []).filter { $0.kind == .positional }
197178
}
198179

199-
var shellVariableNamePrefix: String {
200-
flatMap { $0.compositeCommandName }
201-
.joined(separator: "_")
202-
.shellEscapeForVariableName()
180+
var completionFunctionName: String {
181+
"_" + commandContext.joined(separator: "_")
182+
}
183+
184+
var completionFunctionPrefix: String {
185+
"__\(initialCommand)"
203186
}
204187
}
205188

206-
extension String {
207-
func shellEscapeForSingleQuotedString(iterationCount: UInt64 = 1) -> Self {
208-
iterationCount == 0
209-
? self
210-
: replacingOccurrences(of: "'", with: "'\\''")
211-
.shellEscapeForSingleQuotedString(iterationCount: iterationCount - 1)
189+
extension ArgumentInfoV0 {
190+
/// Returns a string with the arguments for the callback to generate custom
191+
/// completions for this argument.
192+
func commonCustomCompletionCall(command: CommandInfoV0) -> String {
193+
let subcommandNames =
194+
command.commandContext.dropFirst().map { "\($0) " }.joined()
195+
196+
let argumentName: String
197+
switch kind {
198+
case .positional:
199+
if let index = command.positionalArguments.firstIndex(of: self) {
200+
argumentName = "positional@\(index)"
201+
} else {
202+
argumentName = "---"
203+
}
204+
default:
205+
argumentName = preferredName?.commonCompletionSynopsisString() ?? "---"
206+
}
207+
return "---completion \(subcommandNames)-- \(argumentName)"
212208
}
209+
}
213210

214-
func shellEscapeForVariableName() -> Self {
215-
replacingOccurrences(of: "-", with: "_")
211+
extension ArgumentInfoV0.NameInfoV0 {
212+
func commonCompletionSynopsisString() -> String {
213+
switch kind {
214+
case .long:
215+
return "--\(name)"
216+
case .short, .longWithSingleDash:
217+
return "-\(name)"
218+
}
216219
}
217220
}

0 commit comments

Comments
 (0)