Skip to content

Commit 3d6df7c

Browse files
authored
[NFC] Sequester platform-specific code (#504)
1 parent 8a38c7c commit 3d6df7c

File tree

6 files changed

+159
-106
lines changed

6 files changed

+159
-106
lines changed

Sources/ArgumentParser/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ add_library(ArgumentParser
4141
Usage/UsageGenerator.swift
4242

4343
Utilities/CollectionExtensions.swift
44+
Utilities/Platform.swift
4445
Utilities/SequenceExtensions.swift
4546
Utilities/StringExtensions.swift
4647
Utilities/Tree.swift)

Sources/ArgumentParser/Completions/CompletionsGenerator.swift

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,6 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12-
#if canImport(Glibc)
13-
import Glibc
14-
#elseif canImport(Darwin)
15-
import Darwin
16-
#elseif canImport(CRT)
17-
import CRT
18-
#elseif canImport(WASILibc)
19-
import WASILibc
20-
#endif
21-
2212
/// A shell for which the parser can generate a completion script.
2313
public struct CompletionShell: RawRepresentable, Hashable, CaseIterable {
2414
public var rawValue: String
@@ -44,14 +34,7 @@ public struct CompletionShell: RawRepresentable, Hashable, CaseIterable {
4434

4535
/// Returns an instance representing the current shell, if recognized.
4636
public static func autodetected() -> CompletionShell? {
47-
#if os(Windows)
48-
return nil
49-
#else
50-
// FIXME: This retrieves the user's preferred shell, not necessarily the one currently in use.
51-
guard let shellVar = getenv("SHELL") else { return nil }
52-
let shellParts = String(cString: shellVar).split(separator: "/")
53-
return CompletionShell(rawValue: String(shellParts.last ?? ""))
54-
#endif
37+
Platform.shellName.flatMap(CompletionShell.init(rawValue:))
5538
}
5639

5740
/// An array of all supported shells for completion scripts.

Sources/ArgumentParser/Parsable Properties/Errors.swift

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

12-
#if canImport(Glibc)
13-
import Glibc
14-
#elseif canImport(Darwin)
15-
import Darwin
16-
#elseif canImport(CRT)
17-
import CRT
18-
#elseif canImport(WASILibc)
19-
import WASILibc
20-
#endif
21-
22-
#if os(Windows)
23-
import let WinSDK.ERROR_BAD_ARGUMENTS
24-
#endif
25-
2612
/// An error type that is presented to the user as an error with parsing their
2713
/// command-line input.
2814
public struct ValidationError: Error, CustomStringConvertible {
@@ -60,19 +46,13 @@ public struct ExitCode: Error, RawRepresentable, Hashable {
6046
}
6147

6248
/// An exit code that indicates successful completion of a command.
63-
public static let success = ExitCode(EXIT_SUCCESS)
49+
public static let success = ExitCode(Platform.exitCodeSuccess)
6450

6551
/// An exit code that indicates that the command failed.
66-
public static let failure = ExitCode(EXIT_FAILURE)
52+
public static let failure = ExitCode(Platform.exitCodeFailure)
6753

6854
/// An exit code that indicates that the user provided invalid input.
69-
#if os(Windows)
70-
public static let validationFailure = ExitCode(ERROR_BAD_ARGUMENTS)
71-
#elseif os(WASI)
72-
public static let validationFailure = ExitCode(EXIT_FAILURE)
73-
#else
74-
public static let validationFailure = ExitCode(EX_USAGE)
75-
#endif
55+
public static let validationFailure = ExitCode(Platform.exitCodeValidationFailure)
7656

7757
/// A Boolean value indicating whether this exit code represents the
7858
/// successful completion of a command.

Sources/ArgumentParser/Parsable Types/ParsableArguments.swift

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

12-
#if canImport(Glibc)
13-
import Glibc
14-
let _exit: (Int32) -> Never = Glibc.exit
15-
#elseif canImport(Darwin)
16-
import Darwin
17-
let _exit: (Int32) -> Never = Darwin.exit
18-
#elseif canImport(CRT)
19-
import CRT
20-
let _exit: (Int32) -> Never = ucrt._exit
21-
#elseif canImport(WASILibc)
22-
import WASILibc
23-
#endif
24-
2512
/// A type that can be parsed from a program's command-line arguments.
2613
///
2714
/// When you implement a `ParsableArguments` type, all properties must be declared with
@@ -60,14 +47,6 @@ struct _WrappedParsableCommand<P: ParsableArguments>: ParsableCommand {
6047
@OptionGroup var options: P
6148
}
6249

63-
struct StandardError: TextOutputStream {
64-
mutating func write(_ string: String) {
65-
for byte in string.utf8 { putc(numericCast(byte), stderr) }
66-
}
67-
}
68-
69-
var standardError = StandardError()
70-
7150
extension ParsableArguments {
7251
public mutating func validate() throws {}
7352

@@ -208,7 +187,7 @@ extension ParsableArguments {
208187
withError error: Error? = nil
209188
) -> Never {
210189
guard let error = error else {
211-
_exit(ExitCode.success.rawValue)
190+
Platform.exit(ExitCode.success.rawValue)
212191
}
213192

214193
let messageInfo = MessageInfo(error: error, type: self)
@@ -217,10 +196,10 @@ extension ParsableArguments {
217196
if messageInfo.shouldExitCleanly {
218197
print(fullText)
219198
} else {
220-
print(fullText, to: &standardError)
199+
print(fullText, to: &Platform.standardError)
221200
}
222201
}
223-
_exit(messageInfo.exitCode.rawValue)
202+
Platform.exit(messageInfo.exitCode.rawValue)
224203
}
225204

226205
/// Parses a new instance of this type from command-line arguments or exits

Sources/ArgumentParser/Usage/HelpGenerator.swift

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
internal struct HelpGenerator {
1313
static var helpIndent = 2
1414
static var labelColumnWidth = 26
15-
static var systemScreenWidth: Int { _terminalSize().width }
15+
static var systemScreenWidth: Int { Platform.terminalWidth }
1616

1717
struct Section {
1818
struct Element: Hashable {
@@ -386,43 +386,3 @@ internal extension BidirectionalCollection where Element == ParsableCommand.Type
386386
return arguments
387387
}
388388
}
389-
390-
#if canImport(Glibc)
391-
import Glibc
392-
func ioctl(_ a: Int32, _ b: Int32, _ p: UnsafeMutableRawPointer) -> Int32 {
393-
ioctl(CInt(a), UInt(b), p)
394-
}
395-
#elseif canImport(Darwin)
396-
import Darwin
397-
#elseif canImport(CRT)
398-
import CRT
399-
import WinSDK
400-
#endif
401-
402-
func _terminalSize() -> (width: Int, height: Int) {
403-
#if os(WASI)
404-
// WASI doesn't yet support terminal size
405-
return (80, 25)
406-
#elseif os(Windows)
407-
var csbi: CONSOLE_SCREEN_BUFFER_INFO = CONSOLE_SCREEN_BUFFER_INFO()
408-
guard GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) else {
409-
return (80, 25)
410-
}
411-
return (width: Int(csbi.srWindow.Right - csbi.srWindow.Left) + 1,
412-
height: Int(csbi.srWindow.Bottom - csbi.srWindow.Top) + 1)
413-
#else
414-
var w = winsize()
415-
#if os(OpenBSD)
416-
// TIOCGWINSZ is a complex macro, so we need the flattened value.
417-
let tiocgwinsz = Int32(0x40087468)
418-
let err = ioctl(STDOUT_FILENO, tiocgwinsz, &w)
419-
#else
420-
let err = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w)
421-
#endif
422-
let width = Int(w.ws_col)
423-
let height = Int(w.ws_row)
424-
guard err == 0 else { return (80, 25) }
425-
return (width: width > 0 ? width : 80,
426-
height: height > 0 ? height : 25)
427-
#endif
428-
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
//===----------------------------------------------------------*- swift -*-===//
2+
//
3+
// This source file is part of the Swift Argument Parser open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#if canImport(Glibc)
13+
import Glibc
14+
#elseif canImport(Darwin)
15+
import Darwin
16+
#elseif canImport(CRT)
17+
import CRT
18+
#elseif canImport(WASILibc)
19+
import WASILibc
20+
#endif
21+
22+
enum Platform {}
23+
24+
// MARK: Shell
25+
26+
extension Platform {
27+
/// The name of the user's preferred shell, if detectable from the
28+
/// environment.
29+
static var shellName: String? {
30+
#if os(Windows)
31+
return nil
32+
#else
33+
// FIXME: This retrieves the user's preferred shell, not necessarily the one currently in use.
34+
guard let shellVar = getenv("SHELL") else { return nil }
35+
let shellParts = String(cString: shellVar).split(separator: "/")
36+
return shellParts.last.map(String.init)
37+
#endif
38+
}
39+
}
40+
41+
// MARK: Exit codes
42+
43+
#if os(Windows)
44+
import let WinSDK.ERROR_BAD_ARGUMENTS
45+
#endif
46+
47+
extension Platform {
48+
/// The code for successful exit.
49+
static var exitCodeSuccess: Int32 {
50+
EXIT_SUCCESS
51+
}
52+
53+
/// The code for exit with a general failure.
54+
static var exitCodeFailure: Int32 {
55+
EXIT_FAILURE
56+
}
57+
58+
/// The code for exit with a validation failure.
59+
static var exitCodeValidationFailure: Int32 {
60+
#if os(Windows)
61+
return ERROR_BAD_ARGUMENTS
62+
#elseif os(WASI)
63+
return EXIT_FAILURE
64+
#else
65+
return EX_USAGE
66+
#endif
67+
}
68+
}
69+
70+
// MARK: Exit function
71+
72+
extension Platform {
73+
/// Complete execution with the given exit code.
74+
static func exit(_ code: Int32) -> Never {
75+
#if canImport(Glibc)
76+
Glibc.exit(code)
77+
#elseif canImport(Darwin)
78+
Darwin.exit(code)
79+
#elseif canImport(CRT)
80+
ucrt._exit(code)
81+
#elseif canImport(WASILibc)
82+
exit(code)
83+
#endif
84+
}
85+
}
86+
87+
// MARK: Standard error
88+
89+
extension Platform {
90+
/// A type that represents the `stderr` output stream.
91+
struct StandardError: TextOutputStream {
92+
mutating func write(_ string: String) {
93+
for byte in string.utf8 { putc(numericCast(byte), stderr) }
94+
}
95+
}
96+
97+
/// The `stderr` output stream.
98+
static var standardError = StandardError()
99+
}
100+
101+
// MARK: Terminal size
102+
103+
#if canImport(Glibc)
104+
func ioctl(_ a: Int32, _ b: Int32, _ p: UnsafeMutableRawPointer) -> Int32 {
105+
ioctl(CInt(a), UInt(b), p)
106+
}
107+
#endif
108+
109+
extension Platform {
110+
/// The default terminal size.
111+
static var defaultTerminalSize: (width: Int, height: Int) {
112+
(80, 25)
113+
}
114+
115+
/// Returns the current terminal size, or the default if the size is
116+
/// unavailable.
117+
static func terminalSize() -> (width: Int, height: Int) {
118+
#if os(WASI)
119+
// WASI doesn't yet support terminal size
120+
return defaultTerminalSize
121+
#elseif os(Windows)
122+
var csbi: CONSOLE_SCREEN_BUFFER_INFO = CONSOLE_SCREEN_BUFFER_INFO()
123+
guard GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) else {
124+
return defaultTerminalSize
125+
}
126+
return (width: Int(csbi.srWindow.Right - csbi.srWindow.Left) + 1,
127+
height: Int(csbi.srWindow.Bottom - csbi.srWindow.Top) + 1)
128+
#else
129+
var w = winsize()
130+
#if os(OpenBSD)
131+
// TIOCGWINSZ is a complex macro, so we need the flattened value.
132+
let tiocgwinsz = Int32(0x40087468)
133+
let err = ioctl(STDOUT_FILENO, tiocgwinsz, &w)
134+
#else
135+
let err = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w)
136+
#endif
137+
let width = Int(w.ws_col)
138+
let height = Int(w.ws_row)
139+
guard err == 0 else { return defaultTerminalSize }
140+
return (width: width > 0 ? width : defaultTerminalSize.width,
141+
height: height > 0 ? height : defaultTerminalSize.height)
142+
#endif
143+
}
144+
145+
/// The current terminal size, or the default if the width is unavailable.
146+
static var terminalWidth: Int {
147+
terminalSize().width
148+
}
149+
}
150+

0 commit comments

Comments
 (0)