1
0

NameSpecification.swift 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Swift Argument Parser open source project
  4. //
  5. // Copyright (c) 2020 Apple Inc. and the Swift project authors
  6. // Licensed under Apache License v2.0 with Runtime Library Exception
  7. //
  8. // See https://swift.org/LICENSE.txt for license information
  9. //
  10. //===----------------------------------------------------------------------===//
  11. /// A specification for how to represent a property as a command-line argument
  12. /// label.
  13. public struct NameSpecification: ExpressibleByArrayLiteral {
  14. /// An individual property name translation.
  15. public struct Element: Hashable, Sendable {
  16. internal enum Representation: Hashable {
  17. case long
  18. case customLong(_ name: String, withSingleDash: Bool)
  19. case short
  20. case customShort(_ char: Character, allowingJoined: Bool)
  21. }
  22. internal var base: Representation
  23. /// Use the property's name, converted to lowercase with words separated by
  24. /// hyphens.
  25. ///
  26. /// For example, a property named `allowLongNames` would be converted to the
  27. /// label `--allow-long-names`.
  28. public static var long: Element {
  29. self.init(base: .long)
  30. }
  31. /// Use the given string instead of the property's name.
  32. ///
  33. /// To create a single-dash argument, pass `true` as `withSingleDash`. Note
  34. /// that combining single-dash options and options with short,
  35. /// single-character names can lead to ambiguities for the user.
  36. ///
  37. /// - Parameters:
  38. /// - name: The name of the option or flag.
  39. /// - withSingleDash: A Boolean value indicating whether to use a single
  40. /// dash as the prefix. If `false`, the name has a double-dash prefix.
  41. ///
  42. /// - Returns: A `long` name specification with the requested `name`.
  43. public static func customLong(
  44. _ name: String,
  45. withSingleDash: Bool = false
  46. ) -> Element {
  47. self.init(base: .customLong(name, withSingleDash: withSingleDash))
  48. }
  49. /// Use the first character of the property's name as a short option label.
  50. ///
  51. /// For example, a property named `verbose` would be converted to the
  52. /// label `-v`. Short labels can be combined into groups.
  53. public static var short: Element {
  54. self.init(base: .short)
  55. }
  56. /// Use the given character as a short option label.
  57. ///
  58. /// When passing `true` as `allowingJoined` in an `@Option` declaration,
  59. /// the user can join a value with the option name. For example, if an
  60. /// option is declared as `-D`, allowing joined values, a user could pass
  61. /// `-Ddebug` to specify `debug` as the value for that option.
  62. ///
  63. /// - Parameters:
  64. /// - char: The name of the option or flag.
  65. /// - allowingJoined: A Boolean value indicating whether this short name
  66. /// allows a joined value.
  67. ///
  68. /// - Returns: A `short` name specification with the requested `char`.
  69. public static func customShort(
  70. _ char: Character,
  71. allowingJoined: Bool = false
  72. ) -> Element {
  73. self.init(base: .customShort(char, allowingJoined: allowingJoined))
  74. }
  75. }
  76. var elements: [Element]
  77. public init<S>(_ sequence: S) where S: Sequence, Element == S.Element {
  78. self.elements = sequence.uniquing()
  79. }
  80. public init(arrayLiteral elements: Element...) {
  81. self.init(elements)
  82. }
  83. }
  84. extension NameSpecification: Sendable {}
  85. extension NameSpecification {
  86. /// Use the property's name converted to lowercase with words separated by
  87. /// hyphens.
  88. ///
  89. /// For example, a property named `allowLongNames` would be converted to the
  90. /// label `--allow-long-names`.
  91. public static var long: NameSpecification { [.long] }
  92. /// Use the given string instead of the property's name.
  93. ///
  94. /// To create a single-dash argument, pass `true` as `withSingleDash`. Note
  95. /// that combining single-dash options and options with short,
  96. /// single-character names can lead to ambiguities for the user.
  97. ///
  98. /// - Parameters:
  99. /// - name: The name of the option or flag.
  100. /// - withSingleDash: A Boolean value indicating whether to use a single
  101. /// dash as the prefix. If `false`, the name has a double-dash prefix.
  102. ///
  103. /// - Returns: A `long` name specification with the requested `name`.
  104. public static func customLong(
  105. _ name: String,
  106. withSingleDash: Bool = false
  107. ) -> NameSpecification {
  108. [.customLong(name, withSingleDash: withSingleDash)]
  109. }
  110. /// Use the first character of the property's name as a short option label.
  111. ///
  112. /// For example, a property named `verbose` would be converted to the
  113. /// label `-v`. Short labels can be combined into groups.
  114. public static var short: NameSpecification { [.short] }
  115. /// Use the given character as a short option label.
  116. ///
  117. /// When passing `true` as `allowingJoined` in an `@Option` declaration,
  118. /// the user can join a value with the option name. For example, if an
  119. /// option is declared as `-D`, allowing joined values, a user could pass
  120. /// `-Ddebug` to specify `debug` as the value for that option.
  121. ///
  122. /// - Parameters:
  123. /// - char: The name of the option or flag.
  124. /// - allowingJoined: A Boolean value indicating whether this short name
  125. /// allows a joined value.
  126. ///
  127. /// - Returns: A `short` name specification with the requested `char`.
  128. public static func customShort(
  129. _ char: Character,
  130. allowingJoined: Bool = false
  131. ) -> NameSpecification {
  132. [.customShort(char, allowingJoined: allowingJoined)]
  133. }
  134. /// Combine the `.short` and `.long` specifications to allow both long
  135. /// and short labels.
  136. ///
  137. /// For example, a property named `verbose` would be converted to both the
  138. /// long `--verbose` and short `-v` labels.
  139. public static var shortAndLong: NameSpecification { [.long, .short] }
  140. }
  141. extension NameSpecification.Element {
  142. /// Creates the argument name for this specification element.
  143. internal func name(for key: InputKey) -> Name? {
  144. switch self.base {
  145. case .long:
  146. return .long(key.name.convertedToSnakeCase(separator: "-"))
  147. case .short:
  148. guard let c = key.name.first else {
  149. fatalError(
  150. "Key '\(key.name)' has not characters to form short option name.")
  151. }
  152. return .short(c)
  153. case .customLong(let name, let withSingleDash):
  154. return withSingleDash
  155. ? .longWithSingleDash(name)
  156. : .long(name)
  157. case .customShort(let name, let allowingJoined):
  158. return .short(name, allowingJoined: allowingJoined)
  159. }
  160. }
  161. }
  162. extension NameSpecification {
  163. /// Creates the argument names for each element in the name specification.
  164. internal func makeNames(_ key: InputKey) -> [Name] {
  165. elements.compactMap { $0.name(for: key) }
  166. }
  167. }
  168. extension FlagInversion {
  169. /// Creates the enable and disable name(s) for the given flag.
  170. internal func enableDisableNamePair(
  171. for key: InputKey, name: NameSpecification
  172. ) -> ([Name], [Name]) {
  173. func makeNames(withPrefix prefix: String, includingShort: Bool) -> [Name] {
  174. name.elements.compactMap { element -> Name? in
  175. switch element.base {
  176. case .short, .customShort:
  177. return includingShort ? element.name(for: key) : nil
  178. case .long:
  179. let modifiedKey = InputKey(
  180. name: key.name.addingIntercappedPrefix(prefix), parent: key)
  181. return element.name(for: modifiedKey)
  182. case .customLong(let name, let withSingleDash):
  183. let modifiedName = name.addingPrefixWithAutodetectedStyle(prefix)
  184. let modifiedElement = NameSpecification.Element.customLong(
  185. modifiedName, withSingleDash: withSingleDash)
  186. return modifiedElement.name(for: key)
  187. }
  188. }
  189. }
  190. switch self.base {
  191. case .prefixedNo:
  192. return (
  193. name.makeNames(key),
  194. makeNames(withPrefix: "no", includingShort: false)
  195. )
  196. case .prefixedEnableDisable:
  197. return (
  198. makeNames(withPrefix: "enable", includingShort: true),
  199. makeNames(withPrefix: "disable", includingShort: false)
  200. )
  201. }
  202. }
  203. }