Navigation Menu

Skip to content

Commit

Permalink
Merge pull request #63 from vapor/dep-flag-config
Browse files Browse the repository at this point in the history
Dep flag config
  • Loading branch information
tanner0101 committed Mar 27, 2018
2 parents a69a7ac + 66680eb commit 273cf2e
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 17 deletions.
79 changes: 79 additions & 0 deletions Sources/Command/CommandConfig.swift
@@ -0,0 +1,79 @@
import Service

/// Configures commands for a service container.
///
/// var commandConfig = CommandConfig.default()
/// /// You can register command types that will be lazily created
/// commandConfig.use(FooCommand.self, as: "foo")
/// /// You can also register pre-initialized instances of a command
/// commandConfig.use(barCommand, as: "bar")
/// services.register(commandConfig)
///
public struct CommandConfig: Service {
/// A lazily initialized `CommandRunnable`.
public typealias LazyCommand = (Container) throws -> CommandRunnable

/// Internal storage
private var commands: [String: LazyCommand]

/// The default runnable
private var defaultCommand: LazyCommand?

/// Create a new `CommandConfig`.
public init() {
self.commands = [:]
}

/// Adds a `Command` instance to the config.
///
/// var commandConfig = CommandConfig.default()
/// commandConfig.use(barCommand, as: "bar")
/// services.register(commandConfig)
///
/// - parameters:
/// - command: Some `CommandRunnable`. This type will be requested from the service container later.
/// - name: A unique name for running this command.
/// - isDefault: If `true`, this command will be set as the default command to run when none other are specified.
/// Setting this overrides any previous default commands.
public mutating func use(_ command: CommandRunnable, as name: String, isDefault: Bool = false) {
commands[name] = { _ in command }
if isDefault {
defaultCommand = { _ in command }
}
}

/// Adds a `CommandRunnable` type to the config. This type will be lazily initialized later using a `Container`.
///
/// var commandConfig = CommandConfig.default()
/// commandConfig.use(FooCommand.self, as: "foo")
/// services.register(commandConfig)
///
/// - parameters:
/// - command: `Type` of some `Command`. This type will be requested from the service container later.
/// - name: A unique name for running this command.
/// - isDefault: If `true`, this command will be set as the default command to run when none other are specified.
/// Setting this overrides any previous default commands.
public mutating func use<R>(_ command: R.Type, as name: String, isDefault: Bool = false) where R: CommandRunnable {
commands[name] = { try $0.make(R.self) }
if isDefault {
defaultCommand = { try $0.make(R.self) }
}
}

/// Resolves the configured commands to a `ConfiguredCommands` struct.
///
/// - parameters:
/// - container: `Container` to use for creating lazily initialized commands.
/// - returns: `Commands` struct which contains initialized commands.
/// - throws: Errors creating the lazy commands from the container.
public func resolve(for container: Container) throws -> ConfiguredCommands {
let commands = try self.commands.mapValues { lazy -> CommandRunnable in
return try lazy(container)
}

return try .init(
commands: commands,
defaultCommand: defaultCommand.flatMap { try $0(container) }
)
}
}
37 changes: 27 additions & 10 deletions Sources/Command/CommandInput.swift
Expand Up @@ -28,11 +28,21 @@ public struct CommandInput {
for (i, arg) in arguments.enumerated() {
guard var arg = arg else { continue }

if arg.hasPrefix("--") {
var deprecatedFlagFormat = false

if arg.hasPrefix("--\(option.name)=") {
// deprecated option support
print("[Deprecated] --option=value syntax is deprecated. Please use --option value (with no =) instead.")
deprecatedFlagFormat = true

// remove this option from the command input
arguments[i] = nil
} else if arg.hasPrefix("--") {
// check if option matches
guard arg == "--\(option.name)" else {
continue
}

// remove this option from the command input
arguments[i] = nil
} else if let short = option.short, arg.hasPrefix("-") {
Expand Down Expand Up @@ -61,20 +71,27 @@ public struct CommandInput {
case .value(let d):
let supplied: String?

// check if the next arg is available
if i + 1 < arguments.count {
let next = arguments[i + 1]
// ensure it's non-nil and not an option
if next?.hasPrefix("-") == false {
supplied = next
arguments[i + 1] = nil
if deprecatedFlagFormat {
// parse --option=flag syntax
let parts = arg.split(separator: "=", maxSplits: 2, omittingEmptySubsequences: false)
supplied = String(parts[1])
} else {
// check if the next arg is available
if i + 1 < arguments.count {
let next = arguments[i + 1]
// ensure it's non-nil and not an option
if next?.hasPrefix("-") == false {
supplied = next
arguments[i + 1] = nil
} else {
supplied = nil
}
} else {
supplied = nil
}
} else {
supplied = nil
}


// value options need either a supplied value or a default
guard let value = supplied ?? d else {
throw CommandError(
Expand Down
20 changes: 20 additions & 0 deletions Sources/Command/ConfiguredCommands.swift
@@ -0,0 +1,20 @@
import Service

/// Represents a top-level group of configured commands. This is usually created by calling `resolvle(for:)` on `CommandConfig`.
public struct ConfiguredCommands: Service {
/// Top-level available commands, stored by unique name.
public let commands: [String: CommandRunnable]

/// If set, this is the default top-level command that should run if no other commands are specified.
public let defaultCommand: CommandRunnable?

/// Creates a new `ConfiguredCommands` struct. This is usually done by calling `resolvle(for:)` on `CommandConfig`.
///
/// - parameters:
/// - commands: Top-level available commands, stored by unique name.
/// - defaultCommand: If set, this is the default top-level command that should run if no other commands are specified.
public init(commands: [String: CommandRunnable] = [:], defaultCommand: CommandRunnable? = nil) {
self.commands = commands
self.defaultCommand = defaultCommand
}
}
6 changes: 5 additions & 1 deletion Sources/Command/Output+Help.swift
Expand Up @@ -16,7 +16,11 @@ extension OutputConsole {
}

for opt in runnable.options {
success("[--" + opt.name + "] ", newLine: false)
if let short = opt.short {
success("[--\(opt.name),-\(short)] ", newLine: false)
} else {
success("[--\(opt.name)] ", newLine: false)
}
}
print()
print()
Expand Down
2 changes: 1 addition & 1 deletion Sources/Console/Console/Extensions/Console+Confirm.swift
@@ -1,7 +1,7 @@
extension OutputConsole where Self: InputConsole {
/// Requests yes/no confirmation from
/// the console.
public func confirm(_ prompt: String, style: ConsoleStyle = .info) throws -> Bool {
public func confirm(_ prompt: String, style: ConsoleStyle = .info) -> Bool {
var i = 0
var result = ""
while result != "y" && result != "yes" && result != "n" && result != "no" {
Expand Down
59 changes: 56 additions & 3 deletions Tests/CommandTests/CommandTests.swift
Expand Up @@ -5,15 +5,68 @@ import Service
import XCTest

class CommandTests: XCTestCase {
func testExample() throws {
let console = Terminal()
func testHelp() throws {
let console = TestConsole()
let group = TestGroup()
let container = BasicContainer(config: .init(), environment: .testing, services: .init(), on: EmbeddedEventLoop())
var input = CommandInput(arguments: ["vapor", "sub", "test", "--help"])
try console.run(group, input: &input, on: container).wait()
XCTAssertEqual(console.testOutputQueue.reversed().joined(separator: ""), """
Usage: vapor sub test <foo> [--bar,-b]\u{20}
This is a test command
Arguments:
foo A foo is required
An error will occur if none exists
Options:
bar Add a bar if you so desire
Try passing it
""")
}

func testFlag() throws {
let console = TestConsole()
let group = TestGroup()
let container = BasicContainer(config: .init(), environment: .testing, services: .init(), on: EmbeddedEventLoop())
var input = CommandInput(arguments: ["vapor", "sub", "test", "foovalue", "--bar", "baz"])
try console.run(group, input: &input, on: container).wait()
XCTAssertEqual(console.testOutputQueue.reversed().joined(separator: ""), """
Foo: foovalue Bar: baz
""")
}

func testShortFlag() throws {
let console = TestConsole()
let group = TestGroup()
let container = BasicContainer(config: .init(), environment: .testing, services: .init(), on: EmbeddedEventLoop())
var input = CommandInput(arguments: ["vapor", "sub", "test", "foovalue", "-b", "baz"])
try console.run(group, input: &input, on: container).wait()
XCTAssertEqual(console.testOutputQueue.reversed().joined(separator: ""), """
Foo: foovalue Bar: baz
""")
}

func testDeprecatedFlag() throws {
let console = TestConsole()
let group = TestGroup()
let container = BasicContainer(config: .init(), environment: .testing, services: .init(), on: EmbeddedEventLoop())
var input = CommandInput(arguments: ["vapor", "sub", "test", "foovalue", "--bar=baz"])
try console.run(group, input: &input, on: container).wait()
XCTAssertEqual(console.testOutputQueue.reversed().joined(separator: ""), """
Foo: foovalue Bar: baz
""")
}

static var allTests = [
("testExample", testExample),
("testHelp", testHelp),
("testFlag", testFlag),
("testShortFlag", testShortFlag),
("testDeprecatedFlag", testDeprecatedFlag),
]
}
38 changes: 37 additions & 1 deletion Tests/CommandTests/Utilities.swift
@@ -1,6 +1,7 @@
import Async
import Console
import Command
import Service

extension String: Error {}

Expand Down Expand Up @@ -56,7 +57,7 @@ final class TestCommand: Command {
]

let options: [CommandOption] = [
.value(name: "bar", help: ["Add a bar if you so desire", "Try passing it"])
.value(name: "bar", short: "b", help: ["Add a bar if you so desire", "Try passing it"])
]

let help = ["This is a test command"]
Expand All @@ -68,3 +69,38 @@ final class TestCommand: Command {
return .done(on: context.container)
}
}

final class TestConsole: Console {
var testInputQueue: [String]
var testOutputQueue: [String]
var extend: Extend

init() {
self.testInputQueue = []
self.testOutputQueue = []
self.extend = [:]
}

func input(isSecure: Bool) -> String {
return testInputQueue.popLast() ?? ""
}

func output(_ string: String, style: ConsoleStyle, newLine: Bool) {
testOutputQueue.insert(string + (newLine ? "\n" : ""), at: 0)
}

func report(error: String, newLine: Bool) {
//
}

func clear(_ type: ConsoleClear) {
//
}

func execute(program: String, arguments: [String], input: ExecuteStream?, output: ExecuteStream?, error: ExecuteStream?) throws {
//
}

var size: (width: Int, height: Int) { return (0, 0) }
}

2 changes: 1 addition & 1 deletion Tests/ConsoleTests/ConsoleTests.swift
Expand Up @@ -24,7 +24,7 @@ class ConsoleTests: XCTestCase {

console.input = name

let response = try console.confirm(question)
let response = console.confirm(question)

XCTAssertEqual(response, true)
XCTAssertEqual(console.output, question + "\ny/n> ")
Expand Down

0 comments on commit 273cf2e

Please sign in to comment.