Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions Sources/ArgumentParser/Completions/CompletionsGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,27 @@ public struct CompletionShell: RawRepresentable, Hashable, CaseIterable {
[.zsh, .bash, .fish]
}

static let _requesting = Mutex<CompletionShell?>(nil)

/// While generating a shell completion script or while a Swift custom completion
/// function is executing to offer completions for a word from a command line (e.g.,
/// while `customCompletion` from `@Option(completion: .custom(customCompletion))`
/// executes), an instance representing the shell for which completions will
/// be or are being requested, respectively. Otherwise `nil`.
public internal(set) static var requesting: CompletionShell?
public static var requesting: CompletionShell? {
Self._requesting.withLock { $0 }
}

static let _requestingVersion = Mutex<String?>(nil)

/// While a Swift custom completion function is executing to offer completions
/// for a word from a command line (e.g., while `customCompletion` from
/// `@Option(completion: .custom(customCompletion))` executes), a `String`
/// representing the version of the shell for which completions are being
/// requested. Otherwise `nil`.
public internal(set) static var requestingVersion: String?
public static var requestingVersion: String? {
Self._requestingVersion.withLock { $0 }
}

/// The name of the environment variable whose value is the name of the shell
/// for which completions are being requested from a custom completion
Expand Down Expand Up @@ -97,7 +105,7 @@ struct CompletionsGenerator {

/// Generates a shell completion script for this generator's shell and command.
func generateCompletionScript() -> String {
CompletionShell.requesting = shell
CompletionShell._requesting.withLock { $0 = shell }
switch shell {
case .zsh:
return ZshCompletionsGenerator.generateCompletionScript(command)
Expand Down
10 changes: 6 additions & 4 deletions Sources/ArgumentParser/Parsing/CommandParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,14 @@ extension CommandParser {
case .terminator:
throw ParserError.invalidState
}

if let completionShellName = ProcessInfo.processInfo.environment[CompletionShell.shellEnvironmentVariableName] {
CompletionShell.requesting = CompletionShell(rawValue: completionShellName)

let environment = ProcessInfo.processInfo.environment
if let completionShellName = environment[CompletionShell.shellEnvironmentVariableName] {
let shell = CompletionShell(rawValue: completionShellName)
CompletionShell._requesting.withLock { $0 = shell }
}

CompletionShell.requestingVersion = ProcessInfo.processInfo.environment[CompletionShell.shellVersionEnvironmentVariableName]
CompletionShell._requestingVersion.withLock { $0 = environment[CompletionShell.shellVersionEnvironmentVariableName] }

// Parsing and retrieval successful! We don't want to continue with any
// other parsing here, so after printing the result of the completion
Expand Down
58 changes: 58 additions & 0 deletions Sources/ArgumentParser/Utilities/Mutex.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//===----------------------------------------------------------*- swift -*-===//
//
// This source file is part of the Swift Argument Parser open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

import Foundation

/// A synchronization primitive that protects shared mutable state via mutual
/// exclusion.
///
/// The `Mutex` type offers non-recursive exclusive access to the state it is
/// protecting by blocking threads attempting to acquire the lock. Only one
/// execution context at a time has access to the value stored within the
/// `Mutex` allowing for exclusive access.
class Mutex<T>: @unchecked Sendable {
/// The lock used to synchronize access to the value.
var lock: NSLock
/// The value protected by the mutex.
var value: T

/// Initializes a new `Mutex` with the provided value.
///
/// - Parameter value: The initial value to be protected by the mutex.
init(_ value: T) {
self.lock = .init()
self.value = value
}

/// Calls the given closure after acquiring the lock and then releases
/// ownership.
///
/// - Warning: Recursive calls to `withLock` within the closure parameter has
/// behavior that is platform dependent. Some platforms may choose to panic
/// the process, deadlock, or leave this behavior unspecified. This will
/// never reacquire the lock however.
///
/// - Parameter body: A closure with a parameter of `Value` that has exclusive
/// access to the value being stored within this mutex. This closure is
/// considered the critical section as it will only be executed once the
/// calling thread has acquired the lock.
///
/// - Throws: Re-throws any error thrown by `body`.
///
/// - Returns: The return value, if any, of the `body` closure parameter.
func withLock<U, E>(
_ body: (inout T) throws(E) -> U
) throws(E) -> U where E: Error {
self.lock.lock()
defer { self.lock.unlock() }
return try body(&self.value)
}
}