Skip to content

Commit

Permalink
Merge pull request #117 from nekrich/feature/enumsAsArguments
Browse files Browse the repository at this point in the history
Strict arguments values support.
  • Loading branch information
mdiep authored Nov 29, 2017
2 parents fdb28de + 3fd4b4c commit e6ad5d0
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 1 deletion.
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,
])

0 comments on commit e6ad5d0

Please sign in to comment.