Skip to content
This repository was archived by the owner on Dec 29, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
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
60 changes: 49 additions & 11 deletions CommandLine/CommandLine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ let ArgumentStopper = "--"
*/
let ArgumentAttacher: Character = "="

/* An output stream to stderr; used by CommandLine.printUsage(). */
private struct StderrOutputStream: OutputStreamType {
static let stream = StderrOutputStream()
func write(s: String) {
fputs(s, stderr)
}
}

/**
* The CommandLine class implements a command-line interface for your app.
*
Expand Down Expand Up @@ -242,30 +250,60 @@ public class CommandLine {
}
}

/* printUsage() is generic for OutputStreamType because the Swift compiler crashes
* on inout protocol function parameters in Xcode 7 beta 1 (rdar://21372694).
*/

/**
* Prints a usage message to stdout.
* Prints a usage message.
*
* - parameter error: An optional error thrown from `parse()`. A description of the error
* (e.g. "Missing required option --extract") will be printed before the usage message.
* - parameter to: An OutputStreamType to write the error message to.
*/
public func printUsage(error: ErrorType? = nil) {
if let err = error as? ParseError {
print("\(err)\n")
}

public func printUsage<TargetStream: OutputStreamType>(inout to: TargetStream) {
let name = _arguments[0]

var flagWidth = 0
for opt in _options {
flagWidth = max(flagWidth,
" \(ShortOptionPrefix)\(opt.shortFlag), \(LongOptionPrefix)\(opt.longFlag):".characters.count)
}
print("Usage: \(name) [options]")

print("Usage: \(name) [options]", &to)
for opt in _options {
let flags = " \(ShortOptionPrefix)\(opt.shortFlag), \(LongOptionPrefix)\(opt.longFlag):".paddedToWidth(flagWidth)

print("\(flags)\n \(opt.helpMessage)")
print("\(flags)\n \(opt.helpMessage)", &to)
}
}

/**
* Prints a usage message.
*
* - parameter error: An error thrown from `parse()`. A description of the error
* (e.g. "Missing required option --extract") will be printed before the usage message.
* - parameter to: An OutputStreamType to write the error message to.
*/
public func printUsage<TargetStream: OutputStreamType>(error: ErrorType, inout to: TargetStream) {
print("\(error)\n", &to)
printUsage(&to)
}

/**
* Prints a usage message.
*
* - parameter error: An error thrown from `parse()`. A description of the error
* (e.g. "Missing required option --extract") will be printed before the usage message.
*/
public func printUsage(error: ErrorType) {
var out = StderrOutputStream.stream
printUsage(error, to: &out)
}

/**
* Prints a usage message.
*/
public func printUsage() {
var out = StderrOutputStream.stream
printUsage(&out)
}
}
70 changes: 68 additions & 2 deletions CommandLineTests/CommandLineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/

import XCTest
import CommandLine

internal class CommandLineTests: XCTestCase {

Expand Down Expand Up @@ -512,7 +511,6 @@ internal class CommandLineTests: XCTestCase {
"-f", "45", "-p", "0.05", "-x", "extra1", "extra2", "extra3" ])

let boolOpt = BoolOption(shortFlag: "d", longFlag: "debug", helpMessage: "Enables debug mode.")

let counterOpt = CounterOption(shortFlag: "v", longFlag: "verbose",
helpMessage: "Enables verbose output. Specify multiple times for extra verbosity.")
let stringOpt = StringOption(shortFlag: "n", longFlag: "name", required: true,
Expand Down Expand Up @@ -559,4 +557,72 @@ internal class CommandLineTests: XCTestCase {
XCTFail("Unexpected parse error: \(error)")
}
}

func testPrintUsage() {
let cli = CommandLine(arguments: [ "CommandLineTests", "-dvvv", "--name", "John Q. Public",
"-f", "45", "-p", "0.05", "-x", "extra1", "extra2", "extra3" ])

let boolOpt = BoolOption(shortFlag: "d", longFlag: "debug", helpMessage: "Enables debug mode.")
let counterOpt = CounterOption(shortFlag: "v", longFlag: "verbose",
helpMessage: "Enables verbose output. Specify multiple times for extra verbosity.")
let stringOpt = StringOption(shortFlag: "n", longFlag: "name", required: true,
helpMessage: "Name a Cy Young winner.")
let intOpt = IntOption(shortFlag: "f", longFlag: "favorite", required: true,
helpMessage: "Your favorite number.")
let doubleOpt = DoubleOption(shortFlag: "p", longFlag: "p-value", required: true,
helpMessage: "P-value for test.")
let extraOpt = MultiStringOption(shortFlag: "x", longFlag: "Extra", required: true,
helpMessage: "X is for Extra.")

let opts = [boolOpt, counterOpt, stringOpt, intOpt, doubleOpt, extraOpt]
cli.addOptions(opts)

var out = ""
cli.printUsage(&out)
XCTAssertGreaterThan(out.characters.count, 0)

/* There should be at least 2 lines per option, plus the intro Usage statement */
XCTAssertGreaterThanOrEqual(out.splitByCharacter("\n").count, (opts.count * 2) + 1)
}

func testPrintUsageError() {
let cli = CommandLine(arguments: [ "CommandLineTests" ])
cli.addOption(StringOption(shortFlag: "n", longFlag: "name", required: true,
helpMessage: "Your name"))

do {
try cli.parse()
XCTFail("Didn't throw with missing required argument")
} catch {
var out = ""
cli.printUsage(error, to: &out)

let errorMessage = out.splitByCharacter("\n", maxSplits: 1)[0]
XCTAssertTrue(errorMessage.hasPrefix("Missing required"))
}
}

func testPrintUsageToStderr() {
let cli = CommandLine(arguments: [ "CommandLineTests" ])
cli.addOption(StringOption(shortFlag: "n", longFlag: "name", required: true,
helpMessage: "Your name"))

/* Toss stderr into /dev/null, so the printUsage() output doesn't pollute regular
* XCTest messages.
*/
let origStdErr = dup(fileno(stderr))
let null = fopen("/dev/null", "w")
dup2(fileno(null), fileno(stderr))

defer {
dup2(origStdErr, fileno(stderr))
fclose(null)
}

let error = CommandLine.ParseError.InvalidArgument("ack")

/* Just make sure these doesn't crash or throw */
cli.printUsage()
cli.printUsage(error)
}
}