Clean up help generation (#385)

- Adds check to ensure that `wrapped(to:wrappingIndent:)` doesn't
  attempt to retrieve a negative prefix.
- The Usage struct was composed of an array of strings which always
  contained exact one string at runtime. This struct has been removed
  and replaced with a single usage string.
- Removes HelpGenerator._screenWidthOverride in favor of explicitly
  setting the screen width in generateHelp calls.
This commit is contained in:
Rauhul Varma
2022-01-10 16:02:50 -06:00
committed by GitHub
parent df75d7009e
commit 00a86771ca
4 changed files with 26 additions and 35 deletions
@@ -33,9 +33,10 @@ struct HelpCommand: ParsableCommand {
mutating func buildCommandStack(with parser: CommandParser) throws {
commandStack = parser.commandStack(for: subcommands)
}
func generateHelp() -> String {
return HelpGenerator(commandStack: commandStack).rendered()
/// Used for testing.
func generateHelp(screenWidth: Int) -> String {
HelpGenerator(commandStack: commandStack).rendered(screenWidth: screenWidth)
}
enum CodingKeys: CodingKey {
@@ -12,21 +12,8 @@
internal struct HelpGenerator {
static var helpIndent = 2
static var labelColumnWidth = 26
static var systemScreenWidth: Int {
_screenWidthOverride ?? _terminalSize().width
}
internal static var _screenWidthOverride: Int? = nil
struct Usage {
var components: [String]
func rendered(screenWidth: Int) -> String {
components
.joined(separator: "\n")
}
}
static var systemScreenWidth: Int { _terminalSize().width }
struct Section {
struct Element: Hashable {
var label: String
@@ -98,7 +85,7 @@ internal struct HelpGenerator {
var commandStack: [ParsableCommand.Type]
var abstract: String
var usage: Usage
var usage: String
var sections: [Section]
var discussionSections: [DiscussionSection]
@@ -116,10 +103,10 @@ internal struct HelpGenerator {
toolName = "\(superName) \(toolName)"
}
var usageString = UsageGenerator(toolName: toolName, definition: [currentArgSet]).synopsis
var usage = UsageGenerator(toolName: toolName, definition: [currentArgSet]).synopsis
if !currentCommand.configuration.subcommands.isEmpty {
if usageString.last != " " { usageString += " " }
usageString += "<subcommand>"
if usage.last != " " { usage += " " }
usage += "<subcommand>"
}
self.abstract = currentCommand.configuration.abstract
@@ -130,7 +117,7 @@ internal struct HelpGenerator {
self.abstract += "\n\(currentCommand.configuration.discussion)"
}
self.usage = Usage(components: [usageString])
self.usage = usage
self.sections = HelpGenerator.generateSections(commandStack: commandStack)
self.discussionSections = []
}
@@ -222,9 +209,8 @@ internal struct HelpGenerator {
]
}
func usageMessage(screenWidth: Int? = nil) -> String {
let screenWidth = screenWidth ?? HelpGenerator.systemScreenWidth
return "Usage: \(usage.rendered(screenWidth: screenWidth))"
func usageMessage() -> String {
return "Usage: \(usage)"
}
var includesSubcommands: Bool {
@@ -243,7 +229,7 @@ internal struct HelpGenerator {
? ""
: "OVERVIEW: \(abstract)".wrapped(to: screenWidth) + "\n\n"
var helpSubcommandMessage: String = ""
var helpSubcommandMessage = ""
if includesSubcommands {
var names = commandStack.map { $0._commandName }
if let superName = commandStack.first!.configuration._superCommandName {
@@ -259,7 +245,7 @@ internal struct HelpGenerator {
return """
\(renderedAbstract)\
USAGE: \(usage.rendered(screenWidth: screenWidth))
USAGE: \(usage)
\(renderedSections)\(helpSubcommandMessage)
"""
@@ -12,6 +12,13 @@
extension String {
func wrapped(to columns: Int, wrappingIndent: Int = 0) -> String {
let columns = columns - wrappingIndent
guard columns > 0 else {
// Skip wrapping logic if the number of columns is less than 1 in release
// builds and assert in debug builds.
assertionFailure("`columns - wrappingIndent` should be always be greater than 0.")
return ""
}
var result: [Substring] = []
var currentIndex = startIndex
@@ -26,11 +26,11 @@ func getErrorText<T: ParsableArguments>(_: T.Type, _ arguments: [String]) -> Str
}
}
func getErrorText<T: ParsableCommand>(_: T.Type, _ arguments: [String]) -> String {
func getErrorText<T: ParsableCommand>(_: T.Type, _ arguments: [String], screenWidth: Int) -> String {
do {
let command = try T.parseAsRoot(arguments)
if let helpCommand = command as? HelpCommand {
return helpCommand.generateHelp()
return helpCommand.generateHelp(screenWidth: screenWidth)
} else {
XCTFail("Didn't generate a help error")
return ""
@@ -90,7 +90,7 @@ extension HelpTests {
func testConfigHelp() throws {
XCTAssertEqual(
getErrorText(Package.self, ["help", "config"]).trimmingLines(),
getErrorText(Package.self, ["help", "config"], screenWidth: 80).trimmingLines(),
"""
USAGE: package config <subcommand>
@@ -107,11 +107,8 @@ extension HelpTests {
}
func testGetMirrorHelp() throws {
HelpGenerator._screenWidthOverride = 80
defer { HelpGenerator._screenWidthOverride = nil }
XCTAssertEqual(
getErrorText(Package.self, ["help", "config", "get-mirror"]).trimmingLines(),
getErrorText(Package.self, ["help", "config", "get-mirror"], screenWidth: 80).trimmingLines(),
"""
USAGE: package config get-mirror [<options>] --package-url <package-url>