mirror of
https://github.com/apple/swift-argument-parser.git
synced 2026-05-07 20:12:41 +00:00
Augment Option to support default as flag option (#830)
In some use cases, there is a need to have an option argument behave
like a flag.
This change introduced 4 new intialiazers to `Option` that accept a
`defaultAsFlag` value.
With the following usage:
```swift
struct Example: ParsableCommand {
@Option(defaultAsFlag: "default", help: "Set output format.")
var format: String?
func run() {
print("Format: \(format ?? "none")")
}
}
```
The `defaultAsFlag` parameter creates a hybrid that supports both patterns:
- **Flag behavior**: `--format` (sets format to "default")
- **Option behavior**: `--format json` (sets format to "json")
- **No usage**: format remains `nil`
As a user of the command line tool, the `--help` output clearly distinguishes
between the the hybrid and regular usages.
```
OPTIONS:
--format [<format>] Set output format. (default as flag: default)
````
Note the `(default as flag: ...)` text instead of regular `(default: ...)`,
and the optional value syntax `[<value>]` instead of required `<value>`.
Fixes: #829
This commit is contained in:
committed by
GitHub
parent
747565c049
commit
a78d98b92d
@@ -0,0 +1,54 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2025 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 ArgumentParser
|
||||
|
||||
@main
|
||||
struct DefaultAsFlag: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
abstract: "A utility demonstrating defaultAsFlag options.",
|
||||
discussion: """
|
||||
This command shows how defaultAsFlag options can work both as flags
|
||||
and as options with values.
|
||||
"""
|
||||
)
|
||||
|
||||
@Option(defaultAsFlag: "default", help: "A string option with defaultAsFlag.")
|
||||
var stringFlag: String?
|
||||
|
||||
@Option(defaultAsFlag: 42, help: "An integer option with defaultAsFlag.")
|
||||
var numberFlag: Int?
|
||||
|
||||
@Option(defaultAsFlag: true, help: "A boolean option with defaultAsFlag.")
|
||||
var boolFlag: Bool?
|
||||
|
||||
@Option(
|
||||
defaultAsFlag: "transformed",
|
||||
help: "A string option with transform and defaultAsFlag.",
|
||||
transform: { $0.uppercased() }
|
||||
)
|
||||
var transformFlag: String?
|
||||
|
||||
@Option(name: .shortAndLong, help: "A regular option for comparison.")
|
||||
var regular: String?
|
||||
|
||||
@Argument
|
||||
var additionalArgs: [String] = []
|
||||
|
||||
func run() {
|
||||
print("String flag: \(stringFlag?.description ?? "nil")")
|
||||
print("Number flag: \(numberFlag?.description ?? "nil")")
|
||||
print("Bool flag: \(boolFlag?.description ?? "nil")")
|
||||
print("Transform flag: \(transformFlag?.description ?? "nil")")
|
||||
print("Regular option: \(regular?.description ?? "nil")")
|
||||
print("Additional args: \(additionalArgs)")
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,11 @@ var package = Package(
|
||||
name: "color",
|
||||
dependencies: ["ArgumentParser"],
|
||||
path: "Examples/color"),
|
||||
.executableTarget(
|
||||
name: "default-as-flag",
|
||||
dependencies: ["ArgumentParser"],
|
||||
path: "Examples/default-as-flag"
|
||||
),
|
||||
|
||||
// Tools
|
||||
.executableTarget(
|
||||
|
||||
@@ -7,7 +7,7 @@ that you need to collect from the command line.
|
||||
Decorate each stored property with one of `ArgumentParser`'s property wrappers,
|
||||
and then declare conformance to `ParsableCommand` and add the `@main` attribute.
|
||||
(Note, for `async` renditions of `run`, conform to `AsyncParsableCommand` rather
|
||||
than `ParsableCommand`.)
|
||||
than `ParsableCommand`.)
|
||||
Finally, implement your command's logic in the `run()` method.
|
||||
|
||||
```swift
|
||||
@@ -70,7 +70,7 @@ OPTIONS:
|
||||
|
||||
## Documentation
|
||||
|
||||
For guides, articles, and API documentation see the
|
||||
For guides, articles, and API documentation see the
|
||||
[library's documentation on the Web][docs] or in Xcode.
|
||||
|
||||
- [ArgumentParser documentation][docs]
|
||||
@@ -88,6 +88,7 @@ This repository includes a few examples of using the library:
|
||||
- [`roll`](Examples/roll/main.swift) is a simple utility implemented as a straight-line script.
|
||||
- [`math`](Examples/math/Math.swift) is an annotated example of using nested commands and subcommands.
|
||||
- [`count-lines`](Examples/count-lines/CountLines.swift) uses `async`/`await` code in its implementation.
|
||||
- [`default-as-flag`](Examples/default-as-flag/DefaultAsFlag.swift) demonstrates hybrid options that can work both as flags and as options with values.
|
||||
|
||||
You can also see examples of `ArgumentParser` adoption among Swift project tools:
|
||||
|
||||
@@ -104,7 +105,7 @@ The public API of version 1.0.0 of the `swift-argument-parser` package
|
||||
consists of non-underscored declarations that are marked public in the `ArgumentParser` module.
|
||||
Interfaces that aren't part of the public API may continue to change in any release,
|
||||
including the exact wording and formatting of the autogenerated help and error messages,
|
||||
as well as the package’s examples, tests, utilities, and documentation.
|
||||
as well as the package’s examples, tests, utilities, and documentation.
|
||||
|
||||
Future minor versions of the package may introduce changes to these rules as needed.
|
||||
|
||||
@@ -115,7 +116,7 @@ Requiring a new Swift release will only require a minor version bump.
|
||||
|
||||
## Adding `ArgumentParser` as a Dependency
|
||||
|
||||
To use the `ArgumentParser` library in a SwiftPM project,
|
||||
To use the `ArgumentParser` library in a SwiftPM project,
|
||||
add it to the dependencies for your package and your command-line executable target:
|
||||
|
||||
```swift
|
||||
|
||||
@@ -81,7 +81,7 @@ struct Lucky: ParsableCommand {
|
||||
```
|
||||
|
||||
```
|
||||
% lucky
|
||||
% lucky
|
||||
Your lucky numbers are:
|
||||
7 14 21
|
||||
% lucky 1 2 3
|
||||
@@ -327,6 +327,83 @@ If a default is not specified, the user must provide a value for that argument/o
|
||||
You must also always specify a default of `false` for a non-optional `Bool` flag, as in the example above. This makes the behavior consistent with both normal Swift properties (which either must be explicitly initialized or optional to initialize a `struct`/`class` containing them) and the other property types.
|
||||
|
||||
|
||||
### Creating hybrid flag/option behavior with defaultAsFlag
|
||||
|
||||
The `defaultAsFlag` parameter allows you to create options that can work both as flags (without values) and as options (with values). This provides flexible command-line interfaces where users can choose between concise flag usage or explicit value specification.
|
||||
|
||||
```swift
|
||||
struct Example: ParsableCommand {
|
||||
@Option(defaultAsFlag: "json", help: "Set the export format.")
|
||||
var export: String?
|
||||
|
||||
func run() {
|
||||
print("Export: \(format ?? "<don't export>")")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Command-line behavior:**
|
||||
```
|
||||
% example # export = nil
|
||||
% example --export # export = "json"
|
||||
% example --export yaml # format = "yaml"
|
||||
```
|
||||
|
||||
The `defaultAsFlag` parameter creates a hybrid that supports both patterns:
|
||||
- **Flag behavior**: `--export` (sets format to "json")
|
||||
- **Option behavior**: `--export yaml` (sets format to "yaml")
|
||||
- **No usage**: `export` remains `nil`
|
||||
|
||||
#### Type requirements
|
||||
|
||||
- The property **must** be optional (`T?`)
|
||||
- The `defaultAsFlag` value must be of the unwrapped type (`T`)
|
||||
- All standard `ExpressibleByArgument` types are supported (String, Int, Bool, Double, etc.)
|
||||
|
||||
#### Advanced usage
|
||||
|
||||
You can combine `defaultAsFlag` with transform functions:
|
||||
|
||||
```swift
|
||||
@Option(
|
||||
defaultAsFlag: "info",
|
||||
help: "Set log level.",
|
||||
transform: { $0.uppercased() }
|
||||
)
|
||||
var logLevel: String?
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
```
|
||||
% app --log-level # logLevel = "INFO" (transformed default)
|
||||
% app --log-level debug # logLevel = "DEBUG" (transformed input)
|
||||
```
|
||||
|
||||
#### Help display
|
||||
|
||||
DefaultAsFlag options show special help formatting to distinguish them from regular defaults:
|
||||
|
||||
```
|
||||
OPTIONS:
|
||||
--format [<format>] Set output format. (default as flag: json)
|
||||
--port [<port>] Server port. (default as flag: 8080)
|
||||
```
|
||||
|
||||
Note the `(default as flag: ...)` text instead of regular `(default: ...)`, and the optional value syntax `[<value>]` instead of required `<value>`.
|
||||
|
||||
#### Value detection
|
||||
|
||||
The parser determines whether a value follows the option:
|
||||
|
||||
1. **Next argument is a value** if it doesn't start with `-` and isn't another known option
|
||||
2. **No value available**: Use the `defaultAsFlag` value
|
||||
3. **Explicit value provided**: Parse and use that value
|
||||
|
||||
This works with parsing strategies `.next` and `.scanningForValue`. The `.unconditional` parsing strategy defeats the purpose by always requiring a value.
|
||||
|
||||
For complete examples and API reference, see the [`default-as-flag`](https://github.com/apple/swift-argument-parser/tree/main/Examples/default-as-flag) example.
|
||||
|
||||
|
||||
### Specifying a parsing strategy
|
||||
|
||||
When parsing a list of command-line inputs, `ArgumentParser` distinguishes between dash-prefixed keys and un-prefixed values. When looking for the value for a key, only an un-prefixed value will be selected by default.
|
||||
@@ -479,7 +556,7 @@ When appropriate, you can process supported arguments and ignore unknown ones by
|
||||
```swift
|
||||
struct Example: ParsableCommand {
|
||||
@Flag var verbose = false
|
||||
|
||||
|
||||
@Argument(parsing: .allUnrecognized)
|
||||
var unknowns: [String] = []
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ public struct SingleValueParsingStrategy: Hashable {
|
||||
///
|
||||
/// For inputs such as `--foo foo`, this would parse `foo` as the
|
||||
/// value. However, the input `--foo --bar foo bar` would
|
||||
/// result in an error. Even though two values are provided, they don’t
|
||||
/// result in an error. Even though two values are provided, they don't
|
||||
/// succeed each option. Parsing would result in an error such as the following:
|
||||
///
|
||||
/// Error: Missing value for '--foo <foo>'
|
||||
@@ -161,7 +161,46 @@ public struct SingleValueParsingStrategy: Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
/// The strategy to use when parsing a single value from `@Option` arguments with `defaultAsFlag`.
|
||||
///
|
||||
/// This is a subset of `SingleValueParsingStrategy` that excludes strategies incompatible
|
||||
/// with default-as-flag behavior.
|
||||
public struct DefaultAsFlagParsingStrategy: Hashable {
|
||||
internal var base: ArgumentDefinition.ParsingStrategy
|
||||
|
||||
/// Parse the input after the option and expect it to be a value.
|
||||
///
|
||||
/// For inputs such as `--foo foo`, this would parse `foo` as the
|
||||
/// value. However, the input `--foo --bar foo bar` would
|
||||
/// result in an error. Even though two values are provided, they don't
|
||||
/// succeed each option. Parsing would result in an error such as the following:
|
||||
///
|
||||
/// Error: Missing value for '--foo <foo>'
|
||||
/// Usage: command [--foo <foo>]
|
||||
///
|
||||
/// When used with `defaultAsFlag`, if no value is found, the default flag value is used.
|
||||
public static var next: DefaultAsFlagParsingStrategy {
|
||||
self.init(base: .default)
|
||||
}
|
||||
|
||||
/// Parse the next input, as long as that input can't be interpreted as
|
||||
/// an option or flag.
|
||||
///
|
||||
/// - Note: This will skip other options and _read ahead_ in the input
|
||||
/// to find the next available value. This may be *unexpected* for users.
|
||||
/// Use with caution.
|
||||
///
|
||||
/// For example, if `--foo` takes a value, then the input `--foo --bar bar`
|
||||
/// would be parsed such that the value `bar` is used for `--foo`.
|
||||
///
|
||||
/// This is the **default behavior** for `defaultAsFlag` options.
|
||||
public static var scanningForValue: DefaultAsFlagParsingStrategy {
|
||||
self.init(base: .scanningForValue)
|
||||
}
|
||||
}
|
||||
|
||||
extension SingleValueParsingStrategy: Sendable {}
|
||||
extension DefaultAsFlagParsingStrategy: Sendable {}
|
||||
|
||||
/// The strategy to use when parsing multiple values from `@Option` arguments into an
|
||||
/// array.
|
||||
@@ -208,7 +247,7 @@ public struct ArrayParsingStrategy: Hashable {
|
||||
/// the input `--files foo bar` would result in the array
|
||||
/// `["foo", "bar"]`.
|
||||
///
|
||||
/// Parsing stops as soon as there’s another option in the input such that
|
||||
/// Parsing stops as soon as there's another option in the input such that
|
||||
/// `--files foo bar --verbose` would also set `files` to the array
|
||||
/// `["foo", "bar"]`.
|
||||
public static var upToNextOption: ArrayParsingStrategy {
|
||||
@@ -503,6 +542,70 @@ extension Option {
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates an optional property that reads its value from a labeled option,
|
||||
/// with a default value when the flag is provided without a value.
|
||||
///
|
||||
/// This initializer allows providing a `defaultAsFlag` value that is used
|
||||
/// when the flag is present but no value follows it:
|
||||
///
|
||||
/// ```swift
|
||||
/// @Option(name: .customLong("bin-path"), defaultAsFlag: "/default/path")
|
||||
/// var showBinPath: String? = nil
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - wrappedValue: A default value to use for this property, provided
|
||||
/// implicitly by the compiler during property wrapper initialization.
|
||||
/// - name: A specification for what names are allowed for this option.
|
||||
/// - defaultAsFlag: The value to use when the flag is provided without a value.
|
||||
/// - parsingStrategy: The behavior to use when looking for this option's value.
|
||||
/// - help: Information about how to use this option.
|
||||
/// - completion: The type of command-line completion provided for this option.
|
||||
public init<T>(
|
||||
wrappedValue: _OptionalNilComparisonType,
|
||||
name: NameSpecification = .long,
|
||||
defaultAsFlag: T,
|
||||
parsing parsingStrategy: DefaultAsFlagParsingStrategy = .scanningForValue,
|
||||
help: ArgumentHelp? = nil,
|
||||
completion: CompletionKind? = nil
|
||||
) where T: ExpressibleByArgument, Value == T? {
|
||||
self.init(
|
||||
_parsedValue: .init { key in
|
||||
let arg = ArgumentDefinition(
|
||||
kind: .name(key: key, specification: name),
|
||||
help: .init(
|
||||
allValueStrings: T.allValueStrings,
|
||||
options: [.isOptional],
|
||||
help: help,
|
||||
defaultValue: String(describing: defaultAsFlag),
|
||||
key: key,
|
||||
isComposite: false),
|
||||
completion: completion ?? T.defaultCompletionKind,
|
||||
parsingStrategy: parsingStrategy.base,
|
||||
update: .optionalUnary(
|
||||
nullaryHandler: { (origin, name, parsedValues) in
|
||||
// Act like a flag - when present without value, use defaultAsFlag
|
||||
parsedValues.set(defaultAsFlag, forKey: key, inputOrigin: origin)
|
||||
},
|
||||
unaryHandler: { (origin, name, valueString, parsedValues) in
|
||||
// Parse the provided value
|
||||
guard let parsedValue = T(argument: valueString) else {
|
||||
throw ParserError.unableToParseValue(
|
||||
origin, name, valueString, forKey: key, originalError: nil)
|
||||
}
|
||||
parsedValues.set(parsedValue, forKey: key, inputOrigin: origin)
|
||||
}
|
||||
),
|
||||
initial: { origin, values in
|
||||
values.set(
|
||||
nil, forKey: key, inputOrigin: InputOrigin(element: .defaultValue)
|
||||
)
|
||||
})
|
||||
|
||||
return ArgumentSet(arg)
|
||||
})
|
||||
}
|
||||
|
||||
@available(
|
||||
*, deprecated,
|
||||
message: """
|
||||
@@ -580,6 +683,68 @@ extension Option {
|
||||
return ArgumentSet(arg)
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates an optional property that reads its value from a labeled option,
|
||||
/// with a default value when the flag is provided without a value.
|
||||
///
|
||||
/// This initializer allows providing a `defaultAsFlag` value that is used
|
||||
/// when the flag is present but no value follows it:
|
||||
///
|
||||
/// ```swift
|
||||
/// @Option(name: .customLong("bin-path"), defaultAsFlag: "/default/path")
|
||||
/// var showBinPath: String?
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - name: A specification for what names are allowed for this option.
|
||||
/// - defaultAsFlag: The value to use when the flag is provided without a value.
|
||||
/// - parsingStrategy: The behavior to use when looking for this option's value.
|
||||
/// - help: Information about how to use this option.
|
||||
/// - completion: The type of command-line completion provided for this option.
|
||||
public init<T>(
|
||||
name: NameSpecification = .long,
|
||||
defaultAsFlag: T,
|
||||
parsing parsingStrategy: DefaultAsFlagParsingStrategy = .scanningForValue,
|
||||
help: ArgumentHelp? = nil,
|
||||
completion: CompletionKind? = nil
|
||||
) where T: ExpressibleByArgument, Value == T? {
|
||||
// Implementation matching the first initializer - hybrid flag/option behavior
|
||||
self.init(
|
||||
_parsedValue: .init { key in
|
||||
let arg = ArgumentDefinition(
|
||||
kind: .name(key: key, specification: name),
|
||||
help: .init(
|
||||
allValueStrings: T.allValueStrings,
|
||||
options: [.isOptional],
|
||||
help: help,
|
||||
defaultValue: String(describing: defaultAsFlag),
|
||||
key: key,
|
||||
isComposite: false),
|
||||
completion: completion ?? T.defaultCompletionKind,
|
||||
parsingStrategy: parsingStrategy.base,
|
||||
update: .optionalUnary(
|
||||
nullaryHandler: { (origin, name, parsedValues) in
|
||||
// Act like a flag - when present without value, use defaultAsFlag
|
||||
parsedValues.set(defaultAsFlag, forKey: key, inputOrigin: origin)
|
||||
},
|
||||
unaryHandler: { (origin, name, valueString, parsedValues) in
|
||||
// Parse the provided value
|
||||
guard let parsedValue = T(argument: valueString) else {
|
||||
throw ParserError.unableToParseValue(
|
||||
origin, name, valueString, forKey: key, originalError: nil)
|
||||
}
|
||||
parsedValues.set(parsedValue, forKey: key, inputOrigin: origin)
|
||||
}
|
||||
),
|
||||
initial: { origin, values in
|
||||
values.set(
|
||||
nil, forKey: key, inputOrigin: InputOrigin(element: .defaultValue)
|
||||
)
|
||||
})
|
||||
|
||||
return ArgumentSet(arg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - @Option Optional<T> Initializers
|
||||
@@ -631,6 +796,78 @@ extension Option {
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates an optional property that reads its value from a labeled option,
|
||||
/// parsing with the given closure, with a default value when the flag is
|
||||
/// provided without a value.
|
||||
///
|
||||
/// This initializer allows providing a `defaultAsFlag` value that is used
|
||||
/// when the flag is present but no value follows it:
|
||||
///
|
||||
/// ```swift
|
||||
/// @Option(name: .customLong("bin-path"), defaultAsFlag: "/default/path", transform: { $0.uppercased() })
|
||||
/// var showBinPath: String? = nil
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - wrappedValue: A default value to use for this property, provided
|
||||
/// implicitly by the compiler during property wrapper initialization.
|
||||
/// - name: A specification for what names are allowed for this option.
|
||||
/// - defaultAsFlag: The value to use when the flag is provided without a value.
|
||||
/// - parsingStrategy: The behavior to use when looking for this option's value.
|
||||
/// - help: Information about how to use this option.
|
||||
/// - completion: The type of command-line completion provided for this option.
|
||||
/// - transform: A closure that converts a string into this property's
|
||||
/// type, or else throws an error.
|
||||
@preconcurrency
|
||||
public init<T>(
|
||||
wrappedValue: _OptionalNilComparisonType,
|
||||
name: NameSpecification = .long,
|
||||
defaultAsFlag: T,
|
||||
parsing parsingStrategy: DefaultAsFlagParsingStrategy = .scanningForValue,
|
||||
help: ArgumentHelp? = nil,
|
||||
completion: CompletionKind? = nil,
|
||||
transform: @Sendable @escaping (String) throws -> T
|
||||
) where Value == T? {
|
||||
// Implementation with hybrid flag/option behavior and transform
|
||||
self.init(
|
||||
_parsedValue: .init { key in
|
||||
let arg = ArgumentDefinition(
|
||||
kind: .name(key: key, specification: name),
|
||||
help: .init(
|
||||
allValueStrings: [],
|
||||
options: [.isOptional],
|
||||
help: help,
|
||||
defaultValue: String(describing: defaultAsFlag),
|
||||
key: key,
|
||||
isComposite: false),
|
||||
completion: completion ?? .default,
|
||||
parsingStrategy: parsingStrategy.base,
|
||||
update: .optionalUnary(
|
||||
nullaryHandler: { (origin, name, parsedValues) in
|
||||
// Act like a flag - when present without value, use defaultAsFlag
|
||||
parsedValues.set(defaultAsFlag, forKey: key, inputOrigin: origin)
|
||||
},
|
||||
unaryHandler: { (origin, name, valueString, parsedValues) in
|
||||
// Parse the provided value using the transform
|
||||
do {
|
||||
let parsedValue = try transform(valueString)
|
||||
parsedValues.set(parsedValue, forKey: key, inputOrigin: origin)
|
||||
} catch {
|
||||
throw ParserError.unableToParseValue(
|
||||
origin, name, valueString, forKey: key, originalError: error)
|
||||
}
|
||||
}
|
||||
),
|
||||
initial: { origin, values in
|
||||
values.set(
|
||||
nil, forKey: key, inputOrigin: InputOrigin(element: .defaultValue)
|
||||
)
|
||||
})
|
||||
|
||||
return ArgumentSet(arg)
|
||||
})
|
||||
}
|
||||
|
||||
@available(
|
||||
*, deprecated,
|
||||
message: """
|
||||
@@ -706,6 +943,75 @@ extension Option {
|
||||
return ArgumentSet(arg)
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates an optional property that reads its value from a labeled option,
|
||||
/// parsing with the given closure, with a default value when the flag is
|
||||
/// provided without a value.
|
||||
///
|
||||
/// This initializer allows providing a `defaultAsFlag` value that is used
|
||||
/// when the flag is present but no value follows it:
|
||||
///
|
||||
/// ```swift
|
||||
/// @Option(name: .customLong("bin-path"), defaultAsFlag: "/default/path", transform: { $0.uppercased() })
|
||||
/// var showBinPath: String?
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - name: A specification for what names are allowed for this option.
|
||||
/// - defaultAsFlag: The value to use when the flag is provided without a value.
|
||||
/// - parsingStrategy: The behavior to use when looking for this option's value.
|
||||
/// - help: Information about how to use this option.
|
||||
/// - completion: The type of command-line completion provided for this option.
|
||||
/// - transform: A closure that converts a string into this property's
|
||||
/// type, or else throws an error.
|
||||
@preconcurrency
|
||||
public init<T>(
|
||||
name: NameSpecification = .long,
|
||||
defaultAsFlag: T,
|
||||
parsing parsingStrategy: DefaultAsFlagParsingStrategy = .scanningForValue,
|
||||
help: ArgumentHelp? = nil,
|
||||
completion: CompletionKind? = nil,
|
||||
transform: @Sendable @escaping (String) throws -> T
|
||||
) where Value == T? {
|
||||
// Implementation with hybrid flag/option behavior and transform
|
||||
self.init(
|
||||
_parsedValue: .init { key in
|
||||
let arg = ArgumentDefinition(
|
||||
kind: .name(key: key, specification: name),
|
||||
help: .init(
|
||||
allValueStrings: [],
|
||||
options: [.isOptional],
|
||||
help: help,
|
||||
defaultValue: String(describing: defaultAsFlag),
|
||||
key: key,
|
||||
isComposite: false),
|
||||
completion: completion ?? .default,
|
||||
parsingStrategy: parsingStrategy.base,
|
||||
update: .optionalUnary(
|
||||
nullaryHandler: { (origin, name, parsedValues) in
|
||||
// Act like a flag - when present without value, use defaultAsFlag
|
||||
parsedValues.set(defaultAsFlag, forKey: key, inputOrigin: origin)
|
||||
},
|
||||
unaryHandler: { (origin, name, valueString, parsedValues) in
|
||||
// Parse the provided value using the transform
|
||||
do {
|
||||
let parsedValue = try transform(valueString)
|
||||
parsedValues.set(parsedValue, forKey: key, inputOrigin: origin)
|
||||
} catch {
|
||||
throw ParserError.unableToParseValue(
|
||||
origin, name, valueString, forKey: key, originalError: error)
|
||||
}
|
||||
}
|
||||
),
|
||||
initial: { origin, values in
|
||||
values.set(
|
||||
nil, forKey: key, inputOrigin: InputOrigin(element: .defaultValue)
|
||||
)
|
||||
})
|
||||
|
||||
return ArgumentSet(arg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - @Option Array<T: ExpressibleByArgument> Initializers
|
||||
|
||||
@@ -22,6 +22,10 @@ struct ArgumentDefinition {
|
||||
|
||||
/// An argument that takes a string as its value.
|
||||
case unary(Unary)
|
||||
|
||||
/// An argument that can work as both a flag (nullary) and option (unary).
|
||||
/// When no value follows, uses nullaryHandler. When a value is available, uses unaryHandler.
|
||||
case optionalUnary(nullaryHandler: Nullary, unaryHandler: Unary)
|
||||
}
|
||||
|
||||
typealias Initial = (InputOrigin, inout ParsedValues) throws -> Void
|
||||
@@ -133,6 +137,9 @@ struct ArgumentDefinition {
|
||||
if case (.positional, .nullary) = (kind, update) {
|
||||
preconditionFailure("Can't create a nullary positional argument.")
|
||||
}
|
||||
if case (.positional, .optionalUnary) = (kind, update) {
|
||||
preconditionFailure("Can't create an optionalUnary positional argument.")
|
||||
}
|
||||
|
||||
self.kind = kind
|
||||
self.help = help
|
||||
@@ -157,6 +164,12 @@ extension ArgumentDefinition: CustomDebugStringConvertible {
|
||||
.map { $0.synopsisString }
|
||||
.joined(separator: ",")
|
||||
+ " <\(valueName)>"
|
||||
case (.named(let names), .optionalUnary):
|
||||
return
|
||||
names
|
||||
.map { $0.synopsisString }
|
||||
.joined(separator: ",")
|
||||
+ " [<\(valueName)>]" // Optional value syntax
|
||||
case (.positional, _):
|
||||
return "<\(valueName)>"
|
||||
case (.default, _):
|
||||
@@ -192,9 +205,12 @@ extension ArgumentDefinition {
|
||||
}
|
||||
|
||||
var isNullary: Bool {
|
||||
if case .nullary = update {
|
||||
switch update {
|
||||
case .nullary:
|
||||
return true
|
||||
} else {
|
||||
case .optionalUnary:
|
||||
return true // Can behave as nullary
|
||||
case .unary:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +348,19 @@ struct LenientParser {
|
||||
let origins = origin.inserting(origin2)
|
||||
try update(origins, parsed.name, value, &result)
|
||||
usedOrigins.formUnion(origins)
|
||||
} else if let (_, element) = inputArguments.peekNext(),
|
||||
!element.isTerminator,
|
||||
case .option(let nextParsed) = element.value,
|
||||
argumentSet.first(matching: nextParsed) == nil,
|
||||
nextParsed.value != nil,
|
||||
let (actualOrigin, value) = inputArguments.popNextElementAsValue(
|
||||
after: originElement)
|
||||
{
|
||||
// For default-as-flag options with scanningForValue, try consuming option-like
|
||||
// strings as values only if they are unrecognized options with explicit values (e.g., "--somearg=value")
|
||||
let origins = origin.inserting(actualOrigin)
|
||||
try update(origins, parsed.name, value, &result)
|
||||
usedOrigins.formUnion(origins)
|
||||
} else {
|
||||
throw errorForMissingValue(originElement, parsed)
|
||||
}
|
||||
@@ -366,16 +379,15 @@ struct LenientParser {
|
||||
let origins = origin.inserting(origin2)
|
||||
try update(origins, parsed.name, String(value), &result)
|
||||
usedOrigins.formUnion(origins)
|
||||
} else {
|
||||
guard
|
||||
let (origin2, value) = inputArguments.popNextElementAsValue(
|
||||
after: originElement)
|
||||
else {
|
||||
throw errorForMissingValue(originElement, parsed)
|
||||
}
|
||||
} else if let (origin2, value) = inputArguments.popNextElementAsValue(
|
||||
after: originElement)
|
||||
{
|
||||
// Only consume if there's no terminator between option and value
|
||||
let origins = origin.inserting(origin2)
|
||||
try update(origins, parsed.name, value, &result)
|
||||
usedOrigins.formUnion(origins)
|
||||
} else {
|
||||
throw errorForMissingValue(originElement, parsed)
|
||||
}
|
||||
|
||||
case .allRemainingInput:
|
||||
@@ -459,6 +471,61 @@ struct LenientParser {
|
||||
}
|
||||
}
|
||||
|
||||
mutating func parseOptionalUnaryValue(
|
||||
_ argument: ArgumentDefinition,
|
||||
_ parsed: ParsedArgument,
|
||||
_ originElement: InputOrigin.Element,
|
||||
_ nullaryHandler: ArgumentDefinition.Update.Nullary,
|
||||
_ unaryHandler: ArgumentDefinition.Update.Unary,
|
||||
_ result: inout ParsedValues,
|
||||
_ usedOrigins: inout InputOrigin
|
||||
) throws {
|
||||
// For default-as-flag options with .scanningForValue, check if the next element
|
||||
// is a recognized option and fall back to flag behavior if so
|
||||
if case .scanningForValue = argument.parsingStrategy,
|
||||
let (_, element) = inputArguments.peekNext(),
|
||||
!element.isTerminator,
|
||||
case .option(let nextParsed) = element.value,
|
||||
argumentSet.first(matching: nextParsed) != nil
|
||||
{
|
||||
// Fall back to flag behavior when the next element is a recognized option
|
||||
let origin = InputOrigin(elements: [originElement])
|
||||
try nullaryHandler(origin, parsed.name, &result)
|
||||
usedOrigins.formUnion(origin)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
// Try to parse as a unary value first using the main parseValue logic
|
||||
try parseValue(
|
||||
argument,
|
||||
parsed,
|
||||
originElement,
|
||||
unaryHandler,
|
||||
&result,
|
||||
&usedOrigins
|
||||
)
|
||||
} catch let error as ParserError {
|
||||
switch error {
|
||||
case .missingValueForOption, .missingValueOrUnknownCompositeOption:
|
||||
// Fall back to flag behavior when no value is available
|
||||
let origin = InputOrigin(elements: [originElement])
|
||||
try nullaryHandler(origin, parsed.name, &result)
|
||||
usedOrigins.formUnion(origin)
|
||||
case .unknownOption, .unableToParseValue:
|
||||
// Fall back to flag behavior when parseValue fails to find a suitable value
|
||||
// This handles cases where potential values like "--somearg=value" are rejected
|
||||
// because they look like unknown options, or when transform functions fail
|
||||
let origin: InputOrigin = InputOrigin(elements: [originElement])
|
||||
try nullaryHandler(origin, parsed.name, &result)
|
||||
usedOrigins.formUnion(origin)
|
||||
default:
|
||||
// Re-throw other parser errors
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutating func parsePositionalValues(
|
||||
from unusedInput: SplitArguments,
|
||||
into result: inout ParsedValues
|
||||
@@ -629,7 +696,7 @@ struct LenientParser {
|
||||
|
||||
switch argument.update {
|
||||
case .nullary(let update):
|
||||
// We don’t expect a value for this option.
|
||||
// We don't expect a value for this option.
|
||||
if let value = parsed.value {
|
||||
throw ParserError.unexpectedValueForOption(
|
||||
origin, parsed.name, value)
|
||||
@@ -639,6 +706,12 @@ struct LenientParser {
|
||||
case .unary(let update):
|
||||
try parseValue(
|
||||
argument, parsed, origin, update, &result, &usedOrigins)
|
||||
case .optionalUnary(let nullaryHandler, let unaryHandler):
|
||||
// Hybrid behavior: try to find a value, fall back to flag behavior
|
||||
// For default-as-flag options, we need special handling in scanningForValue
|
||||
try parseOptionalUnaryValue(
|
||||
argument, parsed, origin, nullaryHandler, unaryHandler,
|
||||
&result, &usedOrigins)
|
||||
}
|
||||
case .terminator:
|
||||
// Ignore the terminator, it might get picked up as a positional value later.
|
||||
|
||||
@@ -336,6 +336,7 @@ extension SplitArguments {
|
||||
/// value for `-f`, or `--foo name` where `name` is the value for `--foo`.
|
||||
/// If `--foo` expects a value, an input of `--foo --bar name` will return
|
||||
/// `nil`, since the option `--bar` comes before the value `name`.
|
||||
/// Also returns `nil` if there's a terminator between the origin and the target value.
|
||||
mutating func popNextElementIfValue(after origin: InputOrigin.Element) -> (
|
||||
InputOrigin.Element, String
|
||||
)? {
|
||||
@@ -353,14 +354,41 @@ extension SplitArguments {
|
||||
guard case .value(let value) = elements[elementIndex].value
|
||||
else { return nil }
|
||||
|
||||
defer { remove(at: elementIndex) }
|
||||
let matchedArgumentIndex = elements[elementIndex].index
|
||||
return (.argumentIndex(matchedArgumentIndex), value)
|
||||
let targetOrigin = InputOrigin.Element.argumentIndex(matchedArgumentIndex)
|
||||
|
||||
// Check if there's a terminator between the origin and target
|
||||
if hasTerminatorBetween(origin, targetOrigin) {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer { remove(at: elementIndex) }
|
||||
return (targetOrigin, value)
|
||||
}
|
||||
|
||||
/// Helper function to check if there's a terminator between two origins.
|
||||
private func hasTerminatorBetween(
|
||||
_ originElement: InputOrigin.Element,
|
||||
_ targetOrigin: InputOrigin.Element
|
||||
) -> Bool {
|
||||
guard case .argumentIndex(let currentIndex) = originElement,
|
||||
case .argumentIndex(let targetIndex) = targetOrigin
|
||||
else { return false }
|
||||
|
||||
// Check if there's a terminator between current position and target position
|
||||
let terminatorIndex = elements.firstIndex { element in
|
||||
element.isTerminator
|
||||
&& element.index.inputIndex > currentIndex.inputIndex
|
||||
&& element.index.inputIndex < targetIndex.inputIndex
|
||||
}
|
||||
|
||||
return terminatorIndex != nil
|
||||
}
|
||||
|
||||
/// Pops the next `.value` after the given index.
|
||||
///
|
||||
/// This is used to get the next value in `-f -b name` where `name` is the value of `-f`.
|
||||
/// Also returns `nil` if there's a terminator between the origin and the target value.
|
||||
mutating func popNextValue(
|
||||
after origin: InputOrigin.Element
|
||||
) -> (InputOrigin.Element, String)? {
|
||||
@@ -368,11 +396,19 @@ extension SplitArguments {
|
||||
guard let resultIndex = elements[start...].firstIndex(where: { $0.isValue })
|
||||
else { return nil }
|
||||
|
||||
let targetOrigin = InputOrigin.Element.argumentIndex(
|
||||
elements[resultIndex].index)
|
||||
|
||||
// Check if there's a terminator between the origin and target
|
||||
if hasTerminatorBetween(origin, targetOrigin) {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer { remove(at: resultIndex) }
|
||||
// swift-format-ignore: NeverForceUnwrap
|
||||
// This is safe because we know `resultIndex` is refers to a value
|
||||
return (
|
||||
.argumentIndex(elements[resultIndex].index),
|
||||
targetOrigin,
|
||||
elements[resultIndex].value.valueString!
|
||||
)
|
||||
}
|
||||
@@ -384,6 +420,7 @@ extension SplitArguments {
|
||||
///
|
||||
/// For an input such as `--a --b foo`, if passed the origin of `--a`,
|
||||
/// this will first pop the value `--b`, then the value `foo`.
|
||||
/// Also returns `nil` if there's a terminator between the origin and the target element.
|
||||
mutating func popNextElementAsValue(after origin: InputOrigin.Element) -> (
|
||||
InputOrigin.Element, String
|
||||
)? {
|
||||
@@ -395,11 +432,19 @@ extension SplitArguments {
|
||||
$0.index.subIndex == .complete
|
||||
})?.index
|
||||
else { return nil }
|
||||
|
||||
let targetOrigin = InputOrigin.Element.argumentIndex(nextIndex)
|
||||
|
||||
// Check if there's a terminator between the origin and target
|
||||
if hasTerminatorBetween(origin, targetOrigin) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove all elements with this `InputIndex`:
|
||||
remove(at: nextIndex)
|
||||
// Return the original input
|
||||
return (
|
||||
.argumentIndex(nextIndex), originalInput[nextIndex.inputIndex.rawValue]
|
||||
targetOrigin, originalInput[nextIndex.inputIndex.rawValue]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,8 @@ extension ArgumentInfoV0.KindV0 {
|
||||
self = .flag
|
||||
case .unary:
|
||||
self = .option
|
||||
case .optionalUnary:
|
||||
self = .option
|
||||
}
|
||||
case .positional:
|
||||
self = .positional
|
||||
|
||||
@@ -232,10 +232,22 @@ internal struct HelpGenerator {
|
||||
allAndDefaultValues =
|
||||
"(values: \(allValueStrings.joined(separator: ", ")))"
|
||||
case (false, true):
|
||||
allAndDefaultValues = "(default: \(defaultValue))"
|
||||
switch arg.update {
|
||||
case .nullary, .unary:
|
||||
allAndDefaultValues = "(default: \(defaultValue))"
|
||||
case .optionalUnary:
|
||||
allAndDefaultValues = "(default as flag: \(defaultValue))"
|
||||
}
|
||||
|
||||
case (true, true):
|
||||
allAndDefaultValues =
|
||||
"(values: \(allValueStrings.joined(separator: ", ")); default: \(defaultValue))"
|
||||
switch arg.update {
|
||||
case .nullary, .unary:
|
||||
allAndDefaultValues =
|
||||
"(values: \(allValueStrings.joined(separator: ", ")); default: \(defaultValue))"
|
||||
case .optionalUnary:
|
||||
allAndDefaultValues =
|
||||
"(values: \(allValueStrings.joined(separator: ", ")); default as flag: \(defaultValue))"
|
||||
}
|
||||
}
|
||||
|
||||
if arg.help.isComposite {
|
||||
|
||||
@@ -86,6 +86,8 @@ extension ArgumentDefinition {
|
||||
return "\(joinedSynopsisString) <\(valueName)>"
|
||||
case .nullary:
|
||||
return joinedSynopsisString
|
||||
case .optionalUnary:
|
||||
return "\(joinedSynopsisString) [<\(valueName)>]"
|
||||
}
|
||||
case .positional:
|
||||
return "<\(valueName)>"
|
||||
@@ -106,6 +108,8 @@ extension ArgumentDefinition {
|
||||
return "\(name.synopsisString) <\(valueName)>"
|
||||
case .nullary:
|
||||
return name.synopsisString
|
||||
case .optionalUnary:
|
||||
return "\(name.synopsisString) [<\(valueName)>]"
|
||||
}
|
||||
case .positional:
|
||||
return "<\(valueName)>"
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2025 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 ArgumentParser
|
||||
import ArgumentParserTestHelpers
|
||||
import XCTest
|
||||
|
||||
final class DefaultAsFlagEndToEndTests: XCTestCase {}
|
||||
|
||||
// MARK: - Test Cases
|
||||
|
||||
extension DefaultAsFlagEndToEndTests {
|
||||
|
||||
// Test struct for defaultAsFlag without transform - explicit nil
|
||||
private struct CommandWithDefaultAsFlagWithoutTransformExplicitNil:
|
||||
ParsableCommand
|
||||
{
|
||||
@Option(name: .customLong("bin-path"), defaultAsFlag: "/default/path")
|
||||
var showBinPath: String? = nil
|
||||
}
|
||||
|
||||
// Test struct for defaultAsFlag without transform - no explicit default
|
||||
private struct CommandWithDefaultAsFlagWithoutTransformNoExplicitNil:
|
||||
ParsableCommand
|
||||
{
|
||||
@Option(name: .customLong("bin-path"), defaultAsFlag: "/default/path")
|
||||
var showBinPath: String?
|
||||
}
|
||||
|
||||
// Test struct for defaultAsFlag with transform - explicit nil
|
||||
private struct CommandWithDefaultAsFlagWithTransformExplicitNil:
|
||||
ParsableCommand
|
||||
{
|
||||
@Option(
|
||||
name: .customLong("bin-path"), defaultAsFlag: "/default/path",
|
||||
transform: { $0.uppercased() })
|
||||
var showBinPath: String? = nil
|
||||
}
|
||||
|
||||
// Test struct for defaultAsFlag with transform - no explicit default
|
||||
private struct CommandWithDefaultAsFlagWithTransformNoExplicitNil:
|
||||
ParsableCommand
|
||||
{
|
||||
@Option(
|
||||
name: .customLong("bin-path"), defaultAsFlag: "/default/path",
|
||||
transform: { $0.uppercased() })
|
||||
var showBinPath: String?
|
||||
}
|
||||
|
||||
func testDefaultAsFlagWithExplicitNil() throws {
|
||||
// When no argument is provided, should be nil
|
||||
AssertParse(CommandWithDefaultAsFlagWithoutTransformExplicitNil.self, []) {
|
||||
cmd in
|
||||
XCTAssertNil(cmd.showBinPath)
|
||||
}
|
||||
|
||||
// When flag is provided without value, should use defaultAsFlag
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagWithoutTransformExplicitNil.self, ["--bin-path"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.showBinPath, "/default/path")
|
||||
}
|
||||
|
||||
// When flag is provided with value, should use provided value
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagWithoutTransformExplicitNil.self,
|
||||
["--bin-path", "/custom/path"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.showBinPath, "/custom/path")
|
||||
}
|
||||
}
|
||||
|
||||
func testDefaultAsFlagWithoutExplicitDefault() throws {
|
||||
// When no argument is provided, should be nil
|
||||
AssertParse(CommandWithDefaultAsFlagWithoutTransformNoExplicitNil.self, [])
|
||||
{ cmd in
|
||||
XCTAssertNil(cmd.showBinPath)
|
||||
}
|
||||
|
||||
// When flag is provided without value, should use defaultAsFlag
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagWithoutTransformNoExplicitNil.self, ["--bin-path"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.showBinPath, "/default/path")
|
||||
}
|
||||
|
||||
// When flag is provided with value, should use provided value
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagWithoutTransformNoExplicitNil.self,
|
||||
["--bin-path", "/custom/path"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.showBinPath, "/custom/path")
|
||||
}
|
||||
}
|
||||
|
||||
func testDefaultAsFlagWithTransformWithExplicitNil() throws {
|
||||
// When no argument is provided, should be nil
|
||||
AssertParse(CommandWithDefaultAsFlagWithTransformExplicitNil.self, []) {
|
||||
cmd in
|
||||
XCTAssertNil(cmd.showBinPath)
|
||||
}
|
||||
|
||||
// When flag is provided without value, should use defaultAsFlag
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagWithTransformExplicitNil.self, ["--bin-path"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.showBinPath, "/default/path")
|
||||
}
|
||||
|
||||
// When flag is provided with value, should use provided value with transform
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagWithTransformExplicitNil.self,
|
||||
["--bin-path", "/custom/path"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.showBinPath, "/CUSTOM/PATH")
|
||||
}
|
||||
}
|
||||
|
||||
func testDefaultAsFlagWithTransformWithoutExplicitDefault() throws {
|
||||
// When no argument is provided, should be nil
|
||||
AssertParse(CommandWithDefaultAsFlagWithTransformNoExplicitNil.self, []) {
|
||||
cmd in
|
||||
XCTAssertNil(cmd.showBinPath)
|
||||
}
|
||||
|
||||
// When flag is provided without value, should use defaultAsFlag
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagWithTransformNoExplicitNil.self, ["--bin-path"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.showBinPath, "/default/path")
|
||||
}
|
||||
|
||||
// When flag is provided with value, should use provided value with transform
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagWithTransformNoExplicitNil.self,
|
||||
["--bin-path", "/custom/path"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.showBinPath, "/CUSTOM/PATH")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Tests for -- terminator behavior
|
||||
|
||||
private struct CommandWithDefaultAsFlagAndArguments: ParsableCommand {
|
||||
@Option(defaultAsFlag: "default")
|
||||
var option: String?
|
||||
|
||||
@Argument
|
||||
var files: [String] = []
|
||||
}
|
||||
|
||||
func testDefaultAsFlagWithTerminatorFlagBeforeTerminator() throws {
|
||||
// --option -- value
|
||||
// Should use defaultAsFlag value, "value" becomes positional argument
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagAndArguments.self, ["--option", "--", "value"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.option, "default")
|
||||
XCTAssertEqual(cmd.files, ["value"])
|
||||
}
|
||||
}
|
||||
|
||||
func testDefaultAsFlagWithTerminatorValueBeforeTerminator() throws {
|
||||
// --option custom -- other
|
||||
// Should use "custom" as option value, "other" becomes positional argument
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagAndArguments.self,
|
||||
["--option", "custom", "--", "other"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.option, "custom")
|
||||
XCTAssertEqual(cmd.files, ["other"])
|
||||
}
|
||||
}
|
||||
|
||||
func implTestDefaultAsFlagWithTerminatorValueBeforeTerminator(
|
||||
optionValue: String,
|
||||
file: StaticString = #file,
|
||||
line: UInt = #line
|
||||
) throws {
|
||||
// --option custom -- other
|
||||
// Should use "custom" as option value, "other" becomes positional argument
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagAndArguments.self,
|
||||
["--option", optionValue, "--", "other"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.option, optionValue, file: file, line: line)
|
||||
XCTAssertEqual(cmd.files, ["other"], file: file, line: line)
|
||||
}
|
||||
}
|
||||
func testDefaultAsFlagWithValueWithTerminatorValueBeforeTerminator() throws {
|
||||
try implTestDefaultAsFlagWithTerminatorValueBeforeTerminator(
|
||||
optionValue: "custom")
|
||||
}
|
||||
|
||||
func
|
||||
testDefaultAsFlagWithCompleteValueAndWithTerminatorValueBeforeTerminator()
|
||||
throws
|
||||
{
|
||||
try implTestDefaultAsFlagWithTerminatorValueBeforeTerminator(
|
||||
optionValue: "--somearg=value")
|
||||
}
|
||||
|
||||
func
|
||||
testDefaultAsFlagWithCompleteValueAndWithTerminatorValueInQuotesBeforeTerminator()
|
||||
throws
|
||||
{
|
||||
try implTestDefaultAsFlagWithTerminatorValueBeforeTerminator(
|
||||
optionValue: "--somearg=\"value\"")
|
||||
}
|
||||
|
||||
func testDefaultAsFlagWithTerminatorOptionAfterTerminator() throws {
|
||||
// -- --option
|
||||
// Should treat "--option" as positional argument, option should be nil
|
||||
AssertParse(CommandWithDefaultAsFlagAndArguments.self, ["--", "--option"]) {
|
||||
cmd in
|
||||
XCTAssertNil(cmd.option)
|
||||
XCTAssertEqual(cmd.files, ["--option"])
|
||||
}
|
||||
}
|
||||
|
||||
func testDefaultAsFlagWithTerminatorComplexScenario() throws {
|
||||
// --option -- --another-option value
|
||||
// Should use defaultAsFlag, everything after -- is positional
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagAndArguments.self,
|
||||
["--option", "--", "--another-option", "value"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.option, "default")
|
||||
XCTAssertEqual(cmd.files, ["--another-option", "value"])
|
||||
}
|
||||
}
|
||||
|
||||
func testDefaultAsFlagWithTerminatorValueAfterTerminatorNotConsumed() throws {
|
||||
// --option -- value1 value2
|
||||
// Should use defaultAsFlag, both values become positional arguments
|
||||
AssertParse(
|
||||
CommandWithDefaultAsFlagAndArguments.self,
|
||||
["--option", "--", "value1", "value2"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.option, "default")
|
||||
XCTAssertEqual(cmd.files, ["value1", "value2"])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Tests for parsing strategy compilation restrictions
|
||||
|
||||
func testDefaultAsFlagCompilationRestrictionsWork() throws {
|
||||
// This test verifies that DefaultAsFlagParsingStrategy only allows compatible strategies
|
||||
// and prevents .unconditional at compile time
|
||||
|
||||
struct CommandWithAllowedStrategies: ParsableCommand {
|
||||
// These should compile successfully
|
||||
@Option(defaultAsFlag: "next", parsing: .next)
|
||||
var nextStrategy: String?
|
||||
|
||||
@Option(defaultAsFlag: "scanning", parsing: .scanningForValue)
|
||||
var scanningStrategy: String?
|
||||
}
|
||||
|
||||
// Verify that the allowed strategies work correctly
|
||||
AssertParse(CommandWithAllowedStrategies.self, ["--next-strategy"]) { cmd in
|
||||
XCTAssertEqual(cmd.nextStrategy, "next")
|
||||
XCTAssertNil(cmd.scanningStrategy)
|
||||
}
|
||||
|
||||
AssertParse(CommandWithAllowedStrategies.self, ["--scanning-strategy"]) {
|
||||
cmd in
|
||||
XCTAssertNil(cmd.nextStrategy)
|
||||
XCTAssertEqual(cmd.scanningStrategy, "scanning")
|
||||
}
|
||||
|
||||
AssertParse(
|
||||
CommandWithAllowedStrategies.self, ["--next-strategy", "custom"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.nextStrategy, "custom")
|
||||
XCTAssertNil(cmd.scanningStrategy)
|
||||
}
|
||||
|
||||
AssertParse(
|
||||
CommandWithAllowedStrategies.self,
|
||||
["--scanning-strategy", "--next-strategy", "next-custom"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.nextStrategy, "next-custom")
|
||||
XCTAssertEqual(cmd.scanningStrategy, "scanning")
|
||||
}
|
||||
|
||||
AssertParse(
|
||||
CommandWithAllowedStrategies.self,
|
||||
["--next-strategy", "next-custom", "--scanning-strategy"]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.nextStrategy, "next-custom")
|
||||
XCTAssertEqual(cmd.scanningStrategy, "scanning")
|
||||
}
|
||||
|
||||
AssertParse(
|
||||
CommandWithAllowedStrategies.self,
|
||||
[
|
||||
"--scanning-strategy", "scanning-custom", "--next-strategy",
|
||||
"next-custom",
|
||||
]
|
||||
) { cmd in
|
||||
XCTAssertEqual(cmd.nextStrategy, "next-custom")
|
||||
XCTAssertEqual(cmd.scanningStrategy, "scanning-custom")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -56,4 +56,13 @@ final class GenerateManualTests: XCTestCase {
|
||||
func testRollMultiPageManual() throws {
|
||||
try assertGenerateManual(multiPage: true, command: "roll")
|
||||
}
|
||||
|
||||
func testDefaultAsFlagSinglePageManual() throws {
|
||||
try assertGenerateManual(multiPage: false, command: "default-as-flag")
|
||||
}
|
||||
|
||||
func testDefaultAsFlagMultiPageManual() throws {
|
||||
try assertGenerateManual(multiPage: true, command: "default-as-flag")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt DEFAULT-AS-FLAG 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm default-as-flag
|
||||
.Nd "A utility demonstrating defaultAsFlag options."
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Ar subcommand
|
||||
.Op Fl -string-flag Ar string-flag
|
||||
.Op Fl -number-flag Ar number-flag
|
||||
.Op Fl -bool-flag Ar bool-flag
|
||||
.Op Fl -transform-flag Ar transform-flag
|
||||
.Op Fl -regular Ar regular
|
||||
.Op Ar additional-args...
|
||||
.Op Fl -help
|
||||
.Sh DESCRIPTION
|
||||
This command shows how defaultAsFlag options can work both as flags
|
||||
and as options with values.
|
||||
.Bl -tag -width 6n
|
||||
.It Fl -string-flag Ar string-flag
|
||||
A string option with defaultAsFlag.
|
||||
.It Fl -number-flag Ar number-flag
|
||||
An integer option with defaultAsFlag.
|
||||
.It Fl -bool-flag Ar bool-flag
|
||||
A boolean option with defaultAsFlag.
|
||||
.It Fl -transform-flag Ar transform-flag
|
||||
A string option with transform and defaultAsFlag.
|
||||
.It Fl r , -regular Ar regular
|
||||
A regular option for comparison.
|
||||
.It Ar additional-args...
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.El
|
||||
.Sh "SEE ALSO"
|
||||
.Xr default-as-flag.help 9
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
and
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt DEFAULT-AS-FLAG.HELP 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm "default-as-flag help"
|
||||
.Nd "Show subcommand help information."
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Ar subcommands...
|
||||
.Sh DESCRIPTION
|
||||
.Bl -tag -width 6n
|
||||
.It Ar subcommands...
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
and
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
.\" "Generated by swift-argument-parser"
|
||||
.Dd May 12, 1996
|
||||
.Dt DEFAULT-AS-FLAG 9
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm default-as-flag
|
||||
.Nd "A utility demonstrating defaultAsFlag options."
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Ar subcommand
|
||||
.Op Fl -string-flag Ar string-flag
|
||||
.Op Fl -number-flag Ar number-flag
|
||||
.Op Fl -bool-flag Ar bool-flag
|
||||
.Op Fl -transform-flag Ar transform-flag
|
||||
.Op Fl -regular Ar regular
|
||||
.Op Ar additional-args...
|
||||
.Op Fl -help
|
||||
.Sh DESCRIPTION
|
||||
This command shows how defaultAsFlag options can work both as flags
|
||||
and as options with values.
|
||||
.Bl -tag -width 6n
|
||||
.It Fl -string-flag Ar string-flag
|
||||
A string option with defaultAsFlag.
|
||||
.It Fl -number-flag Ar number-flag
|
||||
An integer option with defaultAsFlag.
|
||||
.It Fl -bool-flag Ar bool-flag
|
||||
A boolean option with defaultAsFlag.
|
||||
.It Fl -transform-flag Ar transform-flag
|
||||
A string option with transform and defaultAsFlag.
|
||||
.It Fl r , -regular Ar regular
|
||||
A regular option for comparison.
|
||||
.It Ar additional-args...
|
||||
.It Fl h , -help
|
||||
Show help information.
|
||||
.It Em help
|
||||
Show subcommand help information.
|
||||
.Bl -tag -width 6n
|
||||
.It Ar subcommands...
|
||||
.El
|
||||
.El
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
reference was written by
|
||||
.An -nosplit
|
||||
.An "Jane Appleseed" ,
|
||||
.Mt johnappleseed@apple.com ,
|
||||
and
|
||||
.An -nosplit
|
||||
.An "The Appleseeds"
|
||||
.Ao
|
||||
.Mt appleseeds@apple.com
|
||||
.Ac .
|
||||
@@ -0,0 +1,73 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2025 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 ArgumentParserTestHelpers
|
||||
import XCTest
|
||||
|
||||
@testable import ArgumentParser
|
||||
|
||||
final class DefaultAsFlagCompletionTests: XCTestCase {
|
||||
func testDefaultAsFlagCompletion_Bash() throws {
|
||||
let script = try CompletionsGenerator(
|
||||
command: DefaultAsFlagCommand.self, shell: .bash
|
||||
)
|
||||
.generateCompletionScript()
|
||||
try assertSnapshot(actual: script, extension: "bash")
|
||||
}
|
||||
|
||||
func testDefaultAsFlagCompletion_Zsh() throws {
|
||||
let script = try CompletionsGenerator(
|
||||
command: DefaultAsFlagCommand.self, shell: .zsh
|
||||
)
|
||||
.generateCompletionScript()
|
||||
try assertSnapshot(actual: script, extension: "zsh")
|
||||
}
|
||||
|
||||
func testDefaultAsFlagCompletion_Fish() throws {
|
||||
let script = try CompletionsGenerator(
|
||||
command: DefaultAsFlagCommand.self, shell: .fish
|
||||
)
|
||||
.generateCompletionScript()
|
||||
try assertSnapshot(actual: script, extension: "fish")
|
||||
}
|
||||
}
|
||||
|
||||
extension DefaultAsFlagCompletionTests {
|
||||
struct DefaultAsFlagCommand: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
commandName: "defaultasflag-test",
|
||||
abstract:
|
||||
"A command with defaultAsFlag options for testing completion scripts."
|
||||
)
|
||||
|
||||
@Option(defaultAsFlag: "/usr/bin", completion: .directory)
|
||||
var binPath: String? = nil
|
||||
|
||||
@Option(defaultAsFlag: 42)
|
||||
var count: Int?
|
||||
|
||||
@Option(defaultAsFlag: true)
|
||||
var verbose: Bool?
|
||||
|
||||
@Option(
|
||||
defaultAsFlag: "INFO",
|
||||
completion: .list(["DEBUG", "INFO", "WARN", "ERROR"]),
|
||||
transform: { $0.uppercased() }
|
||||
)
|
||||
var logLevel: String?
|
||||
|
||||
@Flag
|
||||
var help: Bool = false
|
||||
|
||||
@Argument(completion: .file())
|
||||
var input: String
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2025 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 ArgumentParserTestHelpers
|
||||
import XCTest
|
||||
|
||||
@testable import ArgumentParser
|
||||
|
||||
final class DefaultAsFlagDumpHelpTests: XCTestCase {
|
||||
func testDefaultAsFlagDumpHelp() throws {
|
||||
try assertDumpHelp(type: DefaultAsFlagCommand.self)
|
||||
}
|
||||
|
||||
func testDefaultAsFlagWithTransformDumpHelp() throws {
|
||||
try assertDumpHelp(type: DefaultAsFlagWithTransformCommand.self)
|
||||
}
|
||||
}
|
||||
|
||||
extension DefaultAsFlagDumpHelpTests {
|
||||
struct DefaultAsFlagCommand: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
abstract: "A command with defaultAsFlag options for testing dump help."
|
||||
)
|
||||
|
||||
@Option(name: .customLong("binary-path"), defaultAsFlag: "/usr/bin")
|
||||
var binPath: String? = nil
|
||||
|
||||
@Option(name: .long, defaultAsFlag: 42)
|
||||
var count: Int?
|
||||
|
||||
@Option(name: .long, defaultAsFlag: true)
|
||||
var verbose: Bool?
|
||||
|
||||
@Argument
|
||||
var input: String
|
||||
}
|
||||
|
||||
struct DefaultAsFlagWithTransformCommand: ParsableCommand {
|
||||
static let configuration = CommandConfiguration(
|
||||
abstract:
|
||||
"A command with defaultAsFlag options using transforms for testing dump help."
|
||||
)
|
||||
|
||||
@Option(
|
||||
name: .customLong("output-dir"),
|
||||
defaultAsFlag: "/default/output",
|
||||
transform: { $0.uppercased() }
|
||||
)
|
||||
var outputDir: String? = nil
|
||||
|
||||
@Option(
|
||||
name: .long,
|
||||
defaultAsFlag: "INFO",
|
||||
transform: { $0.lowercased() }
|
||||
)
|
||||
var level: String?
|
||||
|
||||
@Flag
|
||||
var debug: Bool = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This source file is part of the Swift Argument Parser open source project
|
||||
//
|
||||
// Copyright (c) 2025 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 ArgumentParserTestHelpers
|
||||
import XCTest
|
||||
|
||||
@testable import ArgumentParser
|
||||
|
||||
extension HelpGenerationTests {
|
||||
|
||||
struct BasicDefaultAsFlag: ParsableArguments {
|
||||
@Option(
|
||||
defaultAsFlag: "default", help: "A string option with defaultAsFlag.")
|
||||
var stringFlag: String?
|
||||
|
||||
@Option(defaultAsFlag: 42, help: "An integer option with defaultAsFlag.")
|
||||
var numberFlag: Int?
|
||||
|
||||
@Option(defaultAsFlag: true, help: "A boolean option with defaultAsFlag.")
|
||||
var boolFlag: Bool?
|
||||
|
||||
@Option(
|
||||
defaultAsFlag: "transformed",
|
||||
help: "A string option with defaultAsFlag and transform.")
|
||||
var transformFlag: String?
|
||||
|
||||
@Option(name: .shortAndLong, help: "A regular option for comparison.")
|
||||
var regular: String?
|
||||
}
|
||||
|
||||
func testDefaultAsFlagHelpOutput() {
|
||||
AssertHelp(
|
||||
.default, for: BasicDefaultAsFlag.self,
|
||||
equals: """
|
||||
USAGE: basic_default_as_flag [--string-flag [<string-flag>]] [--number-flag [<number-flag>]] [--bool-flag [<bool-flag>]] [--transform-flag [<transform-flag>]] [--regular <regular>]
|
||||
|
||||
OPTIONS:
|
||||
--string-flag [<string-flag>]
|
||||
A string option with defaultAsFlag. (default as flag:
|
||||
default)
|
||||
--number-flag [<number-flag>]
|
||||
An integer option with defaultAsFlag. (default as
|
||||
flag: 42)
|
||||
--bool-flag [<bool-flag>]
|
||||
A boolean option with defaultAsFlag. (default as
|
||||
flag: true)
|
||||
--transform-flag [<transform-flag>]
|
||||
A string option with defaultAsFlag and transform.
|
||||
(default as flag: transformed)
|
||||
-r, --regular <regular> A regular option for comparison.
|
||||
-h, --help Show help information.
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
struct DefaultAsFlagWithShortNames: ParsableArguments {
|
||||
@Option(
|
||||
name: .shortAndLong, defaultAsFlag: "short", help: "Short and long names."
|
||||
)
|
||||
var shortAndLong: String?
|
||||
|
||||
@Option(
|
||||
name: [.customShort("o")], defaultAsFlag: "s",
|
||||
help: "Different short name.")
|
||||
var shortOnly: String?
|
||||
}
|
||||
|
||||
func testDefaultAsFlagWithShortNames() {
|
||||
AssertHelp(
|
||||
.default, for: DefaultAsFlagWithShortNames.self,
|
||||
equals: """
|
||||
USAGE: default_as_flag_with_short_names [--short-and-long [<short-and-long>]] [-o [<o>]]
|
||||
|
||||
OPTIONS:
|
||||
-s, --short-and-long [<short-and-long>]
|
||||
Short and long names. (default as flag: short)
|
||||
-o [<o>] Different short name. (default as flag: s)
|
||||
-h, --help Show help information.
|
||||
|
||||
""")
|
||||
}
|
||||
|
||||
struct MixedOptionTypes: ParsableArguments {
|
||||
@Flag(help: "A regular flag.")
|
||||
var flag: Bool = false
|
||||
|
||||
@Option(defaultAsFlag: "mixed", help: "A defaultAsFlag option.")
|
||||
var defaultAsFlag: String?
|
||||
|
||||
@Option(help: "A regular option.")
|
||||
var regular: String?
|
||||
|
||||
@Argument(help: "A positional argument.")
|
||||
var positional: String?
|
||||
}
|
||||
|
||||
func testMixedOptionTypes() {
|
||||
AssertHelp(
|
||||
.default, for: MixedOptionTypes.self,
|
||||
equals: """
|
||||
USAGE: mixed_option_types [--flag] [--default-as-flag [<default-as-flag>]] [--regular <regular>] [<positional>]
|
||||
|
||||
ARGUMENTS:
|
||||
<positional> A positional argument.
|
||||
|
||||
OPTIONS:
|
||||
--flag A regular flag.
|
||||
--default-as-flag [<default-as-flag>]
|
||||
A defaultAsFlag option. (default as flag: mixed)
|
||||
--regular <regular> A regular option.
|
||||
-h, --help Show help information.
|
||||
|
||||
""")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
#!/bin/bash
|
||||
|
||||
__defaultasflag-test_cursor_index_in_current_word() {
|
||||
local remaining="${COMP_LINE}"
|
||||
|
||||
local word
|
||||
for word in "${COMP_WORDS[@]::COMP_CWORD}"; do
|
||||
remaining="${remaining##*([[:space:]])"${word}"*([[:space:]])}"
|
||||
done
|
||||
|
||||
local -ir index="$((COMP_POINT - ${#COMP_LINE} + ${#remaining}))"
|
||||
if [[ "${index}" -le 0 ]]; then
|
||||
printf 0
|
||||
else
|
||||
printf %s "${index}"
|
||||
fi
|
||||
}
|
||||
|
||||
# positional arguments:
|
||||
#
|
||||
# - 1: the current (sub)command's count of positional arguments
|
||||
#
|
||||
# required variables:
|
||||
#
|
||||
# - repeating_flags: the repeating flags that the current (sub)command can accept
|
||||
# - non_repeating_flags: the non-repeating flags that the current (sub)command can accept
|
||||
# - repeating_options: the repeating options that the current (sub)command can accept
|
||||
# - non_repeating_options: the non-repeating options that the current (sub)command can accept
|
||||
# - positional_number: value ignored
|
||||
# - unparsed_words: unparsed words from the current command line
|
||||
#
|
||||
# modified variables:
|
||||
#
|
||||
# - non_repeating_flags: remove flags for this (sub)command that are already on the command line
|
||||
# - non_repeating_options: remove options for this (sub)command that are already on the command line
|
||||
# - positional_number: set to the current positional number
|
||||
# - unparsed_words: remove all flags, options, and option values for this (sub)command
|
||||
__defaultasflag-test_offer_flags_options() {
|
||||
local -ir positional_count="${1}"
|
||||
positional_number=0
|
||||
|
||||
local was_flag_option_terminator_seen=false
|
||||
local is_parsing_option_value=false
|
||||
|
||||
local -ar unparsed_word_indices=("${!unparsed_words[@]}")
|
||||
local -i word_index
|
||||
for word_index in "${unparsed_word_indices[@]}"; do
|
||||
if "${is_parsing_option_value}"; then
|
||||
# This word is an option value:
|
||||
# Reset marker for next word iff not currently the last word
|
||||
[[ "${word_index}" -ne "${unparsed_word_indices[${#unparsed_word_indices[@]} - 1]}" ]] && is_parsing_option_value=false
|
||||
unset "unparsed_words[${word_index}]"
|
||||
# Do not process this word as a flag or an option
|
||||
continue
|
||||
fi
|
||||
|
||||
local word="${unparsed_words["${word_index}"]}"
|
||||
if ! "${was_flag_option_terminator_seen}"; then
|
||||
case "${word}" in
|
||||
--)
|
||||
unset "unparsed_words[${word_index}]"
|
||||
# by itself -- is a flag/option terminator, but if it is the last word, it is the start of a completion
|
||||
if [[ "${word_index}" -ne "${unparsed_word_indices[${#unparsed_word_indices[@]} - 1]}" ]]; then
|
||||
was_flag_option_terminator_seen=true
|
||||
fi
|
||||
continue
|
||||
;;
|
||||
-*)
|
||||
# ${word} is a flag or an option
|
||||
# If ${word} is an option, mark that the next word to be parsed is an option value
|
||||
local option
|
||||
for option in "${repeating_options[@]}" "${non_repeating_options[@]}"; do
|
||||
[[ "${word}" = "${option}" ]] && is_parsing_option_value=true && break
|
||||
done
|
||||
|
||||
# Remove ${word} from ${non_repeating_flags} or ${non_repeating_options} so it isn't offered again
|
||||
local not_found=true
|
||||
local -i index
|
||||
for index in "${!non_repeating_flags[@]}"; do
|
||||
if [[ "${non_repeating_flags[${index}]}" = "${word}" ]]; then
|
||||
unset "non_repeating_flags[${index}]"
|
||||
non_repeating_flags=("${non_repeating_flags[@]}")
|
||||
not_found=false
|
||||
break
|
||||
fi
|
||||
done
|
||||
if "${not_found}"; then
|
||||
for index in "${!non_repeating_flags[@]}"; do
|
||||
if [[ "${non_repeating_flags[${index}]}" = "${word}" ]]; then
|
||||
unset "non_repeating_flags[${index}]"
|
||||
non_repeating_flags=("${non_repeating_flags[@]}")
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
unset "unparsed_words[${word_index}]"
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# ${word} is neither a flag, nor an option, nor an option value
|
||||
if [[ "${positional_number}" -lt "${positional_count}" || "${positional_count}" -lt 0 ]]; then
|
||||
# ${word} is a positional
|
||||
((positional_number++))
|
||||
unset "unparsed_words[${word_index}]"
|
||||
else
|
||||
if [[ -z "${word}" ]]; then
|
||||
# Could be completing a flag, option, or subcommand
|
||||
positional_number=-1
|
||||
else
|
||||
# ${word} is a subcommand or invalid, so stop processing this (sub)command
|
||||
positional_number=-2
|
||||
fi
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
unparsed_words=("${unparsed_words[@]}")
|
||||
|
||||
if\
|
||||
! "${was_flag_option_terminator_seen}"\
|
||||
&& ! "${is_parsing_option_value}"\
|
||||
&& [[ ("${cur}" = -* && "${positional_number}" -ge 0) || "${positional_number}" -eq -1 ]]
|
||||
then
|
||||
COMPREPLY+=($(compgen -W "${repeating_flags[*]} ${non_repeating_flags[*]} ${repeating_options[*]} ${non_repeating_options[*]}" -- "${cur}"))
|
||||
fi
|
||||
}
|
||||
|
||||
__defaultasflag-test_add_completions() {
|
||||
local completion
|
||||
while IFS='' read -r completion; do
|
||||
COMPREPLY+=("${completion}")
|
||||
done < <(IFS=$'\n' compgen "${@}" -- "${cur}")
|
||||
}
|
||||
|
||||
__defaultasflag-test_custom_complete() {
|
||||
if [[ -n "${cur}" || -z ${COMP_WORDS[${COMP_CWORD}]} || "${COMP_LINE:${COMP_POINT}:1}" != ' ' ]]; then
|
||||
local -ar words=("${COMP_WORDS[@]}")
|
||||
else
|
||||
local -ar words=("${COMP_WORDS[@]::${COMP_CWORD}}" '' "${COMP_WORDS[@]:${COMP_CWORD}}")
|
||||
fi
|
||||
|
||||
"${COMP_WORDS[0]}" "${@}" "${words[@]}"
|
||||
}
|
||||
|
||||
_defaultasflag-test() {
|
||||
local state
|
||||
state="$(shopt -p;shopt -po)"
|
||||
trap "${state//$'\n'/;}" RETURN
|
||||
shopt -s extglob
|
||||
set +o history +o posix
|
||||
|
||||
local -xr SAP_SHELL=bash
|
||||
local -x SAP_SHELL_VERSION
|
||||
SAP_SHELL_VERSION="$(IFS='.';printf %s "${BASH_VERSINFO[*]}")"
|
||||
local -r SAP_SHELL_VERSION
|
||||
|
||||
local -r cur="${2}"
|
||||
local -r prev="${3}"
|
||||
|
||||
local -i positional_number
|
||||
local -a unparsed_words=("${COMP_WORDS[@]:1:${COMP_CWORD}}")
|
||||
|
||||
local -a repeating_flags=()
|
||||
local -a non_repeating_flags=(--help -h --help)
|
||||
local -a repeating_options=()
|
||||
local -a non_repeating_options=(--bin-path --count --verbose --log-level)
|
||||
__defaultasflag-test_offer_flags_options 1
|
||||
|
||||
# Offer option value completions
|
||||
case "${prev}" in
|
||||
'--bin-path')
|
||||
__defaultasflag-test_add_completions -d
|
||||
return
|
||||
;;
|
||||
'--count')
|
||||
return
|
||||
;;
|
||||
'--verbose')
|
||||
return
|
||||
;;
|
||||
'--log-level')
|
||||
__defaultasflag-test_add_completions -W 'DEBUG'$'\n''INFO'$'\n''WARN'$'\n''ERROR'
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
# Offer positional completions
|
||||
case "${positional_number}" in
|
||||
1)
|
||||
__defaultasflag-test_add_completions -f
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
# Offer subcommand / subcommand argument completions
|
||||
local -r subcommand="${unparsed_words[0]}"
|
||||
unset 'unparsed_words[0]'
|
||||
unparsed_words=("${unparsed_words[@]}")
|
||||
case "${subcommand}" in
|
||||
help)
|
||||
# Offer subcommand argument completions
|
||||
"_defaultasflag-test_${subcommand}"
|
||||
;;
|
||||
*)
|
||||
# Offer subcommand completions
|
||||
COMPREPLY+=($(compgen -W 'help' -- "${cur}"))
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_defaultasflag-test_help() {
|
||||
:
|
||||
}
|
||||
|
||||
complete -o filenames -F _defaultasflag-test defaultasflag-test
|
||||
@@ -0,0 +1,101 @@
|
||||
function __defaultasflag-test_should_offer_completions_for_flags_or_options -a expected_commands
|
||||
set -l non_repeating_flags_or_options $argv[2..]
|
||||
|
||||
set -l non_repeating_flags_or_options_absent 0
|
||||
set -l positional_index 0
|
||||
set -l commands
|
||||
__defaultasflag-test_parse_tokens
|
||||
test "$commands" = "$expected_commands"; and return $non_repeating_flags_or_options_absent
|
||||
end
|
||||
|
||||
function __defaultasflag-test_should_offer_completions_for_positional -a expected_commands expected_positional_index positional_index_comparison
|
||||
if test -z $positional_index_comparison
|
||||
set positional_index_comparison -eq
|
||||
end
|
||||
|
||||
set -l non_repeating_flags_or_options
|
||||
set -l non_repeating_flags_or_options_absent 0
|
||||
set -l positional_index 0
|
||||
set -l commands
|
||||
__defaultasflag-test_parse_tokens
|
||||
test "$commands" = "$expected_commands" -a \( "$positional_index" "$positional_index_comparison" "$expected_positional_index" \)
|
||||
end
|
||||
|
||||
function __defaultasflag-test_parse_tokens -S
|
||||
set -l unparsed_tokens (__defaultasflag-test_tokens -pc)
|
||||
set -l present_flags_and_options
|
||||
|
||||
switch $unparsed_tokens[1]
|
||||
case 'defaultasflag-test'
|
||||
__defaultasflag-test_parse_subcommand 1 'bin-path=' 'count=' 'verbose=' 'log-level=' 'help' 'h/help'
|
||||
switch $unparsed_tokens[1]
|
||||
case 'help'
|
||||
__defaultasflag-test_parse_subcommand -r 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function __defaultasflag-test_tokens
|
||||
if test (string split -m 1 -f 1 -- . "$FISH_VERSION") -gt 3
|
||||
commandline --tokens-raw $argv
|
||||
else
|
||||
commandline -o $argv
|
||||
end
|
||||
end
|
||||
|
||||
function __defaultasflag-test_parse_subcommand -S -a positional_count
|
||||
argparse -s r -- $argv
|
||||
set -l option_specs $argv[2..]
|
||||
|
||||
set -a commands $unparsed_tokens[1]
|
||||
set -e unparsed_tokens[1]
|
||||
|
||||
set positional_index 0
|
||||
|
||||
while true
|
||||
argparse -sn "$commands" $option_specs -- $unparsed_tokens 2> /dev/null
|
||||
set unparsed_tokens $argv
|
||||
set positional_index (math $positional_index + 1)
|
||||
|
||||
for non_repeating_flag_or_option in $non_repeating_flags_or_options
|
||||
if set -ql _flag_$non_repeating_flag_or_option
|
||||
set non_repeating_flags_or_options_absent 1
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if test (count $unparsed_tokens) -eq 0 -o \( -z "$_flag_r" -a "$positional_index" -gt "$positional_count" \)
|
||||
break
|
||||
end
|
||||
set -e unparsed_tokens[1]
|
||||
end
|
||||
end
|
||||
|
||||
function __defaultasflag-test_complete_directories
|
||||
set -l token (commandline -t)
|
||||
string match -- '*/' $token
|
||||
set -l subdirs $token*/
|
||||
printf '%s\n' $subdirs
|
||||
end
|
||||
|
||||
function __defaultasflag-test_custom_completion
|
||||
set -x SAP_SHELL fish
|
||||
set -x SAP_SHELL_VERSION $FISH_VERSION
|
||||
|
||||
set -l tokens (__defaultasflag-test_tokens -p)
|
||||
if test -z (__defaultasflag-test_tokens -t)
|
||||
set -l index (count (__defaultasflag-test_tokens -pc))
|
||||
set tokens $tokens[..$index] \'\' $tokens[(math $index + 1)..]
|
||||
end
|
||||
command $tokens[1] $argv $tokens
|
||||
end
|
||||
|
||||
complete -c 'defaultasflag-test' -f
|
||||
complete -c 'defaultasflag-test' -n '__defaultasflag-test_should_offer_completions_for_flags_or_options "defaultasflag-test" bin-path' -l 'bin-path' -rfa '(__defaultasflag-test_complete_directories)'
|
||||
complete -c 'defaultasflag-test' -n '__defaultasflag-test_should_offer_completions_for_flags_or_options "defaultasflag-test" count' -l 'count' -rfka ''
|
||||
complete -c 'defaultasflag-test' -n '__defaultasflag-test_should_offer_completions_for_flags_or_options "defaultasflag-test" verbose' -l 'verbose' -rfka ''
|
||||
complete -c 'defaultasflag-test' -n '__defaultasflag-test_should_offer_completions_for_flags_or_options "defaultasflag-test" log-level' -l 'log-level' -rfka 'DEBUG INFO WARN ERROR'
|
||||
complete -c 'defaultasflag-test' -n '__defaultasflag-test_should_offer_completions_for_flags_or_options "defaultasflag-test" help' -l 'help'
|
||||
complete -c 'defaultasflag-test' -n '__defaultasflag-test_should_offer_completions_for_positional "defaultasflag-test" 1' -F
|
||||
complete -c 'defaultasflag-test' -n '__defaultasflag-test_should_offer_completions_for_flags_or_options "defaultasflag-test" h help' -s 'h' -l 'help' -d 'Show help information.'
|
||||
complete -c 'defaultasflag-test' -n '__defaultasflag-test_should_offer_completions_for_positional "defaultasflag-test" 2' -fa 'help' -d 'Show subcommand help information.'
|
||||
@@ -0,0 +1,89 @@
|
||||
#compdef defaultasflag-test
|
||||
|
||||
__defaultasflag-test_complete() {
|
||||
local -ar non_empty_completions=("${@:#(|:*)}")
|
||||
local -ar empty_completions=("${(M)@:#(|:*)}")
|
||||
_describe -V '' non_empty_completions -- empty_completions -P $'\'\''
|
||||
}
|
||||
|
||||
__defaultasflag-test_custom_complete() {
|
||||
local -a completions
|
||||
completions=("${(@f)"$("${command_name}" "${@}" "${command_line[@]}")"}")
|
||||
if [[ "${#completions[@]}" -gt 1 ]]; then
|
||||
__defaultasflag-test_complete "${completions[@]:0:-1}"
|
||||
fi
|
||||
}
|
||||
|
||||
__defaultasflag-test_cursor_index_in_current_word() {
|
||||
if [[ -z "${QIPREFIX}${IPREFIX}${PREFIX}" ]]; then
|
||||
printf 0
|
||||
else
|
||||
printf %s "${#${(z)LBUFFER}[-1]}"
|
||||
fi
|
||||
}
|
||||
|
||||
_defaultasflag-test() {
|
||||
emulate -RL zsh -G
|
||||
setopt extendedglob nullglob numericglobsort
|
||||
unsetopt aliases banghist
|
||||
|
||||
local -xr SAP_SHELL=zsh
|
||||
local -x SAP_SHELL_VERSION
|
||||
SAP_SHELL_VERSION="$(builtin emulate zsh -c 'printf %s "${ZSH_VERSION}"')"
|
||||
local -r SAP_SHELL_VERSION
|
||||
|
||||
local context state state_descr line
|
||||
local -A opt_args
|
||||
|
||||
local -r command_name="${words[1]}"
|
||||
local -ar command_line=("${words[@]}")
|
||||
local -ir current_word_index="$((CURRENT - 1))"
|
||||
|
||||
local -i ret=1
|
||||
local -ar ___log_level=('DEBUG' 'INFO' 'WARN' 'ERROR')
|
||||
local -ar arg_specs=(
|
||||
'--bin-path:bin-path:_files -/'
|
||||
'--count:count:'
|
||||
'--verbose:verbose:'
|
||||
'--log-level:log-level:{__defaultasflag-test_complete "${___log_level[@]}"}'
|
||||
'--help'
|
||||
':input:_files'
|
||||
'(-h --help)'{-h,--help}'[Show help information.]'
|
||||
'(-): :->command'
|
||||
'(-)*:: :->arg'
|
||||
)
|
||||
_arguments -w -s -S : "${arg_specs[@]}" && ret=0
|
||||
case "${state}" in
|
||||
command)
|
||||
local -ar subcommands=(
|
||||
'help:Show subcommand help information.'
|
||||
)
|
||||
_describe -V subcommand subcommands && ret=0
|
||||
;;
|
||||
arg)
|
||||
case "${words[1]}" in
|
||||
help)
|
||||
"_defaultasflag-test_${words[1]}" && ret=0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
return "${ret}"
|
||||
}
|
||||
|
||||
_defaultasflag-test_help() {
|
||||
local -i ret=1
|
||||
local -ar arg_specs=(
|
||||
'*:subcommands:'
|
||||
)
|
||||
_arguments -w -s -S : "${arg_specs[@]}" && ret=0
|
||||
|
||||
return "${ret}"
|
||||
}
|
||||
|
||||
if [[ "${funcstack[1]}" = _defaultasflag-test ]]; then
|
||||
_defaultasflag-test "${@}"
|
||||
else
|
||||
compdef _defaultasflag-test defaultasflag-test
|
||||
fi
|
||||
@@ -0,0 +1,144 @@
|
||||
{
|
||||
"command" : {
|
||||
"abstract" : "A command with defaultAsFlag options for testing dump help.",
|
||||
"arguments" : [
|
||||
{
|
||||
"defaultValue" : "\/usr\/bin",
|
||||
"isOptional" : true,
|
||||
"isRepeating" : false,
|
||||
"kind" : "option",
|
||||
"names" : [
|
||||
{
|
||||
"kind" : "long",
|
||||
"name" : "binary-path"
|
||||
}
|
||||
],
|
||||
"parsingStrategy" : "scanningForValue",
|
||||
"preferredName" : {
|
||||
"kind" : "long",
|
||||
"name" : "binary-path"
|
||||
},
|
||||
"shouldDisplay" : true,
|
||||
"valueName" : "binary-path"
|
||||
},
|
||||
{
|
||||
"defaultValue" : "42",
|
||||
"isOptional" : true,
|
||||
"isRepeating" : false,
|
||||
"kind" : "option",
|
||||
"names" : [
|
||||
{
|
||||
"kind" : "long",
|
||||
"name" : "count"
|
||||
}
|
||||
],
|
||||
"parsingStrategy" : "scanningForValue",
|
||||
"preferredName" : {
|
||||
"kind" : "long",
|
||||
"name" : "count"
|
||||
},
|
||||
"shouldDisplay" : true,
|
||||
"valueName" : "count"
|
||||
},
|
||||
{
|
||||
"defaultValue" : "true",
|
||||
"isOptional" : true,
|
||||
"isRepeating" : false,
|
||||
"kind" : "option",
|
||||
"names" : [
|
||||
{
|
||||
"kind" : "long",
|
||||
"name" : "verbose"
|
||||
}
|
||||
],
|
||||
"parsingStrategy" : "scanningForValue",
|
||||
"preferredName" : {
|
||||
"kind" : "long",
|
||||
"name" : "verbose"
|
||||
},
|
||||
"shouldDisplay" : true,
|
||||
"valueName" : "verbose"
|
||||
},
|
||||
{
|
||||
"isOptional" : false,
|
||||
"isRepeating" : false,
|
||||
"kind" : "positional",
|
||||
"parsingStrategy" : "default",
|
||||
"shouldDisplay" : true,
|
||||
"valueName" : "input"
|
||||
},
|
||||
{
|
||||
"abstract" : "Show help information.",
|
||||
"isOptional" : true,
|
||||
"isRepeating" : false,
|
||||
"kind" : "flag",
|
||||
"names" : [
|
||||
{
|
||||
"kind" : "short",
|
||||
"name" : "h"
|
||||
},
|
||||
{
|
||||
"kind" : "long",
|
||||
"name" : "help"
|
||||
}
|
||||
],
|
||||
"parsingStrategy" : "default",
|
||||
"preferredName" : {
|
||||
"kind" : "long",
|
||||
"name" : "help"
|
||||
},
|
||||
"shouldDisplay" : true,
|
||||
"valueName" : "help"
|
||||
}
|
||||
],
|
||||
"commandName" : "default-as-flag-command",
|
||||
"shouldDisplay" : true,
|
||||
"subcommands" : [
|
||||
{
|
||||
"abstract" : "Show subcommand help information.",
|
||||
"arguments" : [
|
||||
{
|
||||
"isOptional" : true,
|
||||
"isRepeating" : true,
|
||||
"kind" : "positional",
|
||||
"parsingStrategy" : "default",
|
||||
"shouldDisplay" : true,
|
||||
"valueName" : "subcommands"
|
||||
},
|
||||
{
|
||||
"isOptional" : true,
|
||||
"isRepeating" : false,
|
||||
"kind" : "flag",
|
||||
"names" : [
|
||||
{
|
||||
"kind" : "short",
|
||||
"name" : "h"
|
||||
},
|
||||
{
|
||||
"kind" : "long",
|
||||
"name" : "help"
|
||||
},
|
||||
{
|
||||
"kind" : "longWithSingleDash",
|
||||
"name" : "help"
|
||||
}
|
||||
],
|
||||
"parsingStrategy" : "default",
|
||||
"preferredName" : {
|
||||
"kind" : "long",
|
||||
"name" : "help"
|
||||
},
|
||||
"shouldDisplay" : false,
|
||||
"valueName" : "help"
|
||||
}
|
||||
],
|
||||
"commandName" : "help",
|
||||
"shouldDisplay" : true,
|
||||
"superCommands" : [
|
||||
"default-as-flag-command"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"serializationVersion" : 0
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
{
|
||||
"command" : {
|
||||
"abstract" : "A command with defaultAsFlag options using transforms for testing dump help.",
|
||||
"arguments" : [
|
||||
{
|
||||
"defaultValue" : "\/default\/output",
|
||||
"isOptional" : true,
|
||||
"isRepeating" : false,
|
||||
"kind" : "option",
|
||||
"names" : [
|
||||
{
|
||||
"kind" : "long",
|
||||
"name" : "output-dir"
|
||||
}
|
||||
],
|
||||
"parsingStrategy" : "scanningForValue",
|
||||
"preferredName" : {
|
||||
"kind" : "long",
|
||||
"name" : "output-dir"
|
||||
},
|
||||
"shouldDisplay" : true,
|
||||
"valueName" : "output-dir"
|
||||
},
|
||||
{
|
||||
"defaultValue" : "INFO",
|
||||
"isOptional" : true,
|
||||
"isRepeating" : false,
|
||||
"kind" : "option",
|
||||
"names" : [
|
||||
{
|
||||
"kind" : "long",
|
||||
"name" : "level"
|
||||
}
|
||||
],
|
||||
"parsingStrategy" : "scanningForValue",
|
||||
"preferredName" : {
|
||||
"kind" : "long",
|
||||
"name" : "level"
|
||||
},
|
||||
"shouldDisplay" : true,
|
||||
"valueName" : "level"
|
||||
},
|
||||
{
|
||||
"isOptional" : true,
|
||||
"isRepeating" : false,
|
||||
"kind" : "flag",
|
||||
"names" : [
|
||||
{
|
||||
"kind" : "long",
|
||||
"name" : "debug"
|
||||
}
|
||||
],
|
||||
"parsingStrategy" : "default",
|
||||
"preferredName" : {
|
||||
"kind" : "long",
|
||||
"name" : "debug"
|
||||
},
|
||||
"shouldDisplay" : true,
|
||||
"valueName" : "debug"
|
||||
},
|
||||
{
|
||||
"abstract" : "Show help information.",
|
||||
"isOptional" : true,
|
||||
"isRepeating" : false,
|
||||
"kind" : "flag",
|
||||
"names" : [
|
||||
{
|
||||
"kind" : "short",
|
||||
"name" : "h"
|
||||
},
|
||||
{
|
||||
"kind" : "long",
|
||||
"name" : "help"
|
||||
}
|
||||
],
|
||||
"parsingStrategy" : "default",
|
||||
"preferredName" : {
|
||||
"kind" : "long",
|
||||
"name" : "help"
|
||||
},
|
||||
"shouldDisplay" : true,
|
||||
"valueName" : "help"
|
||||
}
|
||||
],
|
||||
"commandName" : "default-as-flag-with-transform-command",
|
||||
"shouldDisplay" : true,
|
||||
"subcommands" : [
|
||||
{
|
||||
"abstract" : "Show subcommand help information.",
|
||||
"arguments" : [
|
||||
{
|
||||
"isOptional" : true,
|
||||
"isRepeating" : true,
|
||||
"kind" : "positional",
|
||||
"parsingStrategy" : "default",
|
||||
"shouldDisplay" : true,
|
||||
"valueName" : "subcommands"
|
||||
},
|
||||
{
|
||||
"isOptional" : true,
|
||||
"isRepeating" : false,
|
||||
"kind" : "flag",
|
||||
"names" : [
|
||||
{
|
||||
"kind" : "short",
|
||||
"name" : "h"
|
||||
},
|
||||
{
|
||||
"kind" : "long",
|
||||
"name" : "help"
|
||||
},
|
||||
{
|
||||
"kind" : "longWithSingleDash",
|
||||
"name" : "help"
|
||||
}
|
||||
],
|
||||
"parsingStrategy" : "default",
|
||||
"preferredName" : {
|
||||
"kind" : "long",
|
||||
"name" : "help"
|
||||
},
|
||||
"shouldDisplay" : false,
|
||||
"valueName" : "help"
|
||||
}
|
||||
],
|
||||
"commandName" : "help",
|
||||
"shouldDisplay" : true,
|
||||
"superCommands" : [
|
||||
"default-as-flag-with-transform-command"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"serializationVersion" : 0
|
||||
}
|
||||
Reference in New Issue
Block a user