Skip to content
New issue

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

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

Already on GitHub? # to your account

Strict arguments values support. #117

Merged
merged 2 commits into from
Nov 29, 2017
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
6 changes: 5 additions & 1 deletion Commandant.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
680759651FC5A25D00D6CA70 /* OptionsWithEnumProtocolSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 680759631FC5A1F600D6CA70 /* OptionsWithEnumProtocolSpec.swift */; };
CD2ED3411C1E6C5D0076092B /* Argument.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD2ED3401C1E6C5D0076092B /* Argument.swift */; };
CD2ED3431C1E6D540076092B /* ArgumentProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD2ED3421C1E6D540076092B /* ArgumentProtocol.swift */; };
CDCE78341FBAB047005A9F76 /* OrderedSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDCE78331FBAB047005A9F76 /* OrderedSet.swift */; };
Expand Down Expand Up @@ -37,6 +38,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
680759631FC5A1F600D6CA70 /* OptionsWithEnumProtocolSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsWithEnumProtocolSpec.swift; sourceTree = "<group>"; };
6CAD549C1D371A4E00A2D031 /* LinuxMain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LinuxMain.swift; path = Tests/LinuxMain.swift; sourceTree = SOURCE_ROOT; };
CD2ED3401C1E6C5D0076092B /* Argument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Argument.swift; sourceTree = "<group>"; };
CD2ED3421C1E6D540076092B /* ArgumentProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArgumentProtocol.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -146,6 +148,7 @@
children = (
CDFC88351C3C0612003AC8F8 /* CommandSpec.swift */,
D00CCE281A20741C00109F8C /* OptionSpec.swift */,
680759631FC5A1F600D6CA70 /* OptionsWithEnumProtocolSpec.swift */,
CDCE78351FBAB0A2005A9F76 /* OrderedSetSpec.swift */,
D00CCDE91A20717400109F8C /* Supporting Files */,
6CAD549C1D371A4E00A2D031 /* LinuxMain.swift */,
Expand Down Expand Up @@ -381,8 +384,9 @@
buildActionMask = 2147483647;
files = (
CDFC88361C3C0612003AC8F8 /* CommandSpec.swift in Sources */,
CDCE78361FBAB0A2005A9F76 /* OrderedSetSpec.swift in Sources */,
D00CCE291A20741C00109F8C /* OptionSpec.swift in Sources */,
680759651FC5A25D00D6CA70 /* OptionsWithEnumProtocolSpec.swift in Sources */,
CDCE78361FBAB0A2005A9F76 /* OrderedSetSpec.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
30 changes: 30 additions & 0 deletions Sources/Commandant/ArgumentProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,33 @@ extension String: ArgumentProtocol {
return string
}
}

public extension RawRepresentable where Self.RawValue: StringProtocol, Self: ArgumentProtocol {

public static func from(string: String) -> Self? {

guard let stringValue = Self.RawValue(string)
else {
return .none
}

return Self(rawValue: stringValue)

}

}

public extension RawRepresentable where Self.RawValue: FixedWidthInteger, Self: ArgumentProtocol {

public static func from(string: String) -> Self? {

guard let intValue = Self.RawValue(string)
else {
return .none
}

return Self(rawValue: intValue)

}

}
183 changes: 183 additions & 0 deletions Tests/CommandantTests/OptionsWithEnumProtocolSpec.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
//
// OptionsWithEnumProtocolSpec.swift
// Commandant
//
// Created by Vitalii Budnik on 11/22/17.
// Copyright © 2017 Carthage. All rights reserved.
//

@testable import Commandant
import Foundation
import Nimble
import Quick
import Result

class OptionsWithEnumProtocolSpec: QuickSpec {
override func spec() {
describe("CommandMode.Arguments") {
func tryArguments(_ arguments: String...) -> Result<TestEnumOptions, CommandantError<NoError>> {
return TestEnumOptions.evaluate(.arguments(ArgumentParser(arguments)))
}

it("should fail if a required argument is missing") {
expect(tryArguments().value).to(beNil())
}

it("should fail if an option is missing a value") {
expect(tryArguments("required", "--strictIntValue").value).to(beNil())
}

it("should fail if an option is missing a value") {
expect(tryArguments("required", "--strictStringValue", "drop").value).to(beNil())
}

it("should fail if an optional strict int parameter is wrong") {
expect(tryArguments("required", "256").value).to(beNil())
}

it("should succeed without optional string arguments") {
let value = tryArguments("required").value
let expected = TestEnumOptions(strictIntValue: .theAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything, strictStringValue: .foobar, strictStringsArray: [], optionalStrictStringsArray: nil, optionalStrictStringValue: nil, optionalStrictInt: .min, requiredName: "required", arguments: [])
expect(value).to(equal(expected))
}

it("should succeed without optional strict int value") {
let value = tryArguments("required", "5").value
let expected = TestEnumOptions(strictIntValue: .theAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything, strictStringValue: .foobar, strictStringsArray: [], optionalStrictStringsArray: nil, optionalStrictStringValue: nil, optionalStrictInt: .giveFive, requiredName: "required", arguments: [])
expect(value).to(equal(expected))
}

it("should succeed with some strings array arguments separated by comma") {
let value = tryArguments("required", "--strictIntValue", "3", "--optionalStrictStringValue", "baz", "255", "--strictStringsArray", "a,b,c").value
let expected = TestEnumOptions(strictIntValue: .three, strictStringValue: .foobar, strictStringsArray: [.a, .b, .c], optionalStrictStringsArray: nil, optionalStrictStringValue: .baz, optionalStrictInt: .max, requiredName: "required", arguments: [])
expect(value).to(equal(expected))
}

it("should succeed with some strings array arguments separated by space") {
let value = tryArguments("required", "--strictIntValue", "3", "--optionalStrictStringValue", "baz", "--strictStringsArray", "a b c", "255").value
let expected = TestEnumOptions(strictIntValue: .three, strictStringValue: .foobar, strictStringsArray: [.a, .b, .c], optionalStrictStringsArray: nil, optionalStrictStringValue: .baz, optionalStrictInt: .max, requiredName: "required", arguments: [])
expect(value).to(equal(expected))
}

it("should succeed with some strings array arguments separated by comma and space") {
let value = tryArguments("required", "--strictIntValue", "3", "--optionalStrictStringValue", "baz", "--strictStringsArray", "a, b, c", "255").value
let expected = TestEnumOptions(strictIntValue: .three, strictStringValue: .foobar, strictStringsArray: [.a, .b, .c], optionalStrictStringsArray: nil, optionalStrictStringValue: .baz, optionalStrictInt: .max, requiredName: "required", arguments: [])
expect(value).to(equal(expected))
}

it("should succeed with some optional string arguments") {
let value = tryArguments("required", "--strictIntValue", "3", "--optionalStrictStringValue", "baz", "255").value
let expected = TestEnumOptions(strictIntValue: .three, strictStringValue: .foobar, strictStringsArray: [], optionalStrictStringsArray: nil, optionalStrictStringValue: .baz, optionalStrictInt: .max, requiredName: "required", arguments: [])
expect(value).to(equal(expected))
}

it("should succeed without optional array arguments") {
let value = tryArguments("required").value
let expected = TestEnumOptions(strictIntValue: .theAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything, strictStringValue: .foobar, strictStringsArray: [], optionalStrictStringsArray: nil, optionalStrictStringValue: nil, optionalStrictInt: .min, requiredName: "required", arguments: [])
expect(value).to(equal(expected))
}

it("should succeed with some optional array arguments") {
let value = tryArguments("required", "--strictIntValue", "3", "--optionalStrictStringsArray", "one, two", "255").value
let expected = TestEnumOptions(strictIntValue: .three, strictStringValue: .foobar, strictStringsArray: [], optionalStrictStringsArray: [.one, .two], optionalStrictStringValue: nil, optionalStrictInt: .max, requiredName: "required", arguments: [])
expect(value).to(equal(expected))
}

it("should override previous optional arguments") {
let value = tryArguments("required", "--strictIntValue", "3", "--strictStringValue", "fuzzbuzz", "--strictIntValue", "5", "--strictStringValue", "bazbuzz").value
let expected = TestEnumOptions(strictIntValue: .giveFive, strictStringValue: .bazbuzz, strictStringsArray: [], optionalStrictStringsArray: nil, optionalStrictStringValue: nil, optionalStrictInt: .min, requiredName: "required", arguments: [])
expect(value).to(equal(expected))
}

it("should consume the rest of positional arguments") {
let value = tryArguments("required", "255", "value1", "value2").value
let expected = TestEnumOptions(strictIntValue: .theAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything, strictStringValue: .foobar, strictStringsArray: [], optionalStrictStringsArray: nil, optionalStrictStringValue: nil, optionalStrictInt: .max, requiredName: "required", arguments: [ "value1", "value2" ])
expect(value).to(equal(expected))
}

it("should treat -- as the end of valued options") {
let value = tryArguments("--", "--strictIntValue").value
let expected = TestEnumOptions(strictIntValue: .theAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything, strictStringValue: .foobar, strictStringsArray: [], optionalStrictStringsArray: nil, optionalStrictStringValue: nil, optionalStrictInt: .min, requiredName: "--strictIntValue", arguments: [])
expect(value).to(equal(expected))
}
}

describe("CommandMode.Usage") {
it("should return an error containing usage information") {
let error = TestEnumOptions.evaluate(.usage).error
expect(error?.description).to(contain("strictIntValue"))
expect(error?.description).to(contain("strictStringValue"))
expect(error?.description).to(contain("name you're required to"))
expect(error?.description).to(contain("optionally specify"))
}
}
}
}

struct TestEnumOptions: OptionsProtocol, Equatable {
let strictIntValue: StrictIntValue
let strictStringValue: StrictStringValue
let strictStringsArray: [StrictStringValue]
let optionalStrictStringsArray: [StrictStringValue]?
let optionalStrictStringValue: StrictStringValue?
let optionalStrictInt: StrictIntValue
let requiredName: String
let arguments: [String]

typealias ClientError = NoError

static func create(_ a: StrictIntValue) -> (StrictStringValue) -> ([StrictStringValue]) -> ([StrictStringValue]?) -> (StrictStringValue?) -> (String) -> (StrictIntValue) -> ([String]) -> TestEnumOptions {
return { b in { c in { d in { e in { f in { g in { h in
return self.init(strictIntValue: a, strictStringValue: b, strictStringsArray: c, optionalStrictStringsArray: d, optionalStrictStringValue: e, optionalStrictInt: g, requiredName: f, arguments: h)
} } } } } } }
}

static func evaluate(_ m: CommandMode) -> Result<TestEnumOptions, CommandantError<NoError>> {
return create
<*> m <| Option(key: "strictIntValue", defaultValue: .theAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything, usage: "`0` - zero, `255` - max, `3` - three, `5` - five or `42` - The Answer")
<*> m <| Option(key: "strictStringValue", defaultValue: .foobar, usage: "`foobar`, `bazbuzzz`, `a`, `b`, `c`, `one`, `two`, `c`")
<*> m <| Option<[StrictStringValue]>(key: "strictStringsArray", defaultValue: [], usage: "Some array of arguments")
<*> m <| Option<[StrictStringValue]?>(key: "optionalStrictStringsArray", defaultValue: nil, usage: "Some array of arguments")
<*> m <| Option<StrictStringValue?>(key: "optionalStrictStringValue", defaultValue: nil, usage: "Some string value")
<*> m <| Argument(usage: "A name you're required to specify")
<*> m <| Argument(defaultValue: .min, usage: "A number that you can optionally specify")
<*> m <| Argument(defaultValue: [], usage: "An argument list that consumes the rest of positional arguments")
}
}

func ==(lhs: TestEnumOptions, rhs: TestEnumOptions) -> Bool {
return lhs.strictIntValue == rhs.strictIntValue && lhs.strictStringValue == rhs.strictStringValue && lhs.strictStringsArray == rhs.strictStringsArray && lhs.optionalStrictStringsArray == rhs.optionalStrictStringsArray && lhs.optionalStrictStringValue == rhs.optionalStrictStringValue && lhs.optionalStrictInt == rhs.optionalStrictInt && lhs.requiredName == rhs.requiredName && lhs.arguments == rhs.arguments
}

extension TestEnumOptions: CustomStringConvertible {
var description: String {
return "{ strictIntValue: \(strictIntValue), strictStringValue: \(strictStringValue), strictStringsArray: \(strictStringsArray), optionalStrictStringsArray: \(String(describing: optionalStrictStringsArray)), optionalStrictStringValue: \(String(describing: optionalStrictStringValue)), optionalStrictInt: \(optionalStrictInt), requiredName: \(requiredName), arguments: \(arguments) }"
}
}

enum StrictStringValue: String, ArgumentProtocol {

static var name: String = "Strict string value: `foobar`, `bazbuzz`, `one`, `two`, `baz`, `a`, `b` or `c`"

case foobar
case bazbuzz
case one
case two
case baz
case a
case b
case c

}

enum StrictIntValue: UInt8, ArgumentProtocol {

static var name: String = "Strict int value: `3`, `5`, `42`, `0`, `255`"

case min = 0
case three = 3
case giveFive = 5
case theAnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything = 42
case max = 255

}
1 change: 1 addition & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ import Quick
Quick.QCKMain([
CommandWrapperSpec.self,
OptionsProtocolSpec.self,
OptionsWithEnumProtocolSpec.self,
OrderedSetSpec.self,
])