From e11001aabf30078c005226410ac19df5d0af5750 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Wed, 7 Mar 2018 17:44:56 +0000 Subject: [PATCH 01/18] Re-enable the tests under SwiftPM. Since we can't use a bundle to find the test data, we fall back on a relative path from the built executable. This is a bit skanky, but it's a useable workaround. --- DocoptTests/DocoptTestCasesTests.swift | 13 ++++++++++++- Package.swift | 14 +++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/DocoptTests/DocoptTestCasesTests.swift b/DocoptTests/DocoptTestCasesTests.swift index ddaf89f..16f9027 100644 --- a/DocoptTests/DocoptTestCasesTests.swift +++ b/DocoptTests/DocoptTestCasesTests.swift @@ -61,7 +61,18 @@ class DocoptTestCasesTests: XCTestCase { private func fixturesFilePath() -> String? { let testBundle: Bundle = Bundle(for: type(of: self)) - return testBundle.path(forResource: "testcases", ofType: "docopt") + guard let path = testBundle.path(forResource: "testcases", ofType: "docopt") else { + // SwiftPM currently doesn't support bundles, so if the tests are run with it, + // we'll fail to find the bundle path here. + // As a temporary workaround, we can fall back on a relative path. This is fragile + // as it relies on the assumption that we know where SwiftPM will put the + // executable, and where the testcases file lives relative to it, but it's + // better than just disabling all the tests... + let url = testBundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("DocoptTests").appendingPathComponent("testcases.docopt") + return url.path + } + + return path } private func fixturesFileContents() -> String { diff --git a/Package.swift b/Package.swift index 4c38955..c038980 100644 --- a/Package.swift +++ b/Package.swift @@ -12,12 +12,12 @@ let package = Package( name: "Docopt", path: "Sources" ) - // Commented out until SPM supports resources - //, - // .testTarget( - // name: "DocoptTests", - // dependencies: ["Docopt"], - // path: "DocoptTests" - // ) +// Commented out until SPM supports resources + , + .testTarget( + name: "DocoptTests", + dependencies: ["Docopt"], + path: "DocoptTests" + ) ] ) From 5ecdb80f3b6efb550bbad476412ed1807e11065d Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Wed, 7 Mar 2018 17:47:15 +0000 Subject: [PATCH 02/18] Removed obsolete comment. --- Package.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index c038980..6a07509 100644 --- a/Package.swift +++ b/Package.swift @@ -11,9 +11,7 @@ let package = Package( .target( name: "Docopt", path: "Sources" - ) -// Commented out until SPM supports resources - , + ), .testTarget( name: "DocoptTests", dependencies: ["Docopt"], From a7709608141543b1b8021ea7aa908531acc37f79 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Wed, 7 Mar 2018 18:16:20 +0000 Subject: [PATCH 03/18] Fixed warnings caused by Swift 4 string changes. --- DocoptTests/DocoptTestCaseParser.swift | 2 +- Sources/Docopt.swift | 2 +- Sources/String.swift | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DocoptTests/DocoptTestCaseParser.swift b/DocoptTests/DocoptTestCaseParser.swift index 31ae1d5..4273ceb 100644 --- a/DocoptTests/DocoptTestCaseParser.swift +++ b/DocoptTests/DocoptTestCaseParser.swift @@ -26,7 +26,7 @@ public struct DocoptTestCaseParser { private func removeComments(string: String) -> String { let removeCommentsRegEx = try! NSRegularExpression(pattern: "(?m)#.*$", options: []) - let fullRange: NSRange = NSMakeRange(0, string.characters.count) + let fullRange: NSRange = NSMakeRange(0, string.count) return removeCommentsRegEx.stringByReplacingMatches(in: string, options: [], range: fullRange, withTemplate: "") } diff --git a/Sources/Docopt.swift b/Sources/Docopt.swift index 64ec5d7..392770a 100644 --- a/Sources/Docopt.swift +++ b/Sources/Docopt.swift @@ -177,7 +177,7 @@ open class Docopt : NSObject { let short = "-" + left[0..<1] let similar = options.filter {$0.short == short} var o: Option - left = left[1.. 1 { tokens.error.raise("\(short) is specified ambiguously \(similar.count) times") diff --git a/Sources/String.swift b/Sources/String.swift index 3455464..a3a0078 100644 --- a/Sources/String.swift +++ b/Sources/String.swift @@ -23,18 +23,18 @@ internal extension String { func findAll(_ regex: String, flags: NSRegularExpression.Options) -> [String] { let re = try! NSRegularExpression(pattern: regex, options: flags) - let all = NSMakeRange(0, self.characters.count) + let all = NSMakeRange(0, self.count) let matches = re.matches(in: self, options: [], range: all) return matches.map {self[$0.range(at: 1)].strip()} } func split() -> [String] { - return self.characters.split(whereSeparator: {$0 == " " || $0 == "\n"}).map(String.init) + return self.split(whereSeparator: {$0 == " " || $0 == "\n"}).map(String.init) } func split(_ regex: String) -> [String] { let re = try! NSRegularExpression(pattern: regex, options: .dotMatchesLineSeparators) - let all = NSMakeRange(0, self.characters.count) + let all = NSMakeRange(0, self.count) var result = [String]() let matches = re.matches(in: self, options: [], range: all) if matches.count > 0 { @@ -52,7 +52,7 @@ internal extension String { result.append(self[range]) lastEnd = range.location + range.length - if lastEnd == self.characters.count { + if lastEnd == self.count { // from python docs: If there are capturing groups in the separator and it matches at the start of the string, // the result will start with an empty string. The same holds for the end of the string: result.append("") @@ -62,8 +62,8 @@ internal extension String { lastEnd = match.range.location + match.range.length } } - if lastEnd != self.characters.count { - result.append(self[lastEnd..) -> String { - return String(self[characters.index(startIndex, offsetBy: range.lowerBound).. String { From 3600de01c1b9552c978b6f5ad00f53ce12b6ae56 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Thu, 8 Mar 2018 13:13:46 +0000 Subject: [PATCH 04/18] Remove unneeded Darwin imports --- Sources/Docopt.swift | 1 - Sources/DocoptError.swift | 1 - 2 files changed, 2 deletions(-) diff --git a/Sources/Docopt.swift b/Sources/Docopt.swift index 64ec5d7..a10a96b 100644 --- a/Sources/Docopt.swift +++ b/Sources/Docopt.swift @@ -7,7 +7,6 @@ // import Foundation -import Darwin @objc open class Docopt : NSObject { diff --git a/Sources/DocoptError.swift b/Sources/DocoptError.swift index 68962e2..2cc2f0f 100644 --- a/Sources/DocoptError.swift +++ b/Sources/DocoptError.swift @@ -7,7 +7,6 @@ // import Foundation -import Darwin internal class DocoptError { var message: String From 6f0055bb199bcbde8c57fe097d2bf702023d82f5 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Thu, 8 Mar 2018 14:07:04 +0000 Subject: [PATCH 05/18] removed uses of AnyObject and various NS classes --- Package.swift | 7 +- Sources/{ => Docopt}/Argument.swift | 0 Sources/{ => Docopt}/BranchPattern.swift | 0 Sources/{ => Docopt}/Command.swift | 2 +- Sources/{ => Docopt}/Docopt.h | 0 Sources/{ => Docopt}/Docopt.swift | 94 +++++----- Sources/{ => Docopt}/DocoptError.swift | 0 Sources/{ => Docopt}/Either.swift | 0 Sources/{ => Docopt}/Info.plist | 0 Sources/{ => Docopt}/LeafPattern.swift | 34 ++-- Sources/Docopt/LinuxMain.swift | 0 Sources/{ => Docopt}/OneOrMore.swift | 0 Sources/{ => Docopt}/Option.swift | 24 +-- Sources/{ => Docopt}/Optional.swift | 0 Sources/{ => Docopt}/OptionsShortcut.swift | 0 Sources/{ => Docopt}/Pattern.swift | 28 +-- Sources/{ => Docopt}/Required.swift | 0 Sources/{ => Docopt}/String.swift | 0 Sources/{ => Docopt}/Tokens.swift | 0 .../DocoptTests}/DocoptTestCase.swift | 10 +- .../DocoptTests}/DocoptTestCaseParser.swift | 38 ++-- .../DocoptTests}/DocoptTestCasesTests.swift | 18 +- .../DocoptTests}/DocoptTests.h | 0 .../DocoptTests}/DocoptTests.swift | 169 +++++++++--------- {DocoptTests => Tests/DocoptTests}/Info.plist | 0 .../DocoptTests}/testcases.docopt | 0 Tests/LinuxMain.swift | 6 + 27 files changed, 215 insertions(+), 215 deletions(-) rename Sources/{ => Docopt}/Argument.swift (100%) rename Sources/{ => Docopt}/BranchPattern.swift (100%) rename Sources/{ => Docopt}/Command.swift (88%) rename Sources/{ => Docopt}/Docopt.h (100%) rename Sources/{ => Docopt}/Docopt.swift (91%) rename Sources/{ => Docopt}/DocoptError.swift (100%) rename Sources/{ => Docopt}/Either.swift (100%) rename Sources/{ => Docopt}/Info.plist (100%) rename Sources/{ => Docopt}/LeafPattern.swift (89%) create mode 100644 Sources/Docopt/LinuxMain.swift rename Sources/{ => Docopt}/OneOrMore.swift (100%) rename Sources/{ => Docopt}/Option.swift (91%) rename Sources/{ => Docopt}/Optional.swift (100%) rename Sources/{ => Docopt}/OptionsShortcut.swift (100%) rename Sources/{ => Docopt}/Pattern.swift (94%) rename Sources/{ => Docopt}/Required.swift (100%) rename Sources/{ => Docopt}/String.swift (100%) rename Sources/{ => Docopt}/Tokens.swift (100%) rename {DocoptTests => Tests/DocoptTests}/DocoptTestCase.swift (86%) rename {DocoptTests => Tests/DocoptTests}/DocoptTestCaseParser.swift (95%) rename {DocoptTests => Tests/DocoptTests}/DocoptTestCasesTests.swift (94%) rename {DocoptTests => Tests/DocoptTests}/DocoptTests.h (100%) rename {DocoptTests => Tests/DocoptTests}/DocoptTests.swift (77%) rename {DocoptTests => Tests/DocoptTests}/Info.plist (100%) rename {DocoptTests => Tests/DocoptTests}/testcases.docopt (100%) create mode 100644 Tests/LinuxMain.swift diff --git a/Package.swift b/Package.swift index 6a07509..9e465f7 100644 --- a/Package.swift +++ b/Package.swift @@ -10,12 +10,9 @@ let package = Package( targets: [ .target( name: "Docopt", - path: "Sources" - ), + dependencies: []), .testTarget( name: "DocoptTests", - dependencies: ["Docopt"], - path: "DocoptTests" - ) + dependencies: ["Docopt"]) ] ) diff --git a/Sources/Argument.swift b/Sources/Docopt/Argument.swift similarity index 100% rename from Sources/Argument.swift rename to Sources/Docopt/Argument.swift diff --git a/Sources/BranchPattern.swift b/Sources/Docopt/BranchPattern.swift similarity index 100% rename from Sources/BranchPattern.swift rename to Sources/Docopt/BranchPattern.swift diff --git a/Sources/Command.swift b/Sources/Docopt/Command.swift similarity index 88% rename from Sources/Command.swift rename to Sources/Docopt/Command.swift index 8e9bed4..b354138 100644 --- a/Sources/Command.swift +++ b/Sources/Docopt/Command.swift @@ -18,7 +18,7 @@ internal class Command: Argument { let pattern = left[i] if pattern is Argument { if pattern.value as? String == self.name { - return (i, Command(self.name, value: true as AnyObject)) + return (i, Command(self.name, value: true)) } } } diff --git a/Sources/Docopt.h b/Sources/Docopt/Docopt.h similarity index 100% rename from Sources/Docopt.h rename to Sources/Docopt/Docopt.h diff --git a/Sources/Docopt.swift b/Sources/Docopt/Docopt.swift similarity index 91% rename from Sources/Docopt.swift rename to Sources/Docopt/Docopt.swift index 6a8e563..5d5a611 100644 --- a/Sources/Docopt.swift +++ b/Sources/Docopt/Docopt.swift @@ -8,25 +8,24 @@ import Foundation -@objc -open class Docopt : NSObject { - fileprivate(set) open var result: [String: AnyObject]! +open class Docopt { + fileprivate(set) open var result: [String: Any]! fileprivate let doc: String fileprivate let version: String? fileprivate let help: Bool fileprivate let optionsFirst: Bool fileprivate let arguments: [String] - - @objc open static func parse(_ doc: String, argv: [String], help: Bool = false, version: String? = nil, optionsFirst: Bool = false) -> [String: AnyObject] { + + open static func parse(_ doc: String, argv: [String], help: Bool = false, version: String? = nil, optionsFirst: Bool = false) -> [String: Any] { return Docopt(doc, argv: argv, help: help, version: version, optionsFirst: optionsFirst).result } - + internal init(_ doc: String, argv: [String]? = nil, help: Bool = false, version: String? = nil, optionsFirst: Bool = false) { self.doc = doc self.version = version self.help = help self.optionsFirst = optionsFirst - + var args: [String] if argv == nil { if CommandLine.argc > 1 { @@ -38,13 +37,12 @@ open class Docopt : NSObject { } else { args = argv! } - + arguments = args.filter { $0 != "" } - super.init() result = parse(optionsFirst) } - - fileprivate func parse(_ optionsFirst: Bool) -> [String: AnyObject] { + + fileprivate func parse(_ optionsFirst: Bool) -> [String: Any] { let usageSections = Docopt.parseSection("usage:", source: doc) if usageSections.count == 0 { @@ -52,31 +50,31 @@ open class Docopt : NSObject { } else if usageSections.count > 1 { DocoptLanguageError("More than one \"usage:\" (case-insensitive).").raise() } - + DocoptExit.usage = usageSections[0] - + var options = Docopt.parseDefaults(doc) let pattern = Docopt.parsePattern(Docopt.formalUsage(DocoptExit.usage), options: &options) let argv = Docopt.parseArgv(Tokens(arguments), options: &options, optionsFirst: optionsFirst) let patternOptions = Set(pattern.flat(Option.self)) - + for optionsShortcut in pattern.flat(OptionsShortcut.self) { let docOptions = Set(Docopt.parseDefaults(doc)) optionsShortcut.children = Array(docOptions.subtracting(patternOptions)) } Docopt.extras(help, version: version, options: argv, doc: doc) - + let (matched, left, collected) = pattern.fix().match(argv) - - var result = [String: AnyObject]() - + + var result = [String: Any]() + if matched && left.isEmpty { let collectedLeafs = collected as! [LeafPattern] let flatPattern = pattern.flat().filter { pattern in (collectedLeafs.filter {$0.name == pattern.name}).isEmpty } + collectedLeafs - + for leafChild: LeafPattern in flatPattern { result[leafChild.name!] = leafChild.value ?? NSNull() } @@ -86,7 +84,7 @@ open class Docopt : NSObject { DocoptExit().raise() return result } - + static fileprivate func extras(_ help: Bool, version: String?, options: [LeafPattern], doc: String) { let helpOption = options.filter { $0.name == "--help" || $0.name == "-h" } if help && !(helpOption.isEmpty) { @@ -99,11 +97,11 @@ open class Docopt : NSObject { exit(0) } } - + static internal func parseSection(_ name: String, source: String) -> [String] { return source.findAll("^([^\n]*\(name)[^\n]*\n?(?:[ \t].*?(?:\n|$))*)", flags: [.caseInsensitive, .anchorsMatchLines] ) } - + static internal func parseDefaults(_ doc: String) -> [Option] { var defaults = [Option]() let optionsSection = parseSection("options:", source: doc) @@ -121,14 +119,14 @@ open class Docopt : NSObject { } return defaults } - + static internal func parseLong(_ tokens: Tokens, options: inout [Option]) -> [Option] { let (long, eq, val) = tokens.move()!.partition("=") assert(long.hasPrefix("--")) - + var value: String? = eq != "" || val != "" ? val : nil var similar = options.filter {$0.long == long} - + if tokens.error is DocoptExit && similar.isEmpty { // if no exact match similar = options.filter {$0.long?.hasPrefix(long) ?? false} } @@ -143,7 +141,7 @@ open class Docopt : NSObject { o = Option(nil, long: long, argCount: argCount) options.append(o) if tokens.error is DocoptExit { - o = Option(nil, long: long, argCount: argCount, value: (argCount > 0) ? value as AnyObject : true as AnyObject) + o = Option(nil, long: long, argCount: argCount, value: (argCount > 0) ? value : true) } } else { o = Option(similar[0]) @@ -161,12 +159,12 @@ open class Docopt : NSObject { } } if tokens.error is DocoptExit { - o.value = value as AnyObject? ?? true as AnyObject + o.value = value ?? true } } return [o] } - + static internal func parseShorts(_ tokens: Tokens, options: inout [Option]) -> [Option] { let token = tokens.move()! assert(token.hasPrefix("-") && !token.hasPrefix("--")) @@ -177,7 +175,7 @@ open class Docopt : NSObject { let similar = options.filter {$0.short == short} var o: Option left = left[1.. 1 { tokens.error.raise("\(short) is specified ambiguously \(similar.count) times") return [] @@ -185,7 +183,7 @@ open class Docopt : NSObject { o = Option(short) options.append(o) if tokens.error is DocoptExit { - o = Option(short, value: true as AnyObject) + o = Option(short, value: true) } } else { var value: String? = nil @@ -201,19 +199,19 @@ open class Docopt : NSObject { left = "" } if tokens.error is DocoptExit { - o.value = true as AnyObject + o.value = true if let val = value { - o.value = val as AnyObject + o.value = val } } } - + parsed.append(o) } return parsed } - + static internal func parseAtom(_ tokens: Tokens, options: inout [Option]) -> [Pattern] { let token = tokens.current()! if ["(", "["].contains(token) { @@ -222,14 +220,14 @@ open class Docopt : NSObject { let (matching, result): (String, [BranchPattern]) = (token == "(") ? (")", [Required(u)]) : ("]", [Optional(u)]) - + if tokens.move() != matching { tokens.error.raise("unmatched '\(token)'") } - + return result } - + if token == "options" { _ = tokens.move() return [OptionsShortcut()] @@ -243,7 +241,7 @@ open class Docopt : NSObject { if (token.hasPrefix("<") && token.hasSuffix(">")) || token.isupper() { return [Argument(tokens.move()!)] } - + return [Command(tokens.move()!)] } @@ -260,20 +258,20 @@ open class Docopt : NSObject { return result } - + static internal func parseExpr(_ tokens: Tokens, options: inout [Option]) -> [Pattern] { var seq = parseSeq(tokens, options: &options) if tokens.current() != "|" { return seq } - + var result = seq.count > 1 ? [Required(seq)] : seq while tokens.current() == "|" { _ = tokens.move() seq = parseSeq(tokens, options: &options) result += seq.count > 1 ? [Required(seq)] : seq } - + return result.count > 1 ? [Either(result)] : result } @@ -290,7 +288,7 @@ open class Docopt : NSObject { while let current = tokens.current() { if tokens.current() == "--" { while let token = tokens.move() { - parsed.append(Argument(nil, value: token as AnyObject)) + parsed.append(Argument(nil, value: token)) } return parsed } else if current.hasPrefix("--") { @@ -303,27 +301,27 @@ open class Docopt : NSObject { } } else if optionsFirst { while let token = tokens.move() { - parsed.append(Argument(nil, value: token as AnyObject)) + parsed.append(Argument(nil, value: token)) } return parsed } else { - parsed.append(Command(nil, value: tokens.move() as AnyObject)) + parsed.append(Command(nil, value: tokens.move())) } } return parsed } - + static internal func parsePattern(_ source: String, options: inout [Option]) -> Pattern { let tokens: Tokens = Tokens.fromPattern(source) let result: [Pattern] = parseExpr(tokens, options: &options) - + if tokens.current() != nil { tokens.error.raise("unexpected ending: \(tokens)") } - + return Required(result) } - + static internal func formalUsage(_ section: String) -> String { let (_, _, s) = section.partition(":") // drop "usage:" let pu = s.split() diff --git a/Sources/DocoptError.swift b/Sources/Docopt/DocoptError.swift similarity index 100% rename from Sources/DocoptError.swift rename to Sources/Docopt/DocoptError.swift diff --git a/Sources/Either.swift b/Sources/Docopt/Either.swift similarity index 100% rename from Sources/Either.swift rename to Sources/Docopt/Either.swift diff --git a/Sources/Info.plist b/Sources/Docopt/Info.plist similarity index 100% rename from Sources/Info.plist rename to Sources/Docopt/Info.plist diff --git a/Sources/LeafPattern.swift b/Sources/Docopt/LeafPattern.swift similarity index 89% rename from Sources/LeafPattern.swift rename to Sources/Docopt/LeafPattern.swift index d9465a1..46cd57b 100644 --- a/Sources/LeafPattern.swift +++ b/Sources/Docopt/LeafPattern.swift @@ -18,7 +18,7 @@ internal class LeafPattern : Pattern { var name: String? var valueDescription: String = "Never been set!" - var value: AnyObject? { + var value: Any? { willSet { valueDescription = newValue.debugDescription switch newValue { @@ -46,50 +46,50 @@ internal class LeafPattern : Pattern { case .nil: fallthrough default: return "LeafPattern(\(String(describing: name)), \(String(describing: value)))" } - + } } - + init(_ name: String?, value: Any? = nil) { self.name = name if let val = value { - self.value = val as AnyObject + self.value = val } } - + override func flat(_: T.Type) -> [T] { if let cast = self as? T { return [cast] } return [] } - + override func match(_ left: [T], collected clld: [T]? = nil) -> MatchResult { let collected: [Pattern] = clld ?? [] let (pos, mtch) = singleMatch(left) - + if mtch == nil { return (false, left, collected) } let match = mtch as! LeafPattern - + var left_ = left left_.remove(at: pos) - + var sameName = collected.filter({ item in if let cast = item as? LeafPattern { return self.name == cast.name } return false }) as! [LeafPattern] - + if (valueType == .int) || (valueType == .list) { - var increment: AnyObject? = 1 as NSNumber + var increment: Any? = 1 if valueType != .int { increment = match.value if let val = match.value as? String { - increment = [val] as NSArray + increment = [val] } } if sameName.isEmpty { @@ -98,14 +98,14 @@ internal class LeafPattern : Pattern { return (true, left_, collected + [match]) } if let inc = increment as? Int { - sameName[0].value = (sameName[0].value as! Int + inc) as NSNumber + sameName[0].value = (sameName[0].value as! Int + inc) sameName[0].valueType = .int } else if let inc = increment as? [String] { - sameName[0].value = (((sameName[0].value as? [String]) ?? [String]()) + inc) as NSArray + sameName[0].value = (((sameName[0].value as? [String]) ?? [String]()) + inc) } return (true, left_, collected) } - + return (true, left_, collected + [match]) } } @@ -118,10 +118,10 @@ func ==(lhs: LeafPattern, rhs: LeafPattern) -> Bool { valEqual = lval == rval } else if let lval = lhs.value as? [String], let rval = rhs.value as? [String] { valEqual = lval == rval - } else if let lval = lhs.value as? NSNumber, let rval = rhs.value as? NSNumber { + } else if let lval = lhs.value as? Int, let rval = rhs.value as? Int { valEqual = lval == rval } else { - valEqual = lhs.value === rhs.value + valEqual = lhs.value as? AnyObject === rhs.value as? AnyObject } return lhs.name == rhs.name && valEqual } diff --git a/Sources/Docopt/LinuxMain.swift b/Sources/Docopt/LinuxMain.swift new file mode 100644 index 0000000..e69de29 diff --git a/Sources/OneOrMore.swift b/Sources/Docopt/OneOrMore.swift similarity index 100% rename from Sources/OneOrMore.swift rename to Sources/Docopt/OneOrMore.swift diff --git a/Sources/Option.swift b/Sources/Docopt/Option.swift similarity index 91% rename from Sources/Option.swift rename to Sources/Docopt/Option.swift index d0dddbc..8f1497d 100644 --- a/Sources/Option.swift +++ b/Sources/Docopt/Option.swift @@ -21,7 +21,7 @@ internal class Option: LeafPattern { } override var description: String { get { - var valueDescription : String = value?.description ?? "nil" + var valueDescription : String = value == nil ? "nil" : "\(value!)" if value is Bool, let val = value as? Bool { valueDescription = val ? "true" : "false" @@ -29,12 +29,12 @@ internal class Option: LeafPattern { return "Option(\(String(describing: short)), \(String(describing: long)), \(argCount), \(valueDescription))" } } - + convenience init(_ option: Option) { self.init(option.short, long: option.long, argCount: option.argCount, value: option.value) } - - init(_ short: String? = nil, long: String? = nil, argCount: UInt = 0, value: AnyObject? = false as NSNumber) { + + init(_ short: String? = nil, long: String? = nil, argCount: UInt = 0, value: Any? = false) { assert(argCount <= 1) self.short = short self.long = long @@ -47,17 +47,17 @@ internal class Option: LeafPattern { self.value = value } } - + static func parse(_ optionDescription: String) -> Option { var short: String? = nil var long: String? = nil var argCount: UInt = 0 - var value: AnyObject? = kCFBooleanFalse - + var value: Any? = false + var (options, _, description) = optionDescription.strip().partition(" ") options = options.replacingOccurrences(of: ",", with: " ", options: [], range: nil) options = options.replacingOccurrences(of: "=", with: " ", options: [], range: nil) - + for s in options.components(separatedBy: " ").filter({!$0.isEmpty}) { if s.hasPrefix("--") { long = s @@ -67,22 +67,22 @@ internal class Option: LeafPattern { argCount = 1 } } - + if argCount == 1 { let matched = description.findAll("\\[default: (.*)\\]", flags: .caseInsensitive) if matched.count > 0 { - value = matched[0] as AnyObject + value = matched[0] } else { value = nil } } - + return Option(short, long: long, argCount: argCount, value: value) } - + override func singleMatch(_ left: [T]) -> SingleMatchResult { for i in 0.. Pattern { let either = Pattern.transform(self).children.map { ($0 as! Required).children } - + for c in either { for ch in c { let filteredChildren = c.filter {$0 == ch} @@ -39,23 +39,23 @@ internal class Pattern: Equatable, Hashable, CustomStringConvertible { let e = child as! LeafPattern if ((e is Argument) && !(e is Command)) || ((e is Option) && (e as! Option).argCount != 0) { if e.value == nil { - e.value = [String]() as AnyObject + e.value = [String]() } else if !(e.value is [String]) { - e.value = String(describing:e.value!).split() as AnyObject + e.value = String(describing:e.value!).split() } } if (e is Command) || ((e is Option) && (e as! Option).argCount == 0) { - e.value = 0 as AnyObject + e.value = 0 e.valueType = .int } } } } } - + return self } - + static func isInParents(_ child: Pattern) -> Bool { return (child as? Required != nil) || (child as? Optional != nil) @@ -63,18 +63,18 @@ internal class Pattern: Equatable, Hashable, CustomStringConvertible { || (child as? Either != nil) || (child as? OneOrMore != nil) } - + static func transform(_ pattern: Pattern) -> Either { var result = [[Pattern]]() var groups = [[pattern]] while !groups.isEmpty { var children = groups.remove(at: 0) let child: BranchPattern? = children.filter({ self.isInParents($0) }).first as? BranchPattern - + if let child = child { let index = children.index(of: child)! children.remove(at: index) - + if child is Either { for pattern in child.children { groups.append([pattern] + children) @@ -88,7 +88,7 @@ internal class Pattern: Equatable, Hashable, CustomStringConvertible { result.append(children) } } - + return Either(result.map {Required($0)}) } @@ -99,11 +99,11 @@ internal class Pattern: Equatable, Hashable, CustomStringConvertible { func flat(_: T.Type) -> [T] { // abstract return [] } - + func match(_ left: T, collected clld: [T]? = nil) -> MatchResult { return match([left], collected: clld) } - + func match(_ left: [T], collected clld: [T]? = nil) -> MatchResult { // abstract return (false, [], []) } diff --git a/Sources/Required.swift b/Sources/Docopt/Required.swift similarity index 100% rename from Sources/Required.swift rename to Sources/Docopt/Required.swift diff --git a/Sources/String.swift b/Sources/Docopt/String.swift similarity index 100% rename from Sources/String.swift rename to Sources/Docopt/String.swift diff --git a/Sources/Tokens.swift b/Sources/Docopt/Tokens.swift similarity index 100% rename from Sources/Tokens.swift rename to Sources/Docopt/Tokens.swift diff --git a/DocoptTests/DocoptTestCase.swift b/Tests/DocoptTests/DocoptTestCase.swift similarity index 86% rename from DocoptTests/DocoptTestCase.swift rename to Tests/DocoptTests/DocoptTestCase.swift index 10266ba..0269a49 100644 --- a/DocoptTests/DocoptTestCase.swift +++ b/Tests/DocoptTests/DocoptTestCase.swift @@ -11,14 +11,14 @@ import Foundation public class DocoptTestCase { public var name: String = "" public var usage: String = "" - + public let programName: String public let arguments: [String]? - public let expectedOutput: AnyObject - - public init(_ programName: String, arguments: [String]?, expectedOutput: AnyObject) { + public let expectedOutput: Any + + public init(_ programName: String, arguments: [String]?, expectedOutput: Any) { self.programName = programName self.arguments = arguments self.expectedOutput = expectedOutput } -} \ No newline at end of file +} diff --git a/DocoptTests/DocoptTestCaseParser.swift b/Tests/DocoptTests/DocoptTestCaseParser.swift similarity index 95% rename from DocoptTests/DocoptTestCaseParser.swift rename to Tests/DocoptTests/DocoptTestCaseParser.swift index 4273ceb..15a8850 100644 --- a/DocoptTests/DocoptTestCaseParser.swift +++ b/Tests/DocoptTests/DocoptTestCaseParser.swift @@ -11,30 +11,30 @@ import Foundation public struct DocoptTestCaseParser { public var testCases: [DocoptTestCase]! - + public init(_ stringOfTestCases: String) { testCases = parse(stringOfTestCases: stringOfTestCases) } - + private func parse(stringOfTestCases: String) -> [DocoptTestCase] { let fixturesWithCommentsStripped: String = removeComments(string: stringOfTestCases) let fixtures: [String] = parseFixtures(fixturesString: fixturesWithCommentsStripped) let testCases: [DocoptTestCase] = parseFixturesArray(fixtureStrings: fixtures) - + return testCases } - + private func removeComments(string: String) -> String { let removeCommentsRegEx = try! NSRegularExpression(pattern: "(?m)#.*$", options: []) let fullRange: NSRange = NSMakeRange(0, string.count) return removeCommentsRegEx.stringByReplacingMatches(in: string, options: [], range: fullRange, withTemplate: "") } - + private func parseFixtures(fixturesString: String) -> [String] { let fixtures: [String] = fixturesString.components(separatedBy:"r\"\"\"") return fixtures.filter { !$0.strip().isEmpty } } - + private func parseFixturesArray(fixtureStrings: [String]) -> [DocoptTestCase] { var allTestCases = [DocoptTestCase]() let testBaseName: String = "Test" @@ -45,20 +45,20 @@ public struct DocoptTestCaseParser { testCase.name = testBaseName + String(testIndex) testIndex += 1 } - + allTestCases += newTestCases } - + return allTestCases } - + private func testCasesFromFixtureString(fixtureString: String) -> [DocoptTestCase] { var testCases = [DocoptTestCase]() let fixtureComponents: [String] = fixtureString.components(separatedBy:"\"\"\"") assert(fixtureComponents.count == 2, "Could not split fixture: \(fixtureString) into components") let usageDoc: String = fixtureComponents[0] let testInvocationString: String = fixtureComponents[1] - + let testInvocations: [String] = parseTestInvocations(stringOfTestInvocations: testInvocationString) for testInvocation in testInvocations { let testCase: DocoptTestCase? = parseTestCase(invocationString: testInvocation) @@ -67,30 +67,30 @@ public struct DocoptTestCaseParser { testCases.append(testCase) } } - + return testCases } - + private func parseTestCase(invocationString: String) -> DocoptTestCase? { let trimmedTestInvocation: String = invocationString.strip() var testInvocationComponents: [String] = trimmedTestInvocation.components(separatedBy:"\n") assert(testInvocationComponents.count >= 2, "Could not split test case: \(trimmedTestInvocation) into components") - + let input: String = testInvocationComponents.remove(at: 0) // first line let expectedOutput: String = testInvocationComponents.joined(separator: "\n") // all remaining lines - + var inputComponents: [String] = input.components(separatedBy:" ") let programName: String = inputComponents.remove(at: 0) // first part - + var error : NSError? let jsonData: NSData? = expectedOutput.data(using: String.Encoding.utf8, allowLossyConversion: false) as NSData? if jsonData == nil { NSLog("Error parsing \(expectedOutput) to JSON: \(String(describing: error))") return nil } - let expectedOutputJSON: AnyObject? + let expectedOutputJSON: Any? do { - expectedOutputJSON = try JSONSerialization.jsonObject(with: jsonData! as Data, options: .allowFragments) as AnyObject + expectedOutputJSON = try JSONSerialization.jsonObject(with: jsonData! as Data, options: .allowFragments) } catch let error1 as NSError { error = error1 expectedOutputJSON = nil @@ -99,10 +99,10 @@ public struct DocoptTestCaseParser { NSLog("Error parsing \(expectedOutput) to JSON: \(String(describing: error))") return nil } - + return DocoptTestCase(programName, arguments: inputComponents, expectedOutput: expectedOutputJSON!) } - + private func parseTestInvocations(stringOfTestInvocations: String) -> [String] { let testInvocations: [String] = stringOfTestInvocations.components(separatedBy:"$ ") return testInvocations.filter { !$0.strip().isEmpty } diff --git a/DocoptTests/DocoptTestCasesTests.swift b/Tests/DocoptTests/DocoptTestCasesTests.swift similarity index 94% rename from DocoptTests/DocoptTestCasesTests.swift rename to Tests/DocoptTests/DocoptTestCasesTests.swift index 16f9027..b726fda 100644 --- a/DocoptTests/DocoptTestCasesTests.swift +++ b/Tests/DocoptTests/DocoptTestCasesTests.swift @@ -23,21 +23,21 @@ class DocoptTestCasesTests: XCTestCase { XCTAssertTrue(exists, "Fixtures file testcases.docopt does not exist in testing bundle") } } - + func testFixturesFileCanBeOpened() { XCTAssertNotEqual(fixturesFileContents(), "", "Could not read fixtures file") } - + func testTestCases() { let rawTestCases = fixturesFileContents() let parser = DocoptTestCaseParser(rawTestCases) - + for testCase in parser.testCases { - let expectedOutput: AnyObject = testCase.expectedOutput - var result: AnyObject = "user-error" as AnyObject + let expectedOutput: Any = testCase.expectedOutput + var result: Any = "user-error" let opt = Docopt(testCase.usage, argv: testCase.arguments) if DocoptError.errorMessage == nil { - result = opt.result as AnyObject + result = opt.result } else { DocoptError.errorMessage = nil } @@ -58,7 +58,7 @@ class DocoptTestCasesTests: XCTestCase { } } } - + private func fixturesFilePath() -> String? { let testBundle: Bundle = Bundle(for: type(of: self)) guard let path = testBundle.path(forResource: "testcases", ofType: "docopt") else { @@ -71,10 +71,10 @@ class DocoptTestCasesTests: XCTestCase { let url = testBundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("DocoptTests").appendingPathComponent("testcases.docopt") return url.path } - + return path } - + private func fixturesFileContents() -> String { if let filePath = self.fixturesFilePath() { let fileContents = try! String(contentsOfFile: filePath, encoding: String.Encoding.utf8) diff --git a/DocoptTests/DocoptTests.h b/Tests/DocoptTests/DocoptTests.h similarity index 100% rename from DocoptTests/DocoptTests.h rename to Tests/DocoptTests/DocoptTests.h diff --git a/DocoptTests/DocoptTests.swift b/Tests/DocoptTests/DocoptTests.swift similarity index 77% rename from DocoptTests/DocoptTests.swift rename to Tests/DocoptTests/DocoptTests.swift index 0b349b0..fd1dbfd 100644 --- a/DocoptTests/DocoptTests.swift +++ b/Tests/DocoptTests/DocoptTests.swift @@ -13,7 +13,7 @@ class DocoptTests: XCTestCase { override func setUp() { DocoptError.test = true } - + func testPatternFlat() { XCTAssertEqual(Required([OneOrMore(Argument("N")), Option("-a"), Argument("M")]).flat(), [Argument("N"), Option("-a"), Argument("M")]) XCTAssertEqual(Required([Optional(OptionsShortcut()), Optional(Option("-a"))]).flat(OptionsShortcut.self), [OptionsShortcut()]) @@ -25,7 +25,7 @@ class DocoptTests: XCTestCase { let fixture = [Option("-a"), Option("-r"), Option("-m", argCount: 1)] XCTAssertEqual(parsedDefaults, fixture) } - + func testParseSection() { let usage = "usage: this\nusage:hai\nusage: this that\nusage: foo\n bar\nPROGRAM USAGE:\n foo\n bar\nusage:\n\ttoo\n\ttar\nUsage: eggs spam\nBAZZ\nusage: pit stop" XCTAssertEqual(Docopt.parseSection("usage:", source: "foo bar fizz buzz"), []) @@ -42,12 +42,12 @@ class DocoptTests: XCTestCase { "usage: pit stop", ]) } - + func testFormalUsage() { let doc = "\nUsage: prog [-hv] ARG\n prog N M\n\n prog is a program." let usage = Docopt.parseSection("usage:", source: doc)[0] let formalUsage = Docopt.formalUsage(usage) - + XCTAssertEqual(usage, "Usage: prog [-hv] ARG\n prog N M") XCTAssertEqual(formalUsage, "( [-hv] ARG ) | ( N M )") } @@ -55,29 +55,29 @@ class DocoptTests: XCTestCase { func testParseArgv() { var o = [Option("-h"), Option("-v", long: "--verbose"), Option("-f", long:"--file", argCount: 1)] let TS = {(s: String) in return Tokens(s, error: DocoptExit()) } - + XCTAssertEqual(Docopt.parseArgv(TS(""), options: &o), []) - XCTAssertEqual(Docopt.parseArgv(TS("-h"), options: &o), [Option("-h", value: true as AnyObject)]) + XCTAssertEqual(Docopt.parseArgv(TS("-h"), options: &o), [Option("-h", value: true)]) XCTAssertEqual(Docopt.parseArgv(TS("-h --verbose"), options: &o), - [Option("-h", value: true as AnyObject), Option("-v", long: "--verbose", value: true as AnyObject)]) + [Option("-h", value: true), Option("-v", long: "--verbose", value: true)]) XCTAssertEqual(Docopt.parseArgv(TS("-h --file f.txt"), options: &o), - [Option("-h", value: true as AnyObject), Option("-f", long: "--file", argCount: 1, value: "f.txt" as AnyObject)]) + [Option("-h", value: true), Option("-f", long: "--file", argCount: 1, value: "f.txt")]) XCTAssertEqual(Docopt.parseArgv(TS("-h --file f.txt arg"), options: &o), - [Option("-h", value: true as AnyObject), - Option("-f", long: "--file", argCount: 1, value: "f.txt" as AnyObject), - Argument(nil, value: "arg" as AnyObject)]) + [Option("-h", value: true), + Option("-f", long: "--file", argCount: 1, value: "f.txt"), + Argument(nil, value: "arg")]) XCTAssertEqual(Docopt.parseArgv(TS("-h --file f.txt arg arg2"), options: &o), - [Option("-h", value: true as AnyObject), - Option("-f", long: "--file", argCount: 1, value: "f.txt" as AnyObject), - Argument(nil, value: "arg" as AnyObject), - Argument(nil, value: "arg2" as AnyObject)]) + [Option("-h", value: true), + Option("-f", long: "--file", argCount: 1, value: "f.txt"), + Argument(nil, value: "arg"), + Argument(nil, value: "arg2")]) XCTAssertEqual(Docopt.parseArgv(TS("-h arg -- -v"), options: &o), - [Option("-h", value: true as AnyObject), - Argument(nil, value: "arg" as AnyObject), - Argument(nil, value: "--" as AnyObject), - Argument(nil, value: "-v" as AnyObject)]) + [Option("-h", value: true), + Argument(nil, value: "arg"), + Argument(nil, value: "--"), + Argument(nil, value: "-v")]) } - + func testOptionParse() { XCTAssertEqual(Option.parse("-h"), Option("-h")) XCTAssertEqual(Option.parse("--help"), Option(long: "--help")) @@ -97,23 +97,23 @@ class DocoptTests: XCTestCase { XCTAssertEqual(Option.parse(" -h"), Option("-h")) XCTAssertEqual(Option.parse("-h TOPIC Descripton... [default: 2]"), - Option("-h", argCount: 1, value: "2" as AnyObject)) + Option("-h", argCount: 1, value: "2")) XCTAssertEqual(Option.parse("-h TOPIC Descripton... [default: topic-1]"), - Option("-h", argCount: 1, value: "topic-1" as AnyObject)) + Option("-h", argCount: 1, value: "topic-1")) XCTAssertEqual(Option.parse("--help=TOPIC ... [default: 3.14]"), - Option(long: "--help", argCount: 1, value: "3.14" as AnyObject)) + Option(long: "--help", argCount: 1, value: "3.14")) XCTAssertEqual(Option.parse("-h, --help=DIR ... [default: ./]"), - Option("-h", long: "--help", argCount: 1, value: "./" as AnyObject)) + Option("-h", long: "--help", argCount: 1, value: "./")) XCTAssertEqual(Option.parse("-h TOPIC Descripton... [dEfAuLt: 2]"), - Option("-h", argCount: 1, value: "2" as AnyObject)) + Option("-h", argCount: 1, value: "2")) } - + func testOptionName() { XCTAssertEqual(Option("-h").name!, "-h") XCTAssertEqual(Option("-h", long: "--help").name!, "--help") XCTAssertEqual(Option(long: "--help").name!, "--help") } - + func testParsePattern() { var o = [Option("-h"), Option("-v", long: "--verbose"), Option("-f", long:"--file", argCount: 1)] XCTAssertEqual(Docopt.parsePattern("[ -h ]", options: &o), Required(Optional(Option("-h")))) @@ -152,42 +152,42 @@ class DocoptTests: XCTestCase { XCTAssertEqual(Docopt.parsePattern("", options: &o), Required(Argument(""))) XCTAssertEqual(Docopt.parsePattern("add", options: &o), Required(Command("add"))) } - + func testOptionMatch() { - XCTAssertTrue(Option("-a").match([Option("-a", value: true as AnyObject)]) == - (true, [], [Option("-a", value: true as AnyObject)])) + XCTAssertTrue(Option("-a").match([Option("-a", value: true)]) == + (true, [], [Option("-a", value: true)])) XCTAssertTrue(Option("-a").match([Option("-x")]) == (false, [Option("-x")], [])) XCTAssertTrue(Option("-a").match([Argument("N")]) == (false, [Argument("N")], [])) XCTAssertTrue(Option("-a").match([Option("-x"), Option("-a"), Argument("N")]) == (true, [Option("-x"), Argument("N")], [Option("-a")])) - XCTAssertTrue(Option("-a").match([Option("-a", value: true as AnyObject), Option("-a")]) == - (true, [Option("-a")], [Option("-a", value: true as AnyObject)])) + XCTAssertTrue(Option("-a").match([Option("-a", value: true), Option("-a")]) == + (true, [Option("-a")], [Option("-a", value: true)])) } - + func testArgumentMatch() { XCTAssertTrue(Argument("N").match(Argument(nil, value: 9)) == (true, [], [Argument("N", value: 9)])) XCTAssertTrue(Argument("N").match(Option("-x")) == (false, [Option("-x")], [])) - XCTAssertTrue(Argument("N").match([Option("-x"), Option("-a"), Argument(nil, value: 5 as AnyObject)]) == - (true, [Option("-x"), Option("-a")], [Argument("N", value: 5 as AnyObject)])) - XCTAssertTrue(Argument("N").match([Argument(nil, value: 9 as AnyObject), Argument(nil, value: 0 as AnyObject)]) == - (true, [Argument(nil, value: 0 as AnyObject)], [Argument("N", value: 9 as AnyObject)])) + XCTAssertTrue(Argument("N").match([Option("-x"), Option("-a"), Argument(nil, value: 5)]) == + (true, [Option("-x"), Option("-a")], [Argument("N", value: 5)])) + XCTAssertTrue(Argument("N").match([Argument(nil, value: 9), Argument(nil, value: 0)]) == + (true, [Argument(nil, value: 0)], [Argument("N", value: 9)])) } - + func testCommandMatch() { - XCTAssertTrue(Command("c").match(Argument(nil, value: "c" as AnyObject)) == - (true, [], [Command("c", value: true as AnyObject)])) + XCTAssertTrue(Command("c").match(Argument(nil, value: "c")) == + (true, [], [Command("c", value: true)])) XCTAssertTrue(Command("c").match(Option("-x")) == (false, [Option("-x")], [])) - XCTAssertTrue(Command("c").match([Option("-x"), Option("-a"), Argument(nil, value: "c" as AnyObject)]) == - (true, [Option("-x"), Option("-a")], [Command("c", value: true as AnyObject)])) - XCTAssertTrue(Either([Command("add"), Command("rm")]).match(Argument(nil, value: "rm" as AnyObject)) == - (true, [], [Command("rm", value: true as AnyObject)])) + XCTAssertTrue(Command("c").match([Option("-x"), Option("-a"), Argument(nil, value: "c")]) == + (true, [Option("-x"), Option("-a")], [Command("c", value: true)])) + XCTAssertTrue(Either([Command("add"), Command("rm")]).match(Argument(nil, value: "rm")) == + (true, [], [Command("rm", value: true)])) } - + func testOptionalMatch() { XCTAssertTrue(Optional(Option("-a")).match([Option("-a")]) == (true, [], [Option("-a")])) @@ -200,13 +200,13 @@ class DocoptTests: XCTestCase { (true, [], [Option("-b")])) XCTAssertTrue(Optional([Option("-a"), Option("-b")]).match([Option("-x")]) == (true, [Option("-x")], [])) - XCTAssertTrue(Optional(Argument("N")).match([Argument(nil, value: 9 as AnyObject)]) == - (true, [], [Argument("N", value: 9 as AnyObject)])) + XCTAssertTrue(Optional(Argument("N")).match([Argument(nil, value: 9)]) == + (true, [], [Argument("N", value: 9)])) XCTAssertTrue(Optional([Option("-a"), Option("-b")]).match( [Option("-b"), Option("-x"), Option("-a")]) == (true, [Option("-x")], [Option("-a"), Option("-b")])) } - + func testRequiredMatch() { XCTAssertTrue(Required(Option("-a")).match([Option("-a")]) == (true, [], [Option("-a")])) @@ -216,7 +216,7 @@ class DocoptTests: XCTestCase { XCTAssertTrue(Required([Option("-a"), Option("-b")]).match([Option("-a")]) == (false, [Option("-a")], [])) } - + func testEitherMatch() { // i'm too lazy to mock up a fixture of some kind. deal with it. var expected: MatchResult @@ -238,38 +238,38 @@ class DocoptTests: XCTestCase { actual = Either([Option("-a"), Option("-b"), Option("-c")]).match([Option("-x"), Option("-b")]) XCTAssertTrue(actual == expected, "\nExpected: \(expected)\nActual: \(actual)\n\n") - expected = (true, [], [Argument("N", value: 1 as AnyObject), Argument("M", value: 2 as AnyObject)]) - actual = Either([Argument("M"), Required([Argument("N"), Argument("M")])]).match([Argument(nil, value: 1 as AnyObject), Argument(nil, value: 2 as AnyObject)]) + expected = (true, [], [Argument("N", value: 1), Argument("M", value: 2)]) + actual = Either([Argument("M"), Required([Argument("N"), Argument("M")])]).match([Argument(nil, value: 1), Argument(nil, value: 2)]) XCTAssertTrue(actual == expected, "\nExpected: \(expected)\nActual: \(actual)\n\n") } func testOneOrMoreMatch() { - XCTAssertTrue(OneOrMore(Argument("N")).match([Argument(nil, value: 9 as AnyObject)]) == - (true, [], [Argument("N", value: 9 as AnyObject)])) + XCTAssertTrue(OneOrMore(Argument("N")).match([Argument(nil, value: 9)]) == + (true, [], [Argument("N", value: 9)])) XCTAssertTrue(OneOrMore(Argument("N")).match([]) == (false, [], [])) XCTAssertTrue(OneOrMore(Argument("N")).match([Option("-x")]) == (false, [Option("-x")], [])) XCTAssertTrue(OneOrMore(Argument("N")).match( - [Argument(nil, value: 9 as AnyObject), Argument(nil, value: 8 as AnyObject)]) == ( - true, [], [Argument("N", value: 9 as AnyObject), Argument("N", value: 8 as AnyObject)])) + [Argument(nil, value: 9), Argument(nil, value: 8)]) == ( + true, [], [Argument("N", value: 9), Argument("N", value: 8)])) XCTAssertTrue(OneOrMore(Argument("N")).match( - [Argument(nil, value: 9 as AnyObject), Option("-x"), Argument(nil, value: 8 as AnyObject)]) == ( - true, [Option("-x")], [Argument("N", value: 9 as AnyObject), Argument("N", value: 8 as AnyObject)])) + [Argument(nil, value: 9), Option("-x"), Argument(nil, value: 8)]) == ( + true, [Option("-x")], [Argument("N", value: 9), Argument("N", value: 8)])) XCTAssertTrue(OneOrMore(Option("-a")).match( - [Option("-a"), Argument(nil, value: 8 as AnyObject), Option("-a")]) == - (true, [Argument(nil, value: 8 as AnyObject)], [Option("-a"), Option("-a")])) - XCTAssertTrue(OneOrMore(Option("-a")).match([Argument(nil, value: 8 as AnyObject), + [Option("-a"), Argument(nil, value: 8), Option("-a")]) == + (true, [Argument(nil, value: 8)], [Option("-a"), Option("-a")])) + XCTAssertTrue(OneOrMore(Option("-a")).match([Argument(nil, value: 8), Option("-x")]) == - (false, [Argument(nil, value: 8 as AnyObject), Option("-x")], [])) + (false, [Argument(nil, value: 8), Option("-x")], [])) XCTAssertTrue(OneOrMore(Required([Option("-a"), Argument("N")])).match( - [Option("-a"), Argument(nil, value: 1 as AnyObject), Option("-x"), - Option("-a"), Argument(nil, value: 2 as AnyObject)]) == + [Option("-a"), Argument(nil, value: 1), Option("-x"), + Option("-a"), Argument(nil, value: 2)]) == (true, [Option("-x")], - [Option("-a"), Argument("N", value: 1 as AnyObject), Option("-a"), Argument("N", value: 2 as AnyObject)])) - XCTAssertTrue(OneOrMore(Optional(Argument("N"))).match([Argument(nil, value: 9 as AnyObject)]) == - (true, [], [Argument("N", value: 9 as AnyObject)])) + [Option("-a"), Argument("N", value: 1), Option("-a"), Argument("N", value: 2)])) + XCTAssertTrue(OneOrMore(Optional(Argument("N"))).match([Argument(nil, value: 9)]) == + (true, [], [Argument("N", value: 9)])) } - + func testPatternEither() { XCTAssertEqual(Pattern.transform(Option("-a")), Either(Required(Option("-a")))) XCTAssertEqual(Pattern.transform(Argument("A")), Either(Required(Argument("A")))) @@ -290,7 +290,7 @@ class DocoptTests: XCTestCase { Either(Required([Argument("N"), Argument("M"), Argument("N"), Argument("M")]))) } - + func testFixRepeatingArguments() { XCTAssertEqual(Option("-a").fixRepeatingArguments(), Option("-a")) XCTAssertEqual(Argument("N").fixRepeatingArguments(), Argument("N")) @@ -301,46 +301,46 @@ class DocoptTests: XCTestCase { OneOrMore(Argument("N"))]).fix(), Either([Argument("N", value: []), OneOrMore(Argument("N", value: []))])) } - + func testListArgumentMatch() { XCTAssertTrue(Required([Argument("N"), Argument("N")]).fix().match( - [Argument(nil, value: "1" as AnyObject), Argument(nil, value: "2" as AnyObject)]) == + [Argument(nil, value: "1"), Argument(nil, value: "2")]) == (true, [], [Argument("N", value: ["1", "2"])])) XCTAssertTrue(OneOrMore(Argument("N")).fix().match( - [Argument(nil, value: "1" as AnyObject), Argument(nil, value: "2" as AnyObject), Argument(nil, value: "3" as AnyObject)]) == + [Argument(nil, value: "1"), Argument(nil, value: "2"), Argument(nil, value: "3")]) == (true, [], [Argument("N", value: ["1", "2", "3"])])) XCTAssertTrue(Required([Argument("N"), OneOrMore(Argument("N"))]).fix().match( - [Argument(nil, value: "1" as AnyObject), Argument(nil, value: "2" as AnyObject), Argument(nil, value: "3" as AnyObject)]) == + [Argument(nil, value: "1"), Argument(nil, value: "2"), Argument(nil, value: "3")]) == (true, [], [Argument("N", value: ["1", "2", "3"])])) XCTAssertTrue(Required([Argument("N"), Required(Argument("N"))]).fix().match( - [Argument(nil, value: "1" as AnyObject), Argument(nil, value: "2" as AnyObject)]) == + [Argument(nil, value: "1"), Argument(nil, value: "2")]) == (true, [], [Argument("N", value: ["1", "2"])])) } - + func testBasicPatternMatch() { // ( -a N [ -x Z ] ) let pattern = Required([Option("-a"), Argument("N"), Optional([Option("-x"), Argument("Z")])]) - + // -a N - XCTAssertTrue(pattern.match([Option("-a"), Argument(nil, value: 9 as AnyObject)]) == - (true, [], [Option("-a"), Argument("N", value: 9 as AnyObject)])) + XCTAssertTrue(pattern.match([Option("-a"), Argument(nil, value: 9)]) == + (true, [], [Option("-a"), Argument("N", value: 9)])) // -a -x N Z - XCTAssertTrue(pattern.match([Option("-a"), Option("-x"), Argument(nil, value: 9 as AnyObject), Argument(nil, value: 5 as AnyObject)]) == - (true, [], [Option("-a"), Argument("N", value: 9 as AnyObject), Option("-x"), Argument("Z", value: 5 as AnyObject)])) + XCTAssertTrue(pattern.match([Option("-a"), Option("-x"), Argument(nil, value: 9), Argument(nil, value: 5)]) == + (true, [], [Option("-a"), Argument("N", value: 9), Option("-x"), Argument("Z", value: 5)])) // -x N Z # BZZ! - XCTAssertTrue(pattern.match([Option("-x"), Argument(nil, value: 9 as AnyObject), Argument(nil, value: 5 as AnyObject)]) == - (false, [Option("-x"), Argument(nil, value: 9 as AnyObject), Argument(nil, value: 5 as AnyObject)], [])) + XCTAssertTrue(pattern.match([Option("-x"), Argument(nil, value: 9), Argument(nil, value: 5)]) == + (false, [Option("-x"), Argument(nil, value: 9), Argument(nil, value: 5)], [])) } - + func testSet() { XCTAssertEqual(Argument("N"), Argument("N")) XCTAssertEqual(Set([Argument("N"), Argument("N")]), Set([Argument("N")])) } - + func testDocopt() { let doc = "Usage: prog [-v] A\n\n Options: -v Be verbose." let result = Docopt(doc, argv: ["arg"]).result - let fixture = ["-v": false as AnyObject, "A": "arg" as AnyObject] + let fixture = ["-v": false, "A": "arg"] for (key, value) in result! { XCTAssertEqual(value as! NSObject, fixture[key]! as! NSObject) @@ -354,4 +354,3 @@ internal func ==(lhs: MatchResult, rhs: MatchResult) -> Bool { && lhs.left == rhs.left && lhs.collected == rhs.collected } - diff --git a/DocoptTests/Info.plist b/Tests/DocoptTests/Info.plist similarity index 100% rename from DocoptTests/Info.plist rename to Tests/DocoptTests/Info.plist diff --git a/DocoptTests/testcases.docopt b/Tests/DocoptTests/testcases.docopt similarity index 100% rename from DocoptTests/testcases.docopt rename to Tests/DocoptTests/testcases.docopt diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift new file mode 100644 index 0000000..2a4da7a --- /dev/null +++ b/Tests/LinuxMain.swift @@ -0,0 +1,6 @@ +import XCTest +@testable import Docopt + +XCTMain([ + testCase(DocoptTestCasesTests.testTestCases), +]) From 19b1b370a7136df43907c29cd6f98425a60cb4cb Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Thu, 8 Mar 2018 14:12:01 +0000 Subject: [PATCH 06/18] added explicit dictionary type --- Tests/DocoptTests/DocoptTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/DocoptTests/DocoptTests.swift b/Tests/DocoptTests/DocoptTests.swift index fd1dbfd..93225a6 100644 --- a/Tests/DocoptTests/DocoptTests.swift +++ b/Tests/DocoptTests/DocoptTests.swift @@ -340,7 +340,7 @@ class DocoptTests: XCTestCase { func testDocopt() { let doc = "Usage: prog [-v] A\n\n Options: -v Be verbose." let result = Docopt(doc, argv: ["arg"]).result - let fixture = ["-v": false, "A": "arg"] + let fixture : [String:Any] = ["-v": false, "A": "arg"] for (key, value) in result! { XCTAssertEqual(value as! NSObject, fixture[key]! as! NSObject) From 4f1949acd7db13cc2d2b333acca06767be4c0862 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Thu, 8 Mar 2018 14:16:18 +0000 Subject: [PATCH 07/18] Use Data not NSData --- Tests/DocoptTests/DocoptTestCaseParser.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/DocoptTests/DocoptTestCaseParser.swift b/Tests/DocoptTests/DocoptTestCaseParser.swift index 15a8850..018825a 100644 --- a/Tests/DocoptTests/DocoptTestCaseParser.swift +++ b/Tests/DocoptTests/DocoptTestCaseParser.swift @@ -83,7 +83,7 @@ public struct DocoptTestCaseParser { let programName: String = inputComponents.remove(at: 0) // first part var error : NSError? - let jsonData: NSData? = expectedOutput.data(using: String.Encoding.utf8, allowLossyConversion: false) as NSData? + let jsonData = expectedOutput.data(using: String.Encoding.utf8, allowLossyConversion: false) if jsonData == nil { NSLog("Error parsing \(expectedOutput) to JSON: \(String(describing: error))") return nil From 1682f5b5011e881030c64ace488d26aae90d4b4c Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Thu, 8 Mar 2018 14:36:02 +0000 Subject: [PATCH 08/18] tests run under linux --- Sources/Docopt/LinuxMain.swift | 0 Tests/DocoptTests/DocoptTestCaseParser.swift | 2 ++ Tests/DocoptTests/DocoptTestCasesTests.swift | 31 ++++++++++++++------ Tests/LinuxMain.swift | 4 +-- 4 files changed, 26 insertions(+), 11 deletions(-) delete mode 100644 Sources/Docopt/LinuxMain.swift diff --git a/Sources/Docopt/LinuxMain.swift b/Sources/Docopt/LinuxMain.swift deleted file mode 100644 index e69de29..0000000 diff --git a/Tests/DocoptTests/DocoptTestCaseParser.swift b/Tests/DocoptTests/DocoptTestCaseParser.swift index 018825a..3443597 100644 --- a/Tests/DocoptTests/DocoptTestCaseParser.swift +++ b/Tests/DocoptTests/DocoptTestCaseParser.swift @@ -94,6 +94,8 @@ public struct DocoptTestCaseParser { } catch let error1 as NSError { error = error1 expectedOutputJSON = nil + } catch { + expectedOutputJSON = nil } if (expectedOutputJSON == nil) { NSLog("Error parsing \(expectedOutput) to JSON: \(String(describing: error))") diff --git a/Tests/DocoptTests/DocoptTestCasesTests.swift b/Tests/DocoptTests/DocoptTestCasesTests.swift index b726fda..6a87bc2 100644 --- a/Tests/DocoptTests/DocoptTestCasesTests.swift +++ b/Tests/DocoptTests/DocoptTestCasesTests.swift @@ -59,20 +59,29 @@ class DocoptTestCasesTests: XCTestCase { } } + private func fallbackFilePath(from exeURL : URL) -> String? { + // SwiftPM currently doesn't support building bundles, and Linux doesn't support + // them at all, so if the tests are run with SwiftPM or on Linux, + // we'll fail to find the bundle path here. + // As a temporary workaround, we can fall back on a relative path from the executable. + // This is fragile as it relies on the assumption that we know where SwiftPM will + // put it, and where the testcases file lives relative to it, but it's + // better than just disabling all the tests... + let url = exeURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("Tests/DocoptTests/testcases.docopt") + print(url.path) + return url.path + } + private func fixturesFilePath() -> String? { + #if os(Linux) + return fallbackFilePath(from: URL(fileURLWithPath: CommandLine.arguments[0])) + #else let testBundle: Bundle = Bundle(for: type(of: self)) guard let path = testBundle.path(forResource: "testcases", ofType: "docopt") else { - // SwiftPM currently doesn't support bundles, so if the tests are run with it, - // we'll fail to find the bundle path here. - // As a temporary workaround, we can fall back on a relative path. This is fragile - // as it relies on the assumption that we know where SwiftPM will put the - // executable, and where the testcases file lives relative to it, but it's - // better than just disabling all the tests... - let url = testBundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("DocoptTests").appendingPathComponent("testcases.docopt") - return url.path + return fallbackFilePath(from: testBundle.bundleURL) } - return path + #endif } private func fixturesFileContents() -> String { @@ -82,4 +91,8 @@ class DocoptTestCasesTests: XCTestCase { } return "" } + + static var allTests = [ + ("testTestCases", testTestCases), + ] } diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 2a4da7a..68d2117 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1,6 +1,6 @@ import XCTest -@testable import Docopt +@testable import DocoptTests XCTMain([ - testCase(DocoptTestCasesTests.testTestCases), + testCase(DocoptTestCasesTests.allTests), ]) From 62ad06313a0fcdcf1179e0f3a0d647be9321b563 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Thu, 8 Mar 2018 14:40:27 +0000 Subject: [PATCH 09/18] slightly more compact way of expressing the relative path --- Tests/DocoptTests/DocoptTestCasesTests.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Tests/DocoptTests/DocoptTestCasesTests.swift b/Tests/DocoptTests/DocoptTestCasesTests.swift index 6a87bc2..b8097ea 100644 --- a/Tests/DocoptTests/DocoptTestCasesTests.swift +++ b/Tests/DocoptTests/DocoptTestCasesTests.swift @@ -67,9 +67,8 @@ class DocoptTestCasesTests: XCTestCase { // This is fragile as it relies on the assumption that we know where SwiftPM will // put it, and where the testcases file lives relative to it, but it's // better than just disabling all the tests... - let url = exeURL.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("Tests/DocoptTests/testcases.docopt") - print(url.path) - return url.path + let path = exeURL.appendingPathComponent("../../../../Tests/DocoptTests/testcases.docopt").standardized.path + return path } private func fixturesFilePath() -> String? { From ae93dbca9b73c3f0c6e53d925324e3c0ac928d6d Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Thu, 8 Mar 2018 15:10:02 +0000 Subject: [PATCH 10/18] simple matching v1 --- Tests/DocoptTests/DocoptTestCasesTests.swift | 53 ++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/Tests/DocoptTests/DocoptTestCasesTests.swift b/Tests/DocoptTests/DocoptTestCasesTests.swift index b8097ea..f616ff5 100644 --- a/Tests/DocoptTests/DocoptTestCasesTests.swift +++ b/Tests/DocoptTests/DocoptTestCasesTests.swift @@ -28,6 +28,53 @@ class DocoptTestCasesTests: XCTestCase { XCTAssertNotEqual(fixturesFileContents(), "", "Could not read fixtures file") } + static func valuesMatch(v1 : Any, v2 : Any) -> Bool { + if let a1 = v1 as? [Any], let a2 = v2 as? [Any] { + return !arraysMatch(a1: a1, a2: a2) + } + if let i1 = v1 as? Int, let i2 = v2 as? Int { + return i1 == i2 + } + if let s1 = v1 as? String, let s2 = v2 as? String { + return s1 == s2 + } + if let b1 = v1 as? Bool, let b2 = v2 as? Bool { + return b1 == b2 + } + if let n1 = v1 as? NSNull, let n2 = v2 as? NSNull { + return n1 == n2 + } + return false + } + + static func arraysMatch(a1 : [Any], a2 : [Any]) -> Bool { + if a1.count != a2.count { + return false + } + + var index = 0 + for v1 in a1 { + if !valuesMatch(v1: v1, v2: a2[index]) { + return false + } + index += 1 + } + return true + } + + static func dictionariesMatch(d1 : [String:Any], d2 : [String:Any]) -> Bool { + // filter out all matching key/value pairs + let remaining = d1.filter { (key, value) -> Bool in + if let v2 = d2[key] { + return !valuesMatch(v1: value, v2: v2) + } + return true + } + + // there should be nothing left if the dictionaries match + return remaining.count == 0 + } + func testTestCases() { let rawTestCases = fixturesFileContents() let parser = DocoptTestCaseParser(rawTestCases) @@ -42,9 +89,9 @@ class DocoptTestCasesTests: XCTestCase { DocoptError.errorMessage = nil } - if let expectedDictionary = expectedOutput as? NSDictionary, - let resultDictionary = result as? NSDictionary { - if resultDictionary != expectedDictionary + if let expectedDictionary = expectedOutput as? [String:Any], + let resultDictionary = result as? [String:Any] { + if !DocoptTestCasesTests.dictionariesMatch(d1: expectedDictionary, d2: resultDictionary) { XCTAssert(false, "Test \(testCase.name) failed. Expected:\n\(expectedDictionary)\n\n, got: \(resultDictionary)\n\n") From 1f997f7779c953c1da85b8504a12616f98b0c8d7 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Thu, 8 Mar 2018 15:22:21 +0000 Subject: [PATCH 11/18] simplified dictionary matching to not rely on NS types --- Tests/DocoptTests/DocoptTestCasesTests.swift | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Tests/DocoptTests/DocoptTestCasesTests.swift b/Tests/DocoptTests/DocoptTestCasesTests.swift index f616ff5..5f972f5 100644 --- a/Tests/DocoptTests/DocoptTestCasesTests.swift +++ b/Tests/DocoptTests/DocoptTestCasesTests.swift @@ -30,21 +30,20 @@ class DocoptTestCasesTests: XCTestCase { static func valuesMatch(v1 : Any, v2 : Any) -> Bool { if let a1 = v1 as? [Any], let a2 = v2 as? [Any] { - return !arraysMatch(a1: a1, a2: a2) + return arraysMatch(a1: a1, a2: a2) } + if let i1 = v1 as? Int, let i2 = v2 as? Int { return i1 == i2 } - if let s1 = v1 as? String, let s2 = v2 as? String { - return s1 == s2 - } if let b1 = v1 as? Bool, let b2 = v2 as? Bool { return b1 == b2 } - if let n1 = v1 as? NSNull, let n2 = v2 as? NSNull { - return n1 == n2 + if let s1 = v1 as? String, let s2 = v2 as? String { + return s1 == s2 } - return false + + return "\(v1)" == "\(v2)" } static func arraysMatch(a1 : [Any], a2 : [Any]) -> Bool { From 91fc7807924e592de57defccde81c580d3cc0b06 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Thu, 8 Mar 2018 15:24:56 +0000 Subject: [PATCH 12/18] added explicit null matching --- Tests/DocoptTests/DocoptTestCasesTests.swift | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Tests/DocoptTests/DocoptTestCasesTests.swift b/Tests/DocoptTests/DocoptTestCasesTests.swift index 5f972f5..ea8ef4e 100644 --- a/Tests/DocoptTests/DocoptTestCasesTests.swift +++ b/Tests/DocoptTests/DocoptTestCasesTests.swift @@ -32,7 +32,6 @@ class DocoptTestCasesTests: XCTestCase { if let a1 = v1 as? [Any], let a2 = v2 as? [Any] { return arraysMatch(a1: a1, a2: a2) } - if let i1 = v1 as? Int, let i2 = v2 as? Int { return i1 == i2 } @@ -42,15 +41,17 @@ class DocoptTestCasesTests: XCTestCase { if let s1 = v1 as? String, let s2 = v2 as? String { return s1 == s2 } - + if let n1 = v1 as? NSNull, let n2 = v2 as? NSNull { + return n1 == n2 + } return "\(v1)" == "\(v2)" } - + static func arraysMatch(a1 : [Any], a2 : [Any]) -> Bool { if a1.count != a2.count { return false } - + var index = 0 for v1 in a1 { if !valuesMatch(v1: v1, v2: a2[index]) { @@ -60,7 +61,7 @@ class DocoptTestCasesTests: XCTestCase { } return true } - + static func dictionariesMatch(d1 : [String:Any], d2 : [String:Any]) -> Bool { // filter out all matching key/value pairs let remaining = d1.filter { (key, value) -> Bool in @@ -69,11 +70,11 @@ class DocoptTestCasesTests: XCTestCase { } return true } - + // there should be nothing left if the dictionaries match return remaining.count == 0 } - + func testTestCases() { let rawTestCases = fixturesFileContents() let parser = DocoptTestCaseParser(rawTestCases) From ed01ce276e33bc0ee3afe87adbba57705e6f86fc Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Thu, 8 Mar 2018 16:30:12 +0000 Subject: [PATCH 13/18] Simplified testing code. --- Tests/DocoptTests/DocoptTestCasesTests.swift | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Tests/DocoptTests/DocoptTestCasesTests.swift b/Tests/DocoptTests/DocoptTestCasesTests.swift index ea8ef4e..8aa540a 100644 --- a/Tests/DocoptTests/DocoptTestCasesTests.swift +++ b/Tests/DocoptTests/DocoptTestCasesTests.swift @@ -29,6 +29,9 @@ class DocoptTestCasesTests: XCTestCase { } static func valuesMatch(v1 : Any, v2 : Any) -> Bool { + if let d1 = v1 as? [String:Any], let d2 = v2 as? [String:Any] { + return dictionariesMatch(d1: d1, d2: d2) + } if let a1 = v1 as? [Any], let a2 = v2 as? [Any] { return arraysMatch(a1: a1, a2: a2) } @@ -89,18 +92,7 @@ class DocoptTestCasesTests: XCTestCase { DocoptError.errorMessage = nil } - if let expectedDictionary = expectedOutput as? [String:Any], - let resultDictionary = result as? [String:Any] { - if !DocoptTestCasesTests.dictionariesMatch(d1: expectedDictionary, d2: resultDictionary) - { - XCTAssert(false, - "Test \(testCase.name) failed. Expected:\n\(expectedDictionary)\n\n, got: \(resultDictionary)\n\n") - } - } else if let expectedString = expectedOutput as? String, - let resultString = result as? String { - XCTAssertTrue(resultString == expectedString, - "Test \(testCase.name) failed. Expected:\n\(expectedString)\n\n, got: \(resultString)\n\n") - } else { + if !DocoptTestCasesTests.valuesMatch(v1: expectedOutput, v2: result) { XCTFail("Test \(testCase.name) failed. Expected:\n\(expectedOutput)\n\n, got: \(result)\n\n\(testCase.usage)\n\(String(describing: testCase.arguments))\n\n") } } @@ -112,8 +104,8 @@ class DocoptTestCasesTests: XCTestCase { // we'll fail to find the bundle path here. // As a temporary workaround, we can fall back on a relative path from the executable. // This is fragile as it relies on the assumption that we know where SwiftPM will - // put it, and where the testcases file lives relative to it, but it's - // better than just disabling all the tests... + // put the build products, and where the testcases file lives relative to them, + // but it's better than just disabling all the tests... let path = exeURL.appendingPathComponent("../../../../Tests/DocoptTests/testcases.docopt").standardized.path return path } From 3ea43569e913afce06f6c5c6e2c673f33e5df0f6 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Thu, 8 Mar 2018 17:21:15 +0000 Subject: [PATCH 14/18] Split generic value matching into its own file for clarity, and added some tests for it. Hooked up some missing tests for Linux. --- Tests/DocoptTests/DocoptTestCasesTests.swift | 54 +--------- Tests/DocoptTests/DocoptTests.swift | 24 +++++ Tests/DocoptTests/ValueMatching.swift | 106 +++++++++++++++++++ Tests/DocoptTests/ValueMatchingTests.swift | 47 ++++++++ Tests/LinuxMain.swift | 2 + 5 files changed, 182 insertions(+), 51 deletions(-) create mode 100644 Tests/DocoptTests/ValueMatching.swift create mode 100644 Tests/DocoptTests/ValueMatchingTests.swift diff --git a/Tests/DocoptTests/DocoptTestCasesTests.swift b/Tests/DocoptTests/DocoptTestCasesTests.swift index 8aa540a..0457f59 100644 --- a/Tests/DocoptTests/DocoptTestCasesTests.swift +++ b/Tests/DocoptTests/DocoptTestCasesTests.swift @@ -28,56 +28,6 @@ class DocoptTestCasesTests: XCTestCase { XCTAssertNotEqual(fixturesFileContents(), "", "Could not read fixtures file") } - static func valuesMatch(v1 : Any, v2 : Any) -> Bool { - if let d1 = v1 as? [String:Any], let d2 = v2 as? [String:Any] { - return dictionariesMatch(d1: d1, d2: d2) - } - if let a1 = v1 as? [Any], let a2 = v2 as? [Any] { - return arraysMatch(a1: a1, a2: a2) - } - if let i1 = v1 as? Int, let i2 = v2 as? Int { - return i1 == i2 - } - if let b1 = v1 as? Bool, let b2 = v2 as? Bool { - return b1 == b2 - } - if let s1 = v1 as? String, let s2 = v2 as? String { - return s1 == s2 - } - if let n1 = v1 as? NSNull, let n2 = v2 as? NSNull { - return n1 == n2 - } - return "\(v1)" == "\(v2)" - } - - static func arraysMatch(a1 : [Any], a2 : [Any]) -> Bool { - if a1.count != a2.count { - return false - } - - var index = 0 - for v1 in a1 { - if !valuesMatch(v1: v1, v2: a2[index]) { - return false - } - index += 1 - } - return true - } - - static func dictionariesMatch(d1 : [String:Any], d2 : [String:Any]) -> Bool { - // filter out all matching key/value pairs - let remaining = d1.filter { (key, value) -> Bool in - if let v2 = d2[key] { - return !valuesMatch(v1: value, v2: v2) - } - return true - } - - // there should be nothing left if the dictionaries match - return remaining.count == 0 - } - func testTestCases() { let rawTestCases = fixturesFileContents() let parser = DocoptTestCaseParser(rawTestCases) @@ -92,7 +42,7 @@ class DocoptTestCasesTests: XCTestCase { DocoptError.errorMessage = nil } - if !DocoptTestCasesTests.valuesMatch(v1: expectedOutput, v2: result) { + if !valuesMatch(expectedOutput, result) { XCTFail("Test \(testCase.name) failed. Expected:\n\(expectedOutput)\n\n, got: \(result)\n\n\(testCase.usage)\n\(String(describing: testCase.arguments))\n\n") } } @@ -131,6 +81,8 @@ class DocoptTestCasesTests: XCTestCase { } static var allTests = [ + ("testTestCasesFileExists", testTestCasesFileExists), + ("testFixturesFileCanBeOpened", testFixturesFileCanBeOpened), ("testTestCases", testTestCases), ] } diff --git a/Tests/DocoptTests/DocoptTests.swift b/Tests/DocoptTests/DocoptTests.swift index 93225a6..42d1d1c 100644 --- a/Tests/DocoptTests/DocoptTests.swift +++ b/Tests/DocoptTests/DocoptTests.swift @@ -347,6 +347,30 @@ class DocoptTests: XCTestCase { } XCTAssertEqual(result!.count, fixture.count) } + + static var allTests = [ + ("testPatternFlat", testPatternFlat), + ("testParseDefaults", testParseDefaults), + ("testParseSection", testParseSection), + ("testFormalUsage", testFormalUsage), + ("testParseArgv", testParseArgv), + ("testOptionParse", testOptionParse), + ("testOptionName", testOptionName), + ("testParsePattern", testParsePattern), + ("testOptionMatch", testOptionMatch), + ("testArgumentMatch", testArgumentMatch), + ("testCommandMatch", testCommandMatch), + ("testOptionalMatch", testOptionalMatch), + ("testRequiredMatch", testRequiredMatch), + ("testEitherMatch", testEitherMatch), + ("testOneOrMoreMatch", testOneOrMoreMatch), + ("testPatternEither", testPatternEither), + ("testFixRepeatingArguments", testFixRepeatingArguments), + ("testListArgumentMatch", testListArgumentMatch), + ("testBasicPatternMatch", testBasicPatternMatch), + ("testSet", testSet), + ("testDocopt", testDocopt), + ] } internal func ==(lhs: MatchResult, rhs: MatchResult) -> Bool { diff --git a/Tests/DocoptTests/ValueMatching.swift b/Tests/DocoptTests/ValueMatching.swift new file mode 100644 index 0000000..fb415b5 --- /dev/null +++ b/Tests/DocoptTests/ValueMatching.swift @@ -0,0 +1,106 @@ +// +// ValueMatching.swift +// Docopt +// +// Created by Sam Deane on 08/03/2018. +// + +import Foundation + +/** + Crude generic matching. + + Implements a slightly fuzzy generic test for equality between + two values of unknown type. + + Allows us to check dictionaries, arrays for equality, in + situations where values might be equivalent but not strictly equal. + + For example matching the integer 1 against the string "1" for a + given key in a dictionary. + */ + +/** + If the values are both the same type, and it's equatable, then + it's easy. + */ + +internal func valuesMatch(_ v1 : T, _ v2 : T) -> Bool { + return v1 == v2 +} + +/** + If the values could be any type, we work through a series of + alternatives attempting to case them to basic types, then + compare those. + + Worst-case scenario we fall back on describing both values + as strings and comparing that. + */ + +internal func valuesMatch(_ v1 : Any, _ v2 : Any) -> Bool { + if let d1 = v1 as? [String:Any], let d2 = v2 as? [String:Any] { + return d1.matches(d2) + } + if let a1 = v1 as? [Any], let a2 = v2 as? [Any] { + return a1.matches(a2) + } + if let i1 = v1 as? Int, let i2 = v2 as? Int { + return i1 == i2 + } + if let b1 = v1 as? Bool, let b2 = v2 as? Bool { + return b1 == b2 + } + if let s1 = v1 as? String, let s2 = v2 as? String { + return s1 == s2 + } + if let n1 = v1 as? NSNull, let n2 = v2 as? NSNull { + return n1 == n2 + } + + return "\(v1)" == "\(v2)" +} + +/** + Arrays just match each element in turn. + */ + +extension Array { + func matches(_ other : [Any]) -> Bool { + if count != other.count { + return false + } + + var index = 0 + for value in self { + let otherValue = other[index] + if !valuesMatch(value, otherValue) { + return false + } + index += 1 + } + return true + } +} + +/** + Dictionaries match by filtering out all matching + key/value pairs. If there's nothing left + */ + +extension Dictionary { + func matches(_ other : [String:Any]) -> Bool { + if self.count != other.count { + return false + } + + let remaining = self.filter { (key, value) -> Bool in + if let otherValue = other[key as! String] { + return !valuesMatch(value, otherValue) + } + return true + } + + return remaining.count == 0 + } +} diff --git a/Tests/DocoptTests/ValueMatchingTests.swift b/Tests/DocoptTests/ValueMatchingTests.swift new file mode 100644 index 0000000..ec44916 --- /dev/null +++ b/Tests/DocoptTests/ValueMatchingTests.swift @@ -0,0 +1,47 @@ +// +// ValueMatchingSwiftTests.swift +// Docopt +// +// Created by Sam Deane on 08/03/2018. +// + +import XCTest + +class ValueMatchingTests: XCTestCase { + func testNumberNumber() { + XCTAssertTrue(valuesMatch(1, 1)) + XCTAssertFalse(valuesMatch(1, 4)) + } + func testNumberString() { + XCTAssertTrue(valuesMatch(1, "1")) + XCTAssertFalse(valuesMatch(1, "4")) + } + func testStringString() { + XCTAssertTrue(valuesMatch("test", "test")) + XCTAssertFalse(valuesMatch("test", "blah")) + } + func testBoolBool() { + XCTAssertTrue(valuesMatch(false, false)) + XCTAssertFalse(valuesMatch(false, true)) + } + func testArrays() { + XCTAssertTrue(valuesMatch([10,20], ["10", "20"])) + XCTAssertFalse(valuesMatch([10,20], [20,10])) + XCTAssertFalse(valuesMatch([10,20], [10])) + XCTAssertFalse(valuesMatch([10,20], 10)) + } + func testDictionaries() { + XCTAssertTrue(valuesMatch(["name" : "test", "x" : 10, "y" : "20", "check" : false], ["x" : "10", "y" : 20, "name" : "test", "check" : false])) + } + + static var allTests = [ + ("testNumberNumber", testNumberNumber), + ("testNumberString", testNumberString), + ("testStringString", testStringString), + ("testBoolBool", testBoolBool), + ("testArrays", testArrays), + ("testDictionaries", testDictionaries), + ] + +} + diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 68d2117..a37d80c 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -3,4 +3,6 @@ import XCTest XCTMain([ testCase(DocoptTestCasesTests.allTests), + testCase(DocoptTests.allTests), + testCase(ValueMatchingTests.allTests) ]) From 66f9a7d64e6542427a1e4eed3d8ad5ace262cf11 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Thu, 8 Mar 2018 17:25:11 +0000 Subject: [PATCH 15/18] fixed docopt test on Linux --- Tests/DocoptTests/DocoptTests.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Tests/DocoptTests/DocoptTests.swift b/Tests/DocoptTests/DocoptTests.swift index 42d1d1c..89d1f57 100644 --- a/Tests/DocoptTests/DocoptTests.swift +++ b/Tests/DocoptTests/DocoptTests.swift @@ -341,11 +341,7 @@ class DocoptTests: XCTestCase { let doc = "Usage: prog [-v] A\n\n Options: -v Be verbose." let result = Docopt(doc, argv: ["arg"]).result let fixture : [String:Any] = ["-v": false, "A": "arg"] - for (key, value) in result! - { - XCTAssertEqual(value as! NSObject, fixture[key]! as! NSObject) - } - XCTAssertEqual(result!.count, fixture.count) + XCTAssertTrue(valuesMatch(result, fixture)) } static var allTests = [ From 01e0033e03a3b3adca6d156362303fb71fa999f5 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Sat, 10 Mar 2018 12:34:03 +0000 Subject: [PATCH 16/18] fixed warnings --- Sources/Docopt/LeafPattern.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Docopt/LeafPattern.swift b/Sources/Docopt/LeafPattern.swift index 46cd57b..a65c8a8 100644 --- a/Sources/Docopt/LeafPattern.swift +++ b/Sources/Docopt/LeafPattern.swift @@ -121,7 +121,7 @@ func ==(lhs: LeafPattern, rhs: LeafPattern) -> Bool { } else if let lval = lhs.value as? Int, let rval = rhs.value as? Int { valEqual = lval == rval } else { - valEqual = lhs.value as? AnyObject === rhs.value as? AnyObject + valEqual = lhs.value as AnyObject === rhs.value as AnyObject } return lhs.name == rhs.name && valEqual } From d6245a1d288d296873a0739f78c4eddecf687d97 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Sat, 10 Mar 2018 16:00:39 +0000 Subject: [PATCH 17/18] Added Linux testing to Travis. --- .travis.yml | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 05a71d8..01df2ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,29 @@ -language: objective-c -osx_image: xcode9 -install: - - gem install xcpretty -script: - - xcodebuild -scheme 'Docopt' clean build test | xcpretty -c +matrix: + include: + - os: linux + language: generic + dist: trusty + sudo: required + env: + - SWIFT_BRANCH=swift-4.1-branch + - SWIFT_VERSION=swift-4.1-DEVELOPMENT-SNAPSHOT-2018-03-09-a + install: + - sudo apt-get install clang libicu-dev + - mkdir swift + - curl https://swift.org/builds/$SWIFT_BRANCH/ubuntu1404/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu14.04.tar.gz -s | tar xz -C swift &> /dev/null + - export PATH=$(pwd)/swift/$SWIFT_VERSION-ubuntu14.04/usr/bin:$PATH + - swift package update + script: + - swift test + + - os: osx + language: objective-c + osx_image: xcode9 + install: + - gem install xcpretty + script: + - xcodebuild -scheme 'Docopt' clean build test | xcpretty -c + + notifications: email: false From aebca51028e88e441ac0bb5296c9432bbb89e635 Mon Sep 17 00:00:00 2001 From: Sam Deane Date: Sat, 10 Mar 2018 16:20:53 +0000 Subject: [PATCH 18/18] Reversed warning fix for macOS which doesn't build on Linux. Not quite sure what's going on here - I thought I was testing with Swift 4.1 on both platforms, but it appears not... --- Sources/Docopt/LeafPattern.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Docopt/LeafPattern.swift b/Sources/Docopt/LeafPattern.swift index a65c8a8..46cd57b 100644 --- a/Sources/Docopt/LeafPattern.swift +++ b/Sources/Docopt/LeafPattern.swift @@ -121,7 +121,7 @@ func ==(lhs: LeafPattern, rhs: LeafPattern) -> Bool { } else if let lval = lhs.value as? Int, let rval = rhs.value as? Int { valEqual = lval == rval } else { - valEqual = lhs.value as AnyObject === rhs.value as AnyObject + valEqual = lhs.value as? AnyObject === rhs.value as? AnyObject } return lhs.name == rhs.name && valEqual }