Skip to content

Commit

Permalink
Merge branch 'apple:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
YourMJK authored Jun 30, 2023
2 parents e323f3f + 00b8408 commit 3bf4005
Show file tree
Hide file tree
Showing 32 changed files with 643 additions and 450 deletions.
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ Add new items at the end of the relevant section under **Unreleased**.

---

## [1.2.2] - 2023-02-09

### Fixes

- Arguments with the `.allUnrecognized` parsing strategy no longer consume
built-in flags like `--help` and `--version`. ([#550])
- Fixes an issue introduced in version 1.2.0 where properties with underscored
names couldn't be parsed. ([#548])
- Improves the error message for cases where platform availability causes the
synchronous `ParsableCommand.main()` static method to be run on an
`AsyncParsableCommand` type. ([#547])

## [1.2.1] - 2023-01-12

### Changes
Expand Down Expand Up @@ -764,7 +776,8 @@ This changelog's format is based on [Keep a Changelog](https://keepachangelog.co

<!-- Link references for releases -->

[Unreleased]: https://github.com/apple/swift-argument-parser/compare/1.2.1...HEAD
[Unreleased]: https://github.com/apple/swift-argument-parser/compare/1.2.2...HEAD
[1.2.2]: https://github.com/apple/swift-argument-parser/compare/1.2.1...1.2.2
[1.2.1]: https://github.com/apple/swift-argument-parser/compare/1.2.0...1.2.1
[1.2.0]: https://github.com/apple/swift-argument-parser/compare/1.1.4...1.2.0
[1.1.4]: https://github.com/apple/swift-argument-parser/compare/1.1.3...1.1.4
Expand Down Expand Up @@ -851,6 +864,9 @@ This changelog's format is based on [Keep a Changelog](https://keepachangelog.co
[#522]: https://github.com/apple/swift-argument-parser/pull/522
[#535]: https://github.com/apple/swift-argument-parser/pull/535
[#542]: https://github.com/apple/swift-argument-parser/pull/542
[#547]: https://github.com/apple/swift-argument-parser/pull/547
[#548]: https://github.com/apple/swift-argument-parser/pull/548
[#550]: https://github.com/apple/swift-argument-parser/pull/550

<!-- Link references for contributors -->

Expand Down
9 changes: 2 additions & 7 deletions Examples/count-lines/CountLines.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import ArgumentParser
import Foundation

@main
@available(macOS 10.15, *)
@available(macOS 12, *)
struct CountLines: AsyncParsableCommand {
@Argument(
help: "A file to count lines in. If omitted, counts the lines of stdin.",
Expand All @@ -27,7 +27,7 @@ struct CountLines: AsyncParsableCommand {
var verbose = false
}

@available(macOS 10.15, *)
@available(macOS 12, *)
extension CountLines {
var fileHandle: FileHandle {
get throws {
Expand Down Expand Up @@ -58,11 +58,6 @@ extension CountLines {
}

mutating func run() async throws {
guard #available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *) else {
print("'count-lines' isn't supported on this platform.")
return
}

let countAllLines = prefix == nil
let lineCount = try await fileHandle.bytes.lines.reduce(0) { count, line in
if countAllLines || line.starts(with: prefix!) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ struct BashCompletionsGenerator {
///
/// These consist of completions that are defined as `.list` or `.custom`.
fileprivate static func generateArgumentCompletions(_ commands: [ParsableCommand.Type]) -> [String] {
ArgumentSet(commands.last!, visibility: .default, parent: .root)
ArgumentSet(commands.last!, visibility: .default, parent: nil)
.compactMap { arg -> String? in
guard arg.isPositional else { return nil }

Expand All @@ -159,7 +159,7 @@ struct BashCompletionsGenerator {

/// Returns the case-matching statements for supplying completions after an option or flag.
fileprivate static func generateOptionHandlers(_ commands: [ParsableCommand.Type]) -> String {
ArgumentSet(commands.last!, visibility: .default, parent: .root)
ArgumentSet(commands.last!, visibility: .default, parent: nil)
.compactMap { arg -> String? in
let words = arg.bashCompletionWords()
if words.isEmpty { return nil }
Expand Down
8 changes: 4 additions & 4 deletions Sources/ArgumentParser/Parsable Properties/Flag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ extension Flag where Value: EnumerableFlag {
// flag, the default value to show to the user is the `--value-name`
// flag that a user would provide on the command line, not a Swift value.
let defaultValueFlag = initial.flatMap { value -> String? in
let defaultKey = InputKey(name: String(describing: value), parent: .key(key))
let defaultKey = InputKey(name: String(describing: value), parent: key)
let defaultNames = Value.name(for: value).makeNames(defaultKey)
return defaultNames.first?.synopsisString
}
Expand All @@ -405,7 +405,7 @@ extension Flag where Value: EnumerableFlag {
let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil })

let args = Value.allCases.enumerated().map { (i, value) -> ArgumentDefinition in
let caseKey = InputKey(name: String(describing: value), parent: .key(key))
let caseKey = InputKey(name: String(describing: value), parent: key)
let name = Value.name(for: value)

let helpForCase = caseHelps[i] ?? help
Expand Down Expand Up @@ -519,7 +519,7 @@ extension Flag {
let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil })

let args = Element.allCases.enumerated().map { (i, value) -> ArgumentDefinition in
let caseKey = InputKey(name: String(describing: value), parent: .key(parentKey))
let caseKey = InputKey(name: String(describing: value), parent: parentKey)
let name = Element.name(for: value)
let helpForCase = hasCustomCaseHelp ? (caseHelps[i] ?? help) : help

Expand Down Expand Up @@ -552,7 +552,7 @@ extension Flag {
let hasCustomCaseHelp = caseHelps.contains(where: { $0 != nil })

let args = Element.allCases.enumerated().map { (i, value) -> ArgumentDefinition in
let caseKey = InputKey(name: String(describing: value), parent: .key(parentKey))
let caseKey = InputKey(name: String(describing: value), parent: parentKey)
let name = Element.name(for: value)
let helpForCase = hasCustomCaseHelp ? (caseHelps[i] ?? help) : help
let help = ArgumentDefinition.Help(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ extension FlagInversion {
case .short, .customShort:
return includingShort ? element.name(for: key) : nil
case .long:
let modifiedKey = key.with(newName: key.name.addingIntercappedPrefix(prefix))
let modifiedKey = InputKey(name: key.name.addingIntercappedPrefix(prefix), parent: key)
return element.name(for: modifiedKey)
case .customLong(let name, let withSingleDash):
let modifiedName = name.addingPrefixWithAutodetectedStyle(prefix)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public struct OptionGroup<Value: ParsableArguments>: Decodable, ParsedWrapper {
visibility: ArgumentVisibility = .default
) {
self.init(_parsedValue: .init { parentKey in
var args = ArgumentSet(Value.self, visibility: .private, parent: .key(parentKey))
var args = ArgumentSet(Value.self, visibility: .private, parent: parentKey)
args.content.withEach {
$0.help.parentTitle = title
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ extension ArgumentSetProvider {
}

extension ArgumentSet {
init(_ type: ParsableArguments.Type, visibility: ArgumentVisibility, parent: InputKey.Parent) {
init(_ type: ParsableArguments.Type, visibility: ArgumentVisibility, parent: InputKey?) {
#if DEBUG
do {
try type._validate(parent: parent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
//===----------------------------------------------------------------------===//

fileprivate protocol ParsableArgumentsValidator {
static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError?
static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError?
}

enum ValidatorErrorKind {
Expand All @@ -37,7 +37,7 @@ struct ParsableArgumentsValidationError: Error, CustomStringConvertible {
}

extension ParsableArguments {
static func _validate(parent: InputKey.Parent) throws {
static func _validate(parent: InputKey?) throws {
let validators: [ParsableArgumentsValidator.Type] = [
PositionalArgumentsValidator.self,
ParsableArgumentsCodingKeyValidator.self,
Expand Down Expand Up @@ -80,7 +80,7 @@ struct PositionalArgumentsValidator: ParsableArgumentsValidator {
var kind: ValidatorErrorKind { .failure }
}

static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? {
static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? {
let sets: [ArgumentSet] = Mirror(reflecting: type.init())
.children
.compactMap { child in
Expand Down Expand Up @@ -190,7 +190,7 @@ struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator {
}
}

static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? {
static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? {
let argumentKeys: [InputKey] = Mirror(reflecting: type.init())
.children
.compactMap { child in
Expand Down Expand Up @@ -235,7 +235,7 @@ struct ParsableArgumentsUniqueNamesValidator: ParsableArgumentsValidator {
var kind: ValidatorErrorKind { .failure }
}

static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? {
static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? {
let argSets: [ArgumentSet] = Mirror(reflecting: type.init())
.children
.compactMap { child in
Expand Down Expand Up @@ -283,7 +283,7 @@ struct NonsenseFlagsValidator: ParsableArgumentsValidator {
var kind: ValidatorErrorKind { .warning }
}

static func validate(_ type: ParsableArguments.Type, parent: InputKey.Parent) -> ParsableArgumentsValidatorError? {
static func validate(_ type: ParsableArguments.Type, parent: InputKey?) -> ParsableArgumentsValidatorError? {
let argSets: [ArgumentSet] = Mirror(reflecting: type.init())
.children
.compactMap { child in
Expand Down
74 changes: 69 additions & 5 deletions Sources/ArgumentParser/Parsable Types/ParsableCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,13 @@ extension ParsableCommand {

#if DEBUG
if #available(macOS 12.0, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *) {
checkAsyncHierarchy(self, root: "\(self)")
if let asyncCommand = firstAsyncSubcommand(self) {
if Self() is AsyncParsableCommand {
failAsyncPlatform(rootCommand: self)
} else {
failAsyncHierarchy(rootCommand: self, subCommand: asyncCommand)
}
}
}
#endif

Expand Down Expand Up @@ -159,17 +165,23 @@ extension ParsableCommand {
extension ParsableCommand {
/// `true` if this command contains any array arguments that are declared
/// with `.unconditionalRemaining`.
internal static var includesUnconditionalArguments: Bool {
ArgumentSet(self, visibility: .private, parent: .root).contains(where: {
internal static var includesPassthroughArguments: Bool {
ArgumentSet(self, visibility: .private, parent: nil).contains(where: {
$0.isRepeatingPositional && $0.parsingStrategy == .allRemainingInput
})
}

internal static var includesAllUnrecognizedArgument: Bool {
ArgumentSet(self, visibility: .private, parent: nil).contains(where: {
$0.isRepeatingPositional && $0.parsingStrategy == .allUnrecognized
})
}

/// `true` if this command's default subcommand contains any array arguments
/// that are declared with `.unconditionalRemaining`. This is `false` if
/// there's no default subcommand.
internal static var defaultIncludesUnconditionalArguments: Bool {
configuration.defaultSubcommand?.includesUnconditionalArguments == true
internal static var defaultIncludesPassthroughArguments: Bool {
configuration.defaultSubcommand?.includesPassthroughArguments == true
}

#if DEBUG
Expand All @@ -194,5 +206,57 @@ extension ParsableCommand {
""".wrapped(to: 70))
}
}

@available(macOS 12.0, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
internal static func firstAsyncSubcommand(_ command: ParsableCommand.Type) -> AsyncParsableCommand.Type? {
for sub in command.configuration.subcommands {
if let asyncCommand = sub as? AsyncParsableCommand.Type,
sub.configuration.subcommands.isEmpty
{
return asyncCommand
}

if let asyncCommand = firstAsyncSubcommand(sub) {
return asyncCommand
}
}

return nil
}
#endif
}

// MARK: Async Configuration Errors

func failAsyncHierarchy(
rootCommand: ParsableCommand.Type, subCommand: ParsableCommand.Type
) -> Never {
fatalError("""
--------------------------------------------------------------------
Asynchronous subcommand of a synchronous root.
The asynchronous command `\(subCommand)` is declared as a subcommand of the synchronous root command `\(rootCommand)`.
With this configuration, your asynchronous `run()` method will not be called. To fix this issue, change `\(rootCommand)`'s `ParsableCommand` conformance to `AsyncParsableCommand`.
--------------------------------------------------------------------
""".wrapped(to: 70))
}

func failAsyncPlatform(rootCommand: ParsableCommand.Type) -> Never {
fatalError("""
--------------------------------------------------------------------
Asynchronous root command needs availability annotation.
The asynchronous root command `\(rootCommand)` needs an availability annotation in order to be executed asynchronously. To fix this issue, add the following availability attribute to your `\(rootCommand)` declaration or set the minimum platform in your "Package.swift" file.
""".wrapped(to: 70)
+ """
@available(macOS 12.0, macCatalyst 13, iOS 13, tvOS 13, watchOS 6, *)
--------------------------------------------------------------------
""")
}
7 changes: 7 additions & 0 deletions Sources/ArgumentParser/Parsing/ArgumentDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ final class ParsedArgumentsContainer<K>: KeyedDecodingContainerProtocol where K
}

func decode<T>(_ type: T.Type, forKey key: K) throws -> T where T : Decodable {
let parsedElement = element(forKey: key)
if parsedElement?.inputOrigin.isDefaultValue ?? false, let rawValue = parsedElement?.value {
guard let value = rawValue as? T else {
throw InternalParseError.wrongType(rawValue, forKey: parsedElement!.key)
}
return value
}
let subDecoder = SingleValueDecoder(userInfo: decoder.userInfo, underlying: decoder, codingPath: codingPath + [key], key: InputKey(codingKey: key, path: codingPath), parsedElement: element(forKey: key))
return try type.init(from: subDecoder)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/ArgumentParser/Parsing/ArgumentDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ extension ArgumentDefinition {
///
/// This initializer is used for any property defined on a `ParsableArguments`
/// type that isn't decorated with one of ArgumentParser's property wrappers.
init(unparsedKey: String, default defaultValue: Any?, parent: InputKey.Parent) {
init(unparsedKey: String, default defaultValue: Any?, parent: InputKey?) {
self.init(
container: Bare<Any>.self,
key: InputKey(name: unparsedKey, parent: parent),
Expand Down
Loading

0 comments on commit 3bf4005

Please sign in to comment.