Skip to content

Commit 6924db8

Browse files
authored
Use Foundation Essentials when possible (#804)
* add implementation of replacing(_:with:) * remove use of range(of:) * Consolidate foundation usage * Consolidate error to string logic * Clean up env a bit
1 parent c94be10 commit 6924db8

26 files changed

+459
-293
lines changed

Sources/ArgumentParser/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ add_library(ArgumentParser
4343

4444
Utilities/CollectionExtensions.swift
4545
Utilities/Mutex.swift
46+
Utilities/Foundation.swift
4647
Utilities/Platform.swift
4748
Utilities/SequenceExtensions.swift
4849
Utilities/StringExtensions.swift

Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,10 @@ extension CommandInfoV0 {
190190
shopt -s extglob
191191
set +o history +o posix
192192
193-
local -xr \(CompletionShell.shellEnvironmentVariableName)=bash
194-
local -x \(CompletionShell.shellVersionEnvironmentVariableName)
195-
\(CompletionShell.shellVersionEnvironmentVariableName)="$(IFS='.';printf %s "${BASH_VERSINFO[*]}")"
196-
local -r \(CompletionShell.shellVersionEnvironmentVariableName)
193+
local -xr \(Platform.Environment.Key.shellName.rawValue)=bash
194+
local -x \(Platform.Environment.Key.shellVersion.rawValue)
195+
\(Platform.Environment.Key.shellVersion.rawValue)="$(IFS='.';printf %s "${BASH_VERSINFO[*]}")"
196+
local -r \(Platform.Environment.Key.shellVersion.rawValue)
197197
198198
local -r cur="${2}"
199199
local -r prev="${3}"

Sources/ArgumentParser/Completions/CompletionsGenerator.swift

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111

1212
#if compiler(>=6.0)
1313
internal import ArgumentParserToolInfo
14-
internal import Foundation
1514
#else
1615
import ArgumentParserToolInfo
17-
import Foundation
1816
#endif
1917

2018
/// A shell for which the parser can generate a completion script.
21-
public struct CompletionShell: RawRepresentable, Hashable, CaseIterable {
19+
public struct CompletionShell: RawRepresentable, Hashable, CaseIterable,
20+
Sendable
21+
{
2222
public var rawValue: String
2323

2424
/// Creates a new instance from the given string.
@@ -87,20 +87,6 @@ public struct CompletionShell: RawRepresentable, Hashable, CaseIterable {
8787
Self._requestingVersion.withLock { $0 }
8888
}
8989

90-
/// The name of the environment variable whose value is the name of the shell
91-
/// for which completions are being requested from a custom completion
92-
/// handler.
93-
///
94-
/// The environment variable is set in generated completion scripts.
95-
static let shellEnvironmentVariableName = "SAP_SHELL"
96-
97-
/// The name of the environment variable whose value is the version of the
98-
/// shell for which completions are being requested from a custom completion
99-
/// handler.
100-
///
101-
/// The environment variable is set in generated completion scripts.
102-
static let shellVersionEnvironmentVariableName = "SAP_SHELL_VERSION"
103-
10490
func format(completions: [String]) -> String {
10591
var completions = completions
10692
if self == .zsh {
@@ -157,12 +143,82 @@ extension String {
157143
func shellEscapeForSingleQuotedString(iterationCount: UInt64 = 1) -> Self {
158144
iterationCount == 0
159145
? self
160-
: replacingOccurrences(of: "'", with: "'\\''")
146+
: self
147+
.replacing("'", with: "'\\''")
161148
.shellEscapeForSingleQuotedString(iterationCount: iterationCount - 1)
162149
}
163150

164151
func shellEscapeForVariableName() -> Self {
165-
replacingOccurrences(of: "-", with: "_")
152+
self.replacing("-", with: "_")
153+
}
154+
155+
func replacing(_ old: Self, with new: Self) -> Self {
156+
guard !old.isEmpty else { return self }
157+
158+
var result = ""
159+
var startIndex = self.startIndex
160+
161+
// Look for occurrences of the old string.
162+
while let matchRange = self.firstMatch(of: old, at: startIndex) {
163+
// Add the substring before the match.
164+
result.append(contentsOf: self[startIndex..<matchRange.start])
165+
166+
// Add the replacement string.
167+
result.append(contentsOf: new)
168+
169+
// Move past the matched portion.
170+
startIndex = matchRange.end
171+
}
172+
173+
// No more matches found, add the rest of the string.
174+
result.append(contentsOf: self[startIndex..<self.endIndex])
175+
176+
return result
177+
}
178+
179+
func firstMatch(
180+
of match: Self,
181+
at startIndex: Self.Index
182+
) -> (start: Self.Index, end: Self.Index)? {
183+
guard !match.isEmpty else { return nil }
184+
guard match.count <= self.count else { return nil }
185+
186+
var startIndex = startIndex
187+
while startIndex < self.endIndex {
188+
// Check if theres a match.
189+
if let endIndex = self.matches(match, at: startIndex) {
190+
// Return the match.
191+
return (startIndex, endIndex)
192+
}
193+
194+
// Move to the next of index.
195+
self.formIndex(after: &startIndex)
196+
}
197+
198+
return nil
199+
}
200+
201+
func matches(
202+
_ match: Self,
203+
at startIndex: Self.Index
204+
) -> Self.Index? {
205+
var selfIndex = startIndex
206+
var matchIndex = match.startIndex
207+
208+
while true {
209+
// Only continue checking if there is more match to check
210+
guard matchIndex < match.endIndex else { return selfIndex }
211+
212+
// Exit early if there is no more "self" to check.
213+
guard selfIndex < self.endIndex else { return nil }
214+
215+
// Check match and self are the the same.
216+
guard self[selfIndex] == match[matchIndex] else { return nil }
217+
218+
// Move to the next pair of indices.
219+
self.formIndex(after: &selfIndex)
220+
match.formIndex(after: &matchIndex)
221+
}
166222
}
167223
}
168224

Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@
1111

1212
#if compiler(>=6.0)
1313
internal import ArgumentParserToolInfo
14-
internal import Foundation
1514
#else
1615
import ArgumentParserToolInfo
17-
import Foundation
1816
#endif
1917

2018
extension ToolInfoV0 {
@@ -75,8 +73,8 @@ extension CommandInfoV0 {
7573
end
7674
7775
function \(customCompletionFunctionName)
78-
set -x \(CompletionShell.shellEnvironmentVariableName) fish
79-
set -x \(CompletionShell.shellVersionEnvironmentVariableName) $FISH_VERSION
76+
set -x \(Platform.Environment.Key.shellName.rawValue) fish
77+
set -x \(Platform.Environment.Key.shellVersion.rawValue) $FISH_VERSION
8078
8179
set -l tokens (\(tokensFunctionName) -p)
8280
if test -z (\(tokensFunctionName) -t)
@@ -315,8 +313,9 @@ extension String {
315313
) -> Self {
316314
iterationCount == 0
317315
? self
318-
: replacingOccurrences(of: "\\", with: "\\\\")
319-
.replacingOccurrences(of: "'", with: "\\'")
316+
: self
317+
.replacing("\\", with: "\\\\")
318+
.replacing("'", with: "\\'")
320319
.fishEscapeForSingleQuotedString(iterationCount: iterationCount - 1)
321320
}
322321
}

Sources/ArgumentParser/Completions/ZshCompletionsGenerator.swift

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@
1111

1212
#if compiler(>=6.0)
1313
internal import ArgumentParserToolInfo
14-
internal import Foundation
1514
#else
1615
import ArgumentParserToolInfo
17-
import Foundation
1816
#endif
1917

2018
extension ToolInfoV0 {
@@ -111,10 +109,10 @@ extension CommandInfoV0 {
111109
setopt extendedglob nullglob numericglobsort
112110
unsetopt aliases banghist
113111
114-
local -xr \(CompletionShell.shellEnvironmentVariableName)=zsh
115-
local -x \(CompletionShell.shellVersionEnvironmentVariableName)
116-
\(CompletionShell.shellVersionEnvironmentVariableName)="$(builtin emulate zsh -c 'printf %s "${ZSH_VERSION}"')"
117-
local -r \(CompletionShell.shellVersionEnvironmentVariableName)
112+
local -xr \(Platform.Environment.Key.shellName.rawValue)=zsh
113+
local -x \(Platform.Environment.Key.shellVersion.rawValue)
114+
\(Platform.Environment.Key.shellVersion.rawValue)="$(builtin emulate zsh -c 'printf %s "${ZSH_VERSION}"')"
115+
local -r \(Platform.Environment.Key.shellVersion.rawValue)
118116
119117
local context state state_descr line
120118
local -A opt_args
@@ -271,19 +269,17 @@ extension ArgumentInfoV0 {
271269

272270
extension String {
273271
fileprivate func zshEscapeForSingleQuotedDescribeCompletion() -> String {
274-
replacingOccurrences(
275-
of: #"[:\\]"#,
276-
with: #"\\$0"#,
277-
options: .regularExpression
278-
)
279-
.shellEscapeForSingleQuotedString()
272+
self
273+
.replacing("\\", with: "\\\\")
274+
.replacing(":", with: "\\:")
275+
.shellEscapeForSingleQuotedString()
280276
}
281277
fileprivate func zshEscapeForSingleQuotedOptionSpec() -> String {
282-
replacingOccurrences(
283-
of: #"[:\\\[\]]"#,
284-
with: #"\\$0"#,
285-
options: .regularExpression
286-
)
287-
.shellEscapeForSingleQuotedString()
278+
self
279+
.replacing("\\", with: "\\\\")
280+
.replacing(":", with: "\\:")
281+
.replacing("[", with: "\\[")
282+
.replacing("]", with: "\\]")
283+
.shellEscapeForSingleQuotedString()
288284
}
289285
}

Sources/ArgumentParser/Parsable Types/ParsableArguments.swift

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

12-
#if compiler(>=6.0)
13-
internal import Foundation
14-
#else
15-
import Foundation
16-
#endif
17-
1812
/// A type that can be parsed from a program's command-line arguments.
1913
///
2014
/// When you implement a `ParsableArguments` type, all properties must be declared with
@@ -41,13 +35,14 @@ struct _WrappedParsableCommand<P: ParsableArguments>: ParsableCommand {
4135

4236
// If the type is named something like "TransformOptions", we only want
4337
// to use "transform" as the command name.
44-
if let optionsRange = name.range(of: "_options"),
45-
optionsRange.upperBound == name.endIndex
46-
{
47-
return String(name[..<optionsRange.lowerBound])
48-
} else {
38+
guard
39+
let matchRange = name.firstMatch(of: "_options", at: name.startIndex),
40+
matchRange.end == name.endIndex
41+
else {
4942
return name
5043
}
44+
45+
return String(name[..<matchRange.start])
5146
}
5247

5348
@OptionGroup var options: P

Sources/ArgumentParser/Parsing/ArgumentDefinition.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,8 @@ extension ArgumentDefinition {
304304
help: ArgumentHelp?,
305305
defaultValueDescription: String?,
306306
parsingStrategy: ParsingStrategy,
307-
parser: @escaping (InputKey, InputOrigin, Name?, String) throws ->
307+
parser:
308+
@escaping (InputKey, InputOrigin, Name?, String) throws ->
308309
Container.Contained,
309310
initial: Container.Initial?,
310311
completion: CompletionKind?

Sources/ArgumentParser/Parsing/CommandParser.swift

Lines changed: 7 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,10 @@
1313
#if canImport(Dispatch)
1414
@preconcurrency private import class Dispatch.DispatchSemaphore
1515
#endif
16-
internal import class Foundation.NSLock
17-
internal import class Foundation.ProcessInfo
1816
#else
1917
#if canImport(Dispatch)
2018
@preconcurrency import class Dispatch.DispatchSemaphore
2119
#endif
22-
import class Foundation.NSLock
23-
import class Foundation.ProcessInfo
2420
#endif
2521

2622
struct CommandError: Error {
@@ -455,16 +451,13 @@ extension CommandParser {
455451
_ argument: ArgumentDefinition,
456452
forArguments args: [String]
457453
) throws {
458-
let environment = ProcessInfo.processInfo.environment
459-
if let completionShellName = environment[
460-
CompletionShell.shellEnvironmentVariableName]
461-
{
454+
if let completionShellName = Platform.Environment[.shellName] {
462455
let shell = CompletionShell(rawValue: completionShellName)
463456
CompletionShell._requesting.withLock { $0 = shell }
464457
}
465458

466459
CompletionShell._requestingVersion.withLock {
467-
$0 = environment[CompletionShell.shellVersionEnvironmentVariableName]
460+
$0 = Platform.Environment[.shellVersion]
468461
}
469462

470463
let completions: [String]
@@ -550,46 +543,23 @@ private func asyncCustomCompletions(
550543
let (args, completingArgumentIndex, completingPrefix) =
551544
try parseCustomCompletionArguments(from: args)
552545

553-
let completionsBox = SendableBox<[String]>([])
546+
let completionsBox = Mutex<[String]>([])
554547
let semaphore = DispatchSemaphore(value: 0)
555548

556549
Task {
557-
completionsBox.value = await complete(
550+
let completion = await complete(
558551
args,
559552
completingArgumentIndex,
560-
completingPrefix
561-
)
553+
completingPrefix)
554+
completionsBox.withLock { $0 = completion }
562555
semaphore.signal()
563556
}
564557

565558
semaphore.wait()
566-
return completionsBox.value
559+
return completionsBox.withLock { $0 }
567560
#endif
568561
}
569562

570-
// Helper class to make values sendable across concurrency boundaries
571-
private final class SendableBox<T>: @unchecked Sendable {
572-
private let lock = NSLock()
573-
private var _value: T
574-
575-
init(_ value: T) {
576-
self._value = value
577-
}
578-
579-
var value: T {
580-
get {
581-
lock.lock()
582-
defer { lock.unlock() }
583-
return _value
584-
}
585-
set {
586-
lock.lock()
587-
defer { lock.unlock() }
588-
_value = newValue
589-
}
590-
}
591-
}
592-
593563
// MARK: Building Command Stacks
594564

595565
extension CommandParser {

Sources/ArgumentParser/Usage/DumpHelpGenerator.swift

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,8 @@
1111

1212
#if compiler(>=6.0)
1313
internal import ArgumentParserToolInfo
14-
internal import class Foundation.JSONEncoder
1514
#else
1615
import ArgumentParserToolInfo
17-
import class Foundation.JSONEncoder
1816
#endif
1917

2018
internal struct DumpHelpGenerator {
@@ -29,11 +27,7 @@ internal struct DumpHelpGenerator {
2927
}
3028

3129
func rendered() -> String {
32-
let encoder = JSONEncoder()
33-
encoder.outputFormatting = .prettyPrinted
34-
encoder.outputFormatting.insert(.sortedKeys)
35-
guard let encoded = try? encoder.encode(self.toolInfo) else { return "" }
36-
return String(data: encoded, encoding: .utf8) ?? ""
30+
JSONEncoder.encode(self.toolInfo)
3731
}
3832
}
3933

0 commit comments

Comments
 (0)