| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- //===----------------------------------------------------------------------===//
- //
- // This source file is part of the Swift Argument Parser open source project
- //
- // Copyright (c) 2020 Apple Inc. and the Swift project authors
- // Licensed under Apache License v2.0 with Runtime Library Exception
- //
- // See https://swift.org/LICENSE.txt for license information
- //
- //===----------------------------------------------------------------------===//
- struct ArgumentDefinition {
- /// A closure that modifies a `ParsedValues` instance to include this
- /// argument's value.
- enum Update {
- typealias Nullary = (InputOrigin, Name?, inout ParsedValues) throws -> Void
- typealias Unary = (InputOrigin, Name?, String, inout ParsedValues) throws ->
- Void
- /// An argument that gets its value solely from its presence.
- case nullary(Nullary)
- /// An argument that takes a string as its value.
- case unary(Unary)
- }
- typealias Initial = (InputOrigin, inout ParsedValues) throws -> Void
- enum Kind {
- /// An option or flag, with a name and an optional value.
- case named([Name])
- /// A positional argument.
- case positional
- /// A pseudo-argument that takes its value from a property's default value
- /// instead of from command-line arguments.
- case `default`
- }
- struct Help {
- struct Options: OptionSet {
- var rawValue: UInt
- static let isOptional = Options(rawValue: 1 << 0)
- static let isRepeating = Options(rawValue: 1 << 1)
- }
- var options: Options
- var defaultValue: String?
- var keys: [InputKey]
- var allValueStrings: [String]
- var isComposite: Bool
- var abstract: String
- var discussion: ArgumentDiscussion?
- var valueName: String
- var visibility: ArgumentVisibility
- var parentTitle: String
- init(
- allValueStrings: [String],
- options: Options,
- help: ArgumentHelp?,
- defaultValue: String?,
- key: InputKey,
- isComposite: Bool
- ) {
- self.options = options
- self.defaultValue = defaultValue
- self.keys = [key]
- self.allValueStrings = allValueStrings
- self.isComposite = isComposite
- self.abstract = help?.abstract ?? ""
- self.discussion = .init(help?.discussion, help?.argumentType)
- self.valueName = help?.valueName ?? ""
- self.visibility = help?.visibility ?? .default
- self.parentTitle = ""
- }
- }
- /// This folds the public `ArrayParsingStrategy` and `SingleValueParsingStrategy`
- /// into a single enum.
- enum ParsingStrategy {
- /// Expect the next `SplitArguments.Element` to be a value and parse it.
- /// Will fail if the next input is an option.
- case `default`
- /// Parse the next `SplitArguments.Element.value`
- case scanningForValue
- /// Parse the next `SplitArguments.Element` as a value, regardless of its type.
- case unconditional
- /// Parse multiple `SplitArguments.Element.value` up to the next non-`.value`
- case upToNextOption
- /// Parse all remaining `SplitArguments.Element` as values, regardless of its type.
- case allRemainingInput
- /// Collect all the elements after the terminator, preventing them from
- /// appearing in any other position.
- case postTerminator
- /// Collect all unused inputs once recognized arguments/options/flags have
- /// been parsed.
- case allUnrecognized
- }
- var kind: Kind
- var help: Help
- var completion: CompletionKind
- var parsingStrategy: ParsingStrategy
- var update: Update
- var initial: Initial
- var names: [Name] {
- switch kind {
- case .named(let n): return n
- case .positional, .default: return []
- }
- }
- var valueName: String {
- help.valueName.mapEmpty {
- names.preferredName?.valueString
- ?? help.keys.first?.name.convertedToSnakeCase(separator: "-")
- ?? "value"
- }
- }
- init(
- kind: Kind,
- help: Help,
- completion: CompletionKind,
- parsingStrategy: ParsingStrategy = .default,
- update: Update,
- initial: @escaping Initial = { _, _ in }
- ) {
- if case (.positional, .nullary) = (kind, update) {
- preconditionFailure("Can't create a nullary positional argument.")
- }
- self.kind = kind
- self.help = help
- self.completion = completion
- self.parsingStrategy = parsingStrategy
- self.update = update
- self.initial = initial
- }
- }
- extension ArgumentDefinition: CustomDebugStringConvertible {
- var debugDescription: String {
- switch (kind, update) {
- case (.named(let names), .nullary):
- return
- names
- .map { $0.synopsisString }
- .joined(separator: ",")
- case (.named(let names), .unary):
- return
- names
- .map { $0.synopsisString }
- .joined(separator: ",")
- + " <\(valueName)>"
- case (.positional, _):
- return "<\(valueName)>"
- case (.default, _):
- return ""
- }
- }
- }
- extension ArgumentDefinition {
- var optional: ArgumentDefinition {
- var result = self
- result.help.options.insert(.isOptional)
- return result
- }
- var nonOptional: ArgumentDefinition {
- var result = self
- result.help.options.remove(.isOptional)
- return result
- }
- }
- extension ArgumentDefinition {
- var isPositional: Bool {
- if case .positional = kind {
- return true
- }
- return false
- }
- var isRepeatingPositional: Bool {
- isPositional && help.options.contains(.isRepeating)
- }
- var isNullary: Bool {
- if case .nullary = update {
- return true
- } else {
- return false
- }
- }
- var allowsJoinedValue: Bool {
- names.contains(where: { $0.allowsJoined })
- }
- }
- extension ArgumentDefinition.Kind {
- static func name(key: InputKey, specification: NameSpecification)
- -> ArgumentDefinition.Kind
- {
- let names = specification.makeNames(key)
- return ArgumentDefinition.Kind.named(names)
- }
- }
- // MARK: - Common @Argument, @Option, Unparsed Initializer Path
- extension ArgumentDefinition {
- // MARK: Unparsed Keys
- /// Creates an argument definition for a property that isn't parsed from the
- /// command line.
- ///
- /// 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?) {
- self.init(
- container: Bare<Any>.self,
- key: InputKey(name: unparsedKey, parent: parent),
- kind: .default,
- allValueStrings: [],
- help: .private,
- defaultValueDescription: nil,
- parsingStrategy: .default,
- parser: { (key, origin, name, valueString) in
- throw ParserError.unableToParseValue(
- origin, name, valueString, forKey: key, originalError: nil)
- },
- initial: defaultValue,
- completion: nil)
- }
- init<Container>(
- container: Container.Type,
- key: InputKey,
- kind: ArgumentDefinition.Kind,
- help: ArgumentHelp?,
- parsingStrategy: ParsingStrategy,
- initial: Container.Initial?,
- completion: CompletionKind?
- ) where Container: ArgumentDefinitionContainerExpressibleByArgument {
- self.init(
- container: Container.self,
- key: key,
- kind: kind,
- allValueStrings: Container.Contained.allValueStrings,
- help: help,
- defaultValueDescription: Container.defaultValueDescription(initial),
- parsingStrategy: parsingStrategy,
- parser: { (key, origin, name, valueString) -> Container.Contained in
- guard let value = Container.Contained(argument: valueString) else {
- throw ParserError.unableToParseValue(
- origin, name, valueString, forKey: key, originalError: nil)
- }
- return value
- },
- initial: initial,
- completion: completion ?? Container.Contained.defaultCompletionKind)
- }
- init<Container>(
- container: Container.Type,
- key: InputKey,
- kind: ArgumentDefinition.Kind,
- help: ArgumentHelp?,
- parsingStrategy: ParsingStrategy,
- transform: @escaping (String) throws -> Container.Contained,
- initial: Container.Initial?,
- completion: CompletionKind?
- ) where Container: ArgumentDefinitionContainer {
- self.init(
- container: Container.self,
- key: key,
- kind: kind,
- allValueStrings: [],
- help: help,
- defaultValueDescription: nil,
- parsingStrategy: parsingStrategy,
- parser: { (key, origin, name, valueString) -> Container.Contained in
- do {
- return try transform(valueString)
- } catch {
- throw ParserError.unableToParseValue(
- origin, name, valueString, forKey: key, originalError: error)
- }
- },
- initial: initial,
- completion: completion)
- }
- private init<Container>(
- container: Container.Type,
- key: InputKey,
- kind: ArgumentDefinition.Kind,
- allValueStrings: [String],
- help: ArgumentHelp?,
- defaultValueDescription: String?,
- parsingStrategy: ParsingStrategy,
- parser:
- @escaping (InputKey, InputOrigin, Name?, String) throws ->
- Container.Contained,
- initial: Container.Initial?,
- completion: CompletionKind?
- ) where Container: ArgumentDefinitionContainer {
- self.init(
- kind: kind,
- help: .init(
- allValueStrings: allValueStrings,
- options: Container.helpOptions.union(
- initial != nil ? [.isOptional] : []),
- help: help,
- defaultValue: defaultValueDescription,
- key: key,
- isComposite: false),
- completion: completion ?? .default,
- parsingStrategy: parsingStrategy,
- update: .unary({ (origin, name, valueString, parsedValues) in
- let value = try parser(key, origin, name, valueString)
- Container.update(
- parsedValues: &parsedValues,
- value: value,
- key: key,
- origin: origin)
- }),
- initial: { origin, values in
- let inputOrigin: InputOrigin
- switch kind {
- case .default:
- inputOrigin = InputOrigin(element: .defaultValue)
- case .named, .positional:
- inputOrigin = origin
- }
- values.set(initial, forKey: key, inputOrigin: inputOrigin)
- })
- }
- }
- // MARK: - Abstraction over T, Option<T>, Array<T>
- protocol ArgumentDefinitionContainer {
- associatedtype Contained
- associatedtype Initial
- static var helpOptions: ArgumentDefinition.Help.Options { get }
- static func update(
- parsedValues: inout ParsedValues,
- value: Contained,
- key: InputKey,
- origin: InputOrigin)
- }
- protocol ArgumentDefinitionContainerExpressibleByArgument:
- ArgumentDefinitionContainer
- where Contained: ExpressibleByArgument {
- static func defaultValueDescription(_ initial: Initial?) -> String?
- }
- enum Bare<T> {}
- extension Bare: ArgumentDefinitionContainer {
- typealias Contained = T
- typealias Initial = T
- static var helpOptions: ArgumentDefinition.Help.Options { [] }
- static func update(
- parsedValues: inout ParsedValues,
- value: Contained,
- key: InputKey,
- origin: InputOrigin
- ) {
- parsedValues.set(value, forKey: key, inputOrigin: origin)
- }
- }
- extension Bare: ArgumentDefinitionContainerExpressibleByArgument
- where Contained: ExpressibleByArgument {
- static func defaultValueDescription(_ initial: T?) -> String? {
- guard let initial = initial else { return nil }
- if let initial = initial as? (any CaseIterable & RawRepresentable) {
- return String(describing: initial.rawValue)
- }
- return initial.defaultValueDescription
- }
- }
- extension Optional: ArgumentDefinitionContainer {
- typealias Contained = Wrapped
- typealias Initial = Wrapped
- static var helpOptions: ArgumentDefinition.Help.Options { [.isOptional] }
- static func update(
- parsedValues: inout ParsedValues,
- value: Contained,
- key: InputKey,
- origin: InputOrigin
- ) {
- parsedValues.set(value, forKey: key, inputOrigin: origin)
- }
- }
- extension Optional: ArgumentDefinitionContainerExpressibleByArgument
- where Contained: ExpressibleByArgument {
- static func defaultValueDescription(_ initial: Initial?) -> String? {
- guard let initial = initial else { return nil }
- if let initial = initial as? (any CaseIterable & RawRepresentable) {
- return String(describing: initial.rawValue)
- }
- return initial.defaultValueDescription
- }
- }
- extension Array: ArgumentDefinitionContainer {
- typealias Contained = Element
- typealias Initial = [Element]
- static var helpOptions: ArgumentDefinition.Help.Options { [.isRepeating] }
- static func update(
- parsedValues: inout ParsedValues,
- value: Element,
- key: InputKey,
- origin: InputOrigin
- ) {
- parsedValues.update(
- forKey: key,
- inputOrigin: origin,
- initial: .init(),
- closure: { $0.append(value) })
- }
- }
- extension Array: ArgumentDefinitionContainerExpressibleByArgument
- where Element: ExpressibleByArgument {
- static func defaultValueDescription(_ initial: [Element]?) -> String? {
- guard let initial = initial else { return nil }
- guard !initial.isEmpty else { return nil }
- return initial
- .lazy
- .map { element in
- if let element = element as? (any CaseIterable & RawRepresentable) {
- return String(describing: element.rawValue)
- }
- return element.defaultValueDescription
- }
- .joined(separator: ", ")
- }
- }
|