@@ -330,6 +330,32 @@ extension Executable: CustomStringConvertible, CustomDebugStringConvertible {
330330 }
331331}
332332
333+ // MARK: - Executable Introspection
334+
335+ extension Executable {
336+ /// The public representation of an executable's contents.
337+ ///
338+ /// Use this to introspect how an ``Executable`` was constructed (for
339+ /// example, to verify in tests that a configuration builder produced the
340+ /// expected executable reference without spawning a subprocess).
341+ public enum Representation : Sendable , Hashable {
342+ /// The executable is referenced by name and resolved against `PATH`.
343+ case name( String )
344+ /// The executable is referenced by an absolute or relative file path.
345+ case path( FilePath )
346+ }
347+
348+ /// The contents of this executable.
349+ public var representation : Representation {
350+ switch self . storage {
351+ case . executable( let name) :
352+ return . name( name)
353+ case . path( let path) :
354+ return . path( path)
355+ }
356+ }
357+ }
358+
333359// MARK: - Arguments
334360
335361/// A collection of arguments to pass to the subprocess.
@@ -338,17 +364,17 @@ public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable {
338364 public typealias ArrayLiteralElement = String
339365
340366 internal let storage : [ StringOrRawBytes ]
341- internal let executablePathOverride : StringOrRawBytes ?
367+ internal let _executablePathOverride : StringOrRawBytes ?
342368
343369 /// Creates an arguments value from the given literal values.
344370 public init ( arrayLiteral elements: String ... ) {
345371 self . storage = elements. map { . string( $0) }
346- self . executablePathOverride = nil
372+ self . _executablePathOverride = nil
347373 }
348374 /// Creates an arguments value from the given array.
349375 public init ( _ array: [ String ] ) {
350376 self . storage = array. map { . string( $0) }
351- self . executablePathOverride = nil
377+ self . _executablePathOverride = nil
352378 }
353379
354380 /// Creates an ``Arguments`` value using the given values, but
@@ -362,9 +388,9 @@ public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable {
362388 public init ( executablePathOverride: String ? , remainingValues: [ String ] ) {
363389 self . storage = remainingValues. map { . string( $0) }
364390 if let executablePathOverride = executablePathOverride {
365- self . executablePathOverride = . string( executablePathOverride)
391+ self . _executablePathOverride = . string( executablePathOverride)
366392 } else {
367- self . executablePathOverride = nil
393+ self . _executablePathOverride = nil
368394 }
369395 }
370396 #if !os(Windows) // Windows does not support non-unicode arguments
@@ -379,15 +405,15 @@ public struct Arguments: Sendable, ExpressibleByArrayLiteral, Hashable {
379405 public init ( executablePathOverride: [ UInt8 ] ? , remainingValues: [ [ UInt8 ] ] ) {
380406 self . storage = remainingValues. map { . rawBytes( $0) }
381407 if let override = executablePathOverride {
382- self . executablePathOverride = . rawBytes( override)
408+ self . _executablePathOverride = . rawBytes( override)
383409 } else {
384- self . executablePathOverride = nil
410+ self . _executablePathOverride = nil
385411 }
386412 }
387413 /// Creates an arguments value from the array you provide.
388414 public init ( _ array: [ [ UInt8 ] ] ) {
389415 self . storage = array. map { . rawBytes( $0) }
390- self . executablePathOverride = nil
416+ self . _executablePathOverride = nil
391417 }
392418 #endif
393419}
@@ -397,7 +423,7 @@ extension Arguments: CustomStringConvertible, CustomDebugStringConvertible {
397423 public var description : String {
398424 var result : [ String ] = self . storage. map ( \. description)
399425
400- if let override = self . executablePathOverride {
426+ if let override = self . _executablePathOverride {
401427 result. insert ( " override \( override. description) " , at: 0 )
402428 }
403429 return result. description
@@ -407,6 +433,63 @@ extension Arguments: CustomStringConvertible, CustomDebugStringConvertible {
407433 public var debugDescription : String { return self . description }
408434}
409435
436+ // MARK: - Arguments Introspection
437+
438+ extension Arguments {
439+ /// A single argument value, preserving the form in which it was supplied.
440+ ///
441+ /// On POSIX platforms, arguments may be constructed from non-Unicode
442+ /// raw bytes; on Windows, only `String` values are representable.
443+ public enum Value : Sendable , Hashable {
444+ /// A string argument.
445+ case string( String )
446+ #if !os(Windows)
447+ /// A raw-bytes argument.
448+ ///
449+ /// - Note: This case is only available on POSIX platforms.
450+ case rawBytes( [ UInt8 ] )
451+ #endif
452+ }
453+
454+ /// The argument that overrides the executable path as `argv[0]`, or `nil`
455+ /// if the executable path is used unchanged.
456+ ///
457+ /// This corresponds to the `executablePathOverride` parameter passed to
458+ /// the initializer, and is useful for verifying configuration in tests
459+ /// without spawning a subprocess.
460+ public var executablePathOverride : Value ? {
461+ self . _executablePathOverride. map ( Value . init)
462+ }
463+ }
464+
465+ extension Arguments : RandomAccessCollection {
466+ public typealias Element = Value
467+ public typealias Index = Int
468+
469+ public var startIndex : Int { self . storage. startIndex }
470+ public var endIndex : Int { self . storage. endIndex }
471+
472+ public subscript( position: Int ) -> Value {
473+ Value ( self . storage [ position] )
474+ }
475+ }
476+
477+ extension Arguments . Value {
478+ internal init ( _ storage: StringOrRawBytes ) {
479+ switch storage {
480+ case . string( let s) :
481+ self = . string( s)
482+ case . rawBytes( let b) :
483+ #if os(Windows)
484+ // Unreachable: The Windows public API cannot construct rawBytes arguments.
485+ fatalError ( " Internal inconsistency: rawBytes argument on Windows " )
486+ #else
487+ self = . rawBytes( b)
488+ #endif
489+ }
490+ }
491+ }
492+
410493// MARK: - Environment
411494
412495/// A set of environment variables to use when running the subprocess.
@@ -545,7 +628,8 @@ extension Environment: CustomStringConvertible, CustomDebugStringConvertible {
545628}
546629
547630extension Environment . Key {
548- package static let path : Self = " PATH "
631+ /// The well-known key for the `PATH` environment variable.
632+ public static let path : Self = " PATH "
549633}
550634
551635extension Environment . Key : CodingKeyRepresentable { }
@@ -607,6 +691,49 @@ extension Environment.Key: RawRepresentable {
607691
608692extension Environment . Key : Sendable { }
609693
694+ // MARK: - Environment Introspection
695+
696+ extension Environment {
697+ /// The public representation of an environment's contents.
698+ ///
699+ /// Use this to introspect how an ``Environment`` was constructed (for
700+ /// example, to verify in tests that a configuration builder produced
701+ /// the expected inherited overrides or custom values without spawning a
702+ /// subprocess).
703+ public enum Representation : Sendable , Hashable {
704+ /// The environment inherits from the current process, with the given
705+ /// updates applied.
706+ ///
707+ /// A `nil` value for a key indicates that the key is unset relative to
708+ /// the inherited environment, rather than being set to an empty value.
709+ case inherited( updates: [ Key : String ? ] )
710+ /// The environment uses the given custom values, with no inheritance
711+ /// from the current process.
712+ case custom( [ Key : String ] )
713+ #if !os(Windows)
714+ /// The environment uses the given raw bytes, with no inheritance from
715+ /// the current process.
716+ ///
717+ /// - Note: This case is only available on POSIX platforms.
718+ case rawBytes( [ [ UInt8 ] ] )
719+ #endif
720+ }
721+
722+ /// The contents of this environment.
723+ public var representation : Representation {
724+ switch self . config {
725+ case . inherit( let updates) :
726+ return . inherited( updates: updates)
727+ case . custom( let values) :
728+ return . custom( values)
729+ #if !os(Windows)
730+ case . rawBytes( let bytes) :
731+ return . rawBytes( bytes)
732+ #endif
733+ }
734+ }
735+ }
736+
610737// MARK: - TerminationStatus
611738
612739/// The exit status of a subprocess.
0 commit comments