Skip to content

Commit

Permalink
[NFC] Sequester platform-specific code (apple#504)
Browse files Browse the repository at this point in the history
  • Loading branch information
natecook1000 authored Oct 10, 2022
1 parent 8a38c7c commit 3d6df7c
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 106 deletions.
1 change: 1 addition & 0 deletions Sources/ArgumentParser/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ add_library(ArgumentParser
Usage/UsageGenerator.swift

Utilities/CollectionExtensions.swift
Utilities/Platform.swift
Utilities/SequenceExtensions.swift
Utilities/StringExtensions.swift
Utilities/Tree.swift)
Expand Down
19 changes: 1 addition & 18 deletions Sources/ArgumentParser/Completions/CompletionsGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,6 @@
//
//===----------------------------------------------------------------------===//

#if canImport(Glibc)
import Glibc
#elseif canImport(Darwin)
import Darwin
#elseif canImport(CRT)
import CRT
#elseif canImport(WASILibc)
import WASILibc
#endif

/// A shell for which the parser can generate a completion script.
public struct CompletionShell: RawRepresentable, Hashable, CaseIterable {
public var rawValue: String
Expand All @@ -44,14 +34,7 @@ public struct CompletionShell: RawRepresentable, Hashable, CaseIterable {

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

/// An array of all supported shells for completion scripts.
Expand Down
26 changes: 3 additions & 23 deletions Sources/ArgumentParser/Parsable Properties/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,6 @@
//
//===----------------------------------------------------------------------===//

#if canImport(Glibc)
import Glibc
#elseif canImport(Darwin)
import Darwin
#elseif canImport(CRT)
import CRT
#elseif canImport(WASILibc)
import WASILibc
#endif

#if os(Windows)
import let WinSDK.ERROR_BAD_ARGUMENTS
#endif

/// An error type that is presented to the user as an error with parsing their
/// command-line input.
public struct ValidationError: Error, CustomStringConvertible {
Expand Down Expand Up @@ -60,19 +46,13 @@ public struct ExitCode: Error, RawRepresentable, Hashable {
}

/// An exit code that indicates successful completion of a command.
public static let success = ExitCode(EXIT_SUCCESS)
public static let success = ExitCode(Platform.exitCodeSuccess)

/// An exit code that indicates that the command failed.
public static let failure = ExitCode(EXIT_FAILURE)
public static let failure = ExitCode(Platform.exitCodeFailure)

/// An exit code that indicates that the user provided invalid input.
#if os(Windows)
public static let validationFailure = ExitCode(ERROR_BAD_ARGUMENTS)
#elseif os(WASI)
public static let validationFailure = ExitCode(EXIT_FAILURE)
#else
public static let validationFailure = ExitCode(EX_USAGE)
#endif
public static let validationFailure = ExitCode(Platform.exitCodeValidationFailure)

/// A Boolean value indicating whether this exit code represents the
/// successful completion of a command.
Expand Down
27 changes: 3 additions & 24 deletions Sources/ArgumentParser/Parsable Types/ParsableArguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,6 @@
//
//===----------------------------------------------------------------------===//

#if canImport(Glibc)
import Glibc
let _exit: (Int32) -> Never = Glibc.exit
#elseif canImport(Darwin)
import Darwin
let _exit: (Int32) -> Never = Darwin.exit
#elseif canImport(CRT)
import CRT
let _exit: (Int32) -> Never = ucrt._exit
#elseif canImport(WASILibc)
import WASILibc
#endif

/// A type that can be parsed from a program's command-line arguments.
///
/// When you implement a `ParsableArguments` type, all properties must be declared with
Expand Down Expand Up @@ -60,14 +47,6 @@ struct _WrappedParsableCommand<P: ParsableArguments>: ParsableCommand {
@OptionGroup var options: P
}

struct StandardError: TextOutputStream {
mutating func write(_ string: String) {
for byte in string.utf8 { putc(numericCast(byte), stderr) }
}
}

var standardError = StandardError()

extension ParsableArguments {
public mutating func validate() throws {}

Expand Down Expand Up @@ -208,7 +187,7 @@ extension ParsableArguments {
withError error: Error? = nil
) -> Never {
guard let error = error else {
_exit(ExitCode.success.rawValue)
Platform.exit(ExitCode.success.rawValue)
}

let messageInfo = MessageInfo(error: error, type: self)
Expand All @@ -217,10 +196,10 @@ extension ParsableArguments {
if messageInfo.shouldExitCleanly {
print(fullText)
} else {
print(fullText, to: &standardError)
print(fullText, to: &Platform.standardError)
}
}
_exit(messageInfo.exitCode.rawValue)
Platform.exit(messageInfo.exitCode.rawValue)
}

/// Parses a new instance of this type from command-line arguments or exits
Expand Down
42 changes: 1 addition & 41 deletions Sources/ArgumentParser/Usage/HelpGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
internal struct HelpGenerator {
static var helpIndent = 2
static var labelColumnWidth = 26
static var systemScreenWidth: Int { _terminalSize().width }
static var systemScreenWidth: Int { Platform.terminalWidth }

struct Section {
struct Element: Hashable {
Expand Down Expand Up @@ -386,43 +386,3 @@ internal extension BidirectionalCollection where Element == ParsableCommand.Type
return arguments
}
}

#if canImport(Glibc)
import Glibc
func ioctl(_ a: Int32, _ b: Int32, _ p: UnsafeMutableRawPointer) -> Int32 {
ioctl(CInt(a), UInt(b), p)
}
#elseif canImport(Darwin)
import Darwin
#elseif canImport(CRT)
import CRT
import WinSDK
#endif

func _terminalSize() -> (width: Int, height: Int) {
#if os(WASI)
// WASI doesn't yet support terminal size
return (80, 25)
#elseif os(Windows)
var csbi: CONSOLE_SCREEN_BUFFER_INFO = CONSOLE_SCREEN_BUFFER_INFO()
guard GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) else {
return (80, 25)
}
return (width: Int(csbi.srWindow.Right - csbi.srWindow.Left) + 1,
height: Int(csbi.srWindow.Bottom - csbi.srWindow.Top) + 1)
#else
var w = winsize()
#if os(OpenBSD)
// TIOCGWINSZ is a complex macro, so we need the flattened value.
let tiocgwinsz = Int32(0x40087468)
let err = ioctl(STDOUT_FILENO, tiocgwinsz, &w)
#else
let err = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w)
#endif
let width = Int(w.ws_col)
let height = Int(w.ws_row)
guard err == 0 else { return (80, 25) }
return (width: width > 0 ? width : 80,
height: height > 0 ? height : 25)
#endif
}
150 changes: 150 additions & 0 deletions Sources/ArgumentParser/Utilities/Platform.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
//===----------------------------------------------------------*- 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
//
//===----------------------------------------------------------------------===//

#if canImport(Glibc)
import Glibc
#elseif canImport(Darwin)
import Darwin
#elseif canImport(CRT)
import CRT
#elseif canImport(WASILibc)
import WASILibc
#endif

enum Platform {}

// MARK: Shell

extension Platform {
/// The name of the user's preferred shell, if detectable from the
/// environment.
static var shellName: String? {
#if os(Windows)
return nil
#else
// FIXME: This retrieves the user's preferred shell, not necessarily the one currently in use.
guard let shellVar = getenv("SHELL") else { return nil }
let shellParts = String(cString: shellVar).split(separator: "/")
return shellParts.last.map(String.init)
#endif
}
}

// MARK: Exit codes

#if os(Windows)
import let WinSDK.ERROR_BAD_ARGUMENTS
#endif

extension Platform {
/// The code for successful exit.
static var exitCodeSuccess: Int32 {
EXIT_SUCCESS
}

/// The code for exit with a general failure.
static var exitCodeFailure: Int32 {
EXIT_FAILURE
}

/// The code for exit with a validation failure.
static var exitCodeValidationFailure: Int32 {
#if os(Windows)
return ERROR_BAD_ARGUMENTS
#elseif os(WASI)
return EXIT_FAILURE
#else
return EX_USAGE
#endif
}
}

// MARK: Exit function

extension Platform {
/// Complete execution with the given exit code.
static func exit(_ code: Int32) -> Never {
#if canImport(Glibc)
Glibc.exit(code)
#elseif canImport(Darwin)
Darwin.exit(code)
#elseif canImport(CRT)
ucrt._exit(code)
#elseif canImport(WASILibc)
exit(code)
#endif
}
}

// MARK: Standard error

extension Platform {
/// A type that represents the `stderr` output stream.
struct StandardError: TextOutputStream {
mutating func write(_ string: String) {
for byte in string.utf8 { putc(numericCast(byte), stderr) }
}
}

/// The `stderr` output stream.
static var standardError = StandardError()
}

// MARK: Terminal size

#if canImport(Glibc)
func ioctl(_ a: Int32, _ b: Int32, _ p: UnsafeMutableRawPointer) -> Int32 {
ioctl(CInt(a), UInt(b), p)
}
#endif

extension Platform {
/// The default terminal size.
static var defaultTerminalSize: (width: Int, height: Int) {
(80, 25)
}

/// Returns the current terminal size, or the default if the size is
/// unavailable.
static func terminalSize() -> (width: Int, height: Int) {
#if os(WASI)
// WASI doesn't yet support terminal size
return defaultTerminalSize
#elseif os(Windows)
var csbi: CONSOLE_SCREEN_BUFFER_INFO = CONSOLE_SCREEN_BUFFER_INFO()
guard GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) else {
return defaultTerminalSize
}
return (width: Int(csbi.srWindow.Right - csbi.srWindow.Left) + 1,
height: Int(csbi.srWindow.Bottom - csbi.srWindow.Top) + 1)
#else
var w = winsize()
#if os(OpenBSD)
// TIOCGWINSZ is a complex macro, so we need the flattened value.
let tiocgwinsz = Int32(0x40087468)
let err = ioctl(STDOUT_FILENO, tiocgwinsz, &w)
#else
let err = ioctl(STDOUT_FILENO, TIOCGWINSZ, &w)
#endif
let width = Int(w.ws_col)
let height = Int(w.ws_row)
guard err == 0 else { return defaultTerminalSize }
return (width: width > 0 ? width : defaultTerminalSize.width,
height: height > 0 ? height : defaultTerminalSize.height)
#endif
}

/// The current terminal size, or the default if the width is unavailable.
static var terminalWidth: Int {
terminalSize().width
}
}

0 comments on commit 3d6df7c

Please sign in to comment.