Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify synopsis string generation #316

Merged
merged 2 commits into from
May 22, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ struct BashCompletionsGenerator {
let commandName = commands.first!._commandName
let subcommandNames = commands.dropFirst().map { $0._commandName }.joined(separator: " ")
// TODO: Make this work for @Arguments
let argumentName = arg.preferredNameForSynopsis?.synopsisString
let argumentName = arg.names.preferredName?.synopsisString
?? arg.help.keys.first?.rawValue ?? "---"

return """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ extension ArgumentDefinition {
/// this argument.
func customCompletionCall(_ commands: [ParsableCommand.Type]) -> String {
let subcommandNames = commands.dropFirst().map { $0._commandName }.joined(separator: " ")
let argumentName = preferredNameForSynopsis?.synopsisString
let argumentName = names.preferredName?.synopsisString
?? self.help.keys.first?.rawValue ?? "---"
return "---completion \(subcommandNames) -- \(argumentName)"
}
Expand Down
3 changes: 1 addition & 2 deletions Sources/ArgumentParser/Parsing/ArgumentDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ struct ArgumentDefinition {

var valueName: String {
help.valueName.mapEmpty {
preferredNameForSynopsis?.valueString
names.preferredName?.valueString
?? help.keys.first?.rawValue.convertedToSnakeCase(separator: "-")
?? "value"
}
Expand Down Expand Up @@ -165,7 +165,6 @@ extension ArgumentDefinition: CustomDebugStringConvertible {
extension ArgumentDefinition {
var optional: ArgumentDefinition {
var result = self

result.help.options.insert(.isOptional)
return result
}
Expand Down
9 changes: 0 additions & 9 deletions Sources/ArgumentParser/Parsing/ArgumentSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,15 +159,6 @@ extension ArgumentSet {
extension ArgumentDefinition {
/// Create a unary / argument that parses using the given closure.
init<A>(key: InputKey, kind: ArgumentDefinition.Kind, parsingStrategy: ParsingStrategy = .default, parser: @escaping (String) -> A?, parseType type: A.Type = A.self, default initial: A?, completion: CompletionKind) {
let initialValueCreator: (InputOrigin, inout ParsedValues) throws -> Void
if let initialValue = initial {
initialValueCreator = { origin, values in
values.set(initialValue, forKey: key, inputOrigin: origin)
}
} else {
initialValueCreator = { _, _ in }
}

self.init(kind: kind, help: ArgumentDefinition.Help(key: key), completion: completion, parsingStrategy: parsingStrategy, update: .unary({ (origin, name, value, values) in
guard let v = parser(value) else {
throw ParserError.unableToParseValue(origin, name, value, forKey: key)
Expand Down
58 changes: 41 additions & 17 deletions Sources/ArgumentParser/Parsing/Name.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
//
//===----------------------------------------------------------------------===//

enum Name: Hashable {
enum Name {
/// A name (usually multi-character) prefixed with `--` (2 dashes) or equivalent.
case long(String)
/// A single character name prefixed with `-` (1 dash) or equivalent.
Expand All @@ -18,7 +18,9 @@ enum Name: Hashable {
case short(Character, allowingJoined: Bool = false)
/// A name (usually multi-character) prefixed with `-` (1 dash).
case longWithSingleDash(String)

}

extension Name {
init(_ baseName: Substring) {
assert(baseName.first == "-", "Attempted to create name for unprefixed argument")
if baseName.hasPrefix("--") {
Expand All @@ -31,6 +33,35 @@ enum Name: Hashable {
}
}

// short argument names based on the synopsisString
// this will put the single - options before the -- options
extension Name: Comparable {
static func < (lhs: Name, rhs: Name) -> Bool {
return lhs.synopsisString < rhs.synopsisString
}
}

extension Name: Hashable { }

extension Name {
enum Case: Equatable {
case long
case short
case longWithSingleDash
}

var `case`: Case {
switch self {
case .short:
return .short
case .longWithSingleDash:
return .longWithSingleDash
case .long:
return .long
}
}
}

extension Name {
var synopsisString: String {
switch self {
Expand All @@ -53,16 +84,7 @@ extension Name {
return n
}
}

var isShort: Bool {
switch self {
case .short:
return true
default:
return false
}
}


var allowsJoined: Bool {
switch self {
case .short(_, let allowingJoined):
Expand All @@ -82,10 +104,12 @@ extension Name {
}
}

// short argument names based on the synopsisString
// this will put the single - options before the -- options
extension Name: Comparable {
static func < (lhs: Name, rhs: Name) -> Bool {
return lhs.synopsisString < rhs.synopsisString
extension BidirectionalCollection where Element == Name {
var preferredName: Name? {
first { $0.case != .short } ?? first
}

var paritioned: [Name] {
filter { $0.case == .short } + filter { $0.case != .short }
}
}
19 changes: 13 additions & 6 deletions Sources/ArgumentParser/Usage/HelpGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,16 @@ internal struct HelpGenerator {
let groupEnd = args.firstIndex(where: { $0.help.keys != arg.help.keys }) ?? args.endIndex
let groupedArgs = [arg] + args[..<groupEnd]
args = args[groupEnd...]

synopsis = groupedArgs.compactMap { $0.synopsisForHelp }.joined(separator: "/")

let defaultValue = arg.help.defaultValue.map { "(default: \($0))" } ?? ""
synopsis = groupedArgs
.lazy
.filter { $0.help.shouldDisplay }
.map { $0.synopsisForHelp }
.joined(separator: "/")

let defaultValue = arg.help.defaultValue
.map { "(default: \($0))" } ?? ""

let descriptionString = groupedArgs
.lazy
.map { $0.help.abstract }
Expand All @@ -176,7 +182,9 @@ internal struct HelpGenerator {
.filter { !$0.isEmpty }
.joined(separator: " ")
} else {
synopsis = arg.synopsisForHelp ?? ""
synopsis = arg.help.shouldDisplay
? arg.synopsisForHelp
: ""

let defaultValue = arg.help.defaultValue.flatMap { $0.isEmpty ? nil : "(default: \($0))" }
description = [arg.help.abstract, defaultValue]
Expand Down Expand Up @@ -276,8 +284,7 @@ internal extension BidirectionalCollection where Element == ParsableCommand.Type
}

func getPrimaryHelpName() -> Name? {
let names = getHelpNames()
return names.first(where: { !$0.isShort }) ?? names.first
getHelpNames().preferredName
}

func versionArgumentDefintion() -> ArgumentDefinition? {
Expand Down
118 changes: 48 additions & 70 deletions Sources/ArgumentParser/Usage/UsageGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,49 +36,48 @@ extension UsageGenerator {
///
/// In `roff`.
var synopsis: String {
let definitionSynopsis = definition.synopsis
switch definitionSynopsis.count {
// Filter out options that should not be displayed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch 👍🏻

var options = definition
.filter { $0.help.shouldDisplay }
switch options.count {
case 0:
return toolName
case let x where x > 12:
// When we have too many options, keep required and positional arguments,
// but discard the rest.
let synopsis: [String] = definition.compactMap { argument in
guard argument.isPositional || !argument.help.options.contains(.isOptional) else {
return nil
}
return argument.synopsis
options = options.filter {
$0.isPositional || !$0.help.options.contains(.isOptional)
}
if !synopsis.isEmpty, synopsis.count <= 12 {
return "\(toolName) [<options>] \(synopsis.joined(separator: " "))"
// If there are between 1 and 12 options left, print them, otherwise print
// a simplified usage string.
if !options.isEmpty, options.count <= 12 {
let synopsis = options
.map { $0.synopsis }
.joined(separator: " ")
return "\(toolName) [<options>] \(synopsis)"
}
return "\(toolName) <options>"
default:
return "\(toolName) \(definition.synopsis.joined(separator: " "))"
let synopsis = options
.map { $0.synopsis }
.joined(separator: " ")
return "\(toolName) \(synopsis)"
}
}
}

extension ArgumentSet {
var synopsis: [String] {
return self
.compactMap { $0.synopsis }
}
}

extension ArgumentDefinition {
var synopsisForHelp: String? {
guard help.shouldDisplay else { return nil }

var synopsisForHelp: String {
switch kind {
case .named:
let joinedSynopsisString = partitionedNames
let joinedSynopsisString = names
.paritioned
.map { $0.synopsisString }
.joined(separator: ", ")

switch update {
case .unary:
return "\(joinedSynopsisString) <\(synopsisValueName ?? "")>"
return "\(joinedSynopsisString) <\(valueName)>"
case .nullary:
return joinedSynopsisString
}
Expand All @@ -88,15 +87,17 @@ extension ArgumentDefinition {
return ""
}
}
var unadornedSynopsis: String? {

var unadornedSynopsis: String {
switch kind {
case .named:
guard let name = preferredNameForSynopsis else { return nil }

guard let name = names.preferredName else {
fatalError("preferredName cannot be nil for named arguments")
}

switch update {
case .unary:
return "\(name.synopsisString) <\(synopsisValueName ?? "value")>"
return "\(name.synopsisString) <\(valueName)>"
case .nullary:
return name.synopsisString
}
Expand All @@ -106,34 +107,16 @@ extension ArgumentDefinition {
return ""
}
}

var synopsis: String? {
guard help.shouldDisplay else { return nil }

guard !help.options.contains(.isOptional) else {
var n = self
n.help.options.remove(.isOptional)
return n.synopsis.flatMap { "[\($0)]" }

var synopsis: String {
var synopsis = unadornedSynopsis
if help.options.contains(.isRepeating) {
synopsis += " ..."
}
guard !help.options.contains(.isRepeating) else {
var n = self
n.help.options.remove(.isRepeating)
return n.synopsis.flatMap { "\($0) ..." }
if help.options.contains(.isOptional) {
synopsis = "[\(synopsis)]"
}

return unadornedSynopsis
}

var partitionedNames: [Name] {
return names.filter{ $0.isShort } + names.filter{ !$0.isShort }
}

var preferredNameForSynopsis: Name? {
names.first { !$0.isShort } ?? names.first
}

var synopsisValueName: String? {
valueName
return synopsis
}
}

Expand Down Expand Up @@ -241,27 +224,20 @@ extension ErrorMessageGenerator {

extension ErrorMessageGenerator {
func arguments(for key: InputKey) -> [ArgumentDefinition] {
return arguments
.filter {
$0.help.keys.contains(key)
}
arguments
.filter { $0.help.keys.contains(key) }
}

func help(for key: InputKey) -> ArgumentDefinition.Help? {
return arguments
arguments
.first { $0.help.keys.contains(key) }
.map { $0.help }
}

func valueName(for name: Name) -> String? {
for arg in arguments {
guard
arg.names.contains(name),
let v = arg.synopsisValueName
else { continue }
return v
}
return nil
arguments
.first { $0.names.contains(name) }
.map { $0.valueName }
}
}

Expand Down Expand Up @@ -313,7 +289,7 @@ extension ErrorMessageGenerator {
})

if let suggestion = suggestion {
return "Unknown option '\(name.synopsisString)'. Did you mean '\(suggestion.synopsisString)'?"
return "Unknown option '\(name.synopsisString)'. Did you mean '\(suggestion.synopsisString)'?"
}
return "Unknown option '\(name.synopsisString)'"
}
Expand Down Expand Up @@ -363,8 +339,10 @@ extension ErrorMessageGenerator {

func noValueMessage(key: InputKey) -> String? {
let args = arguments(for: key)
let possibilities = args.compactMap {
$0.nonOptional.synopsis
let possibilities: [String] = args.compactMap {
$0.help.shouldDisplay
? $0.nonOptional.synopsis
: nil
}
switch possibilities.count {
case 0:
Expand Down