diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..9f55b2c --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +3.0 diff --git a/.travis.yml b/.travis.yml index 38a8813..d6f23d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,11 @@ -os: - - osx -osx_image: xcode7.3 +osx_image: xcode8 language: objective-c + install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./swiftlint.sh; fi script: - xcodebuild clean build -project Compass.xcodeproj -scheme Compass-iOS -sdk iphonesimulator - - xcodebuild test -project Compass.xcodeproj -scheme Compass-iOS -sdk iphonesimulator + - xcodebuild test -project Compass.xcodeproj -scheme "Compass-iOS" -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6,OS=10.0' - xcodebuild clean build -project Compass.xcodeproj -scheme Compass-Mac -sdk macosx - xcodebuild test -project Compass.xcodeproj -scheme Compass-Mac -sdk macosx notifications: diff --git a/Compass.podspec b/Compass.podspec index 2334e6b..473d9f0 100644 --- a/Compass.podspec +++ b/Compass.podspec @@ -15,4 +15,6 @@ Pod::Spec.new do |s| s.source_files = 'Sources/**/*' s.frameworks = 'Foundation' + + s.pod_target_xcconfig = { 'SWIFT_VERSION' => '3.0' } end diff --git a/Compass.xcodeproj/project.pbxproj b/Compass.xcodeproj/project.pbxproj index cd7a1c6..37c03bf 100644 --- a/Compass.xcodeproj/project.pbxproj +++ b/Compass.xcodeproj/project.pbxproj @@ -263,9 +263,11 @@ TargetAttributes = { D5B2E89E1C3A780C00C0327D = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0800; }; D5B2E8A81C3A780C00C0327D = { CreatedOnToolsVersion = 7.2; + LastSwiftMigration = 0800; }; D5C6293F1C3A7FAA007F7B7C = { CreatedOnToolsVersion = 7.2; @@ -495,6 +497,7 @@ PRODUCT_NAME = Compass; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -514,6 +517,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "no.hyper.Compass-iOS"; PRODUCT_NAME = Compass; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -527,6 +531,7 @@ PRODUCT_BUNDLE_IDENTIFIER = no.hyper.CompassTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -539,6 +544,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = no.hyper.CompassTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -563,6 +569,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -586,6 +593,7 @@ PRODUCT_NAME = Compass; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -601,6 +609,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "no.hyper.Compass-MacTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -616,6 +625,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "no.hyper.Compass-MacTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/README.md b/README.md index 631804a..86de747 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![License](https://img.shields.io/cocoapods/l/Compass.svg?style=flat)](http://cocoadocs.org/docsets/Compass) [![Platform](https://img.shields.io/cocoapods/p/Compass.svg?style=flat)](http://cocoadocs.org/docsets/Compass) [![CI Status](http://img.shields.io/travis/hyperoslo/Compass.svg?style=flat)](https://travis-ci.org/hyperoslo/Compass) +![Swift](https://img.shields.io/badge/%20in-swift%203.0-orange.svg) Compass helps you setup a central navigation system for your application. This has many benefits, one of them being that controllers can now be diff --git a/Sources/Compass.swift b/Sources/Compass.swift index 743296c..88d421a 100644 --- a/Sources/Compass.swift +++ b/Sources/Compass.swift @@ -8,7 +8,7 @@ public struct Compass { concreteMatchCount: Int, wildcardMatchCount: Int) - private static var internalScheme = "" + fileprivate static var internalScheme = "" public static var delimiter: String = ":" public static var scheme: String { @@ -18,21 +18,21 @@ public struct Compass { public static var routes = [String]() - public static func parse(url: NSURL, payload: Any? = nil) -> Location? { - let path = url.absoluteString.substringFromIndex(scheme.endIndex) + public static func parse(url: URL, payload: Any? = nil) -> Location? { + let path = url.absoluteString.substring(from: scheme.endIndex) - guard !(path.containsString("?") || path.containsString("#")) else { - return parseAsURL(url, payload: payload) + guard !(path.contains("?") || path.contains("#")) else { + return parseComponents(url: url, payload: payload) } let results: [Result] = routes.flatMap { - return findMatch($0, pathString: path) - }.sort { (r1: Result, r2: Result) in - if r1.concreteMatchCount == r2.concreteMatchCount { - return r1.wildcardMatchCount > r2.wildcardMatchCount - } + return findMatch(routeString: $0, pathString: path) + }.sorted { (r1: Result, r2: Result) in + if r1.concreteMatchCount == r2.concreteMatchCount { + return r1.wildcardMatchCount > r2.wildcardMatchCount + } - return r1.concreteMatchCount > r2.concreteMatchCount + return r1.concreteMatchCount > r2.concreteMatchCount } if let result = results.first { @@ -42,10 +42,10 @@ public struct Compass { return nil } - static func parseAsURL(url: NSURL, payload: Any? = nil) -> Location? { + static func parseComponents(url: URL, payload: Any? = nil) -> Location? { guard let route = url.host else { return nil } - let urlComponents = NSURLComponents(URL: url, resolvingAgainstBaseURL: false) + let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) var arguments = [String : String]() urlComponents?.queryItems?.forEach { queryItem in @@ -71,7 +71,8 @@ public struct Compass { for (route, path) in zip(routes, paths) { if route.hasPrefix("{") { - let key = route.replace("{", with: "").replace("}", with: "") + let key = route.replacingOccurrences(of: "{", with: "") + .replacingOccurrences(of: "}", with: "") arguments[key] = path wildcardMatchCount += 1 @@ -94,8 +95,8 @@ public struct Compass { extension Compass { - public static func navigate(urn: String, scheme: String = Compass.scheme) { - guard let url = NSURL(string: "\(scheme)\(urn)") else { return } - openURL(url) + public static func navigate(to urn: String, scheme: String = Compass.scheme) { + guard let url = URL(string: "\(scheme)\(urn)") else { return } + open(url: url) } } diff --git a/Sources/Router.swift b/Sources/Router.swift index 94891a2..26ccefe 100644 --- a/Sources/Router.swift +++ b/Sources/Router.swift @@ -1,17 +1,17 @@ -public enum RouteError: ErrorType { - case NotFound - case InvalidArguments(Location) - case InvalidPayload(Location) +public enum RouteError: Error { + case notFound + case invalidArguments(Location) + case invalidPayload(Location) } public protocol Routable { - func navigate(to location: Location, from currentController: Controller) throws + func navigate(to location: Location, from currentController: CurrentController) throws } public protocol ErrorRoutable { - func handle(routeError: ErrorType, from currentController: Controller) + func handle(routeError error: Error, from currentController: CurrentController) } public struct Router: Routable { @@ -21,16 +21,16 @@ public struct Router: Routable { public init() {} - public func navigate(to location: Location, from currentController: Controller) { + public func navigate(to location: Location, from currentController: CurrentController) { guard let route = routes[location.path] else { - errorRoute?.handle(RouteError.NotFound, from: currentController) + errorRoute?.handle(routeError: RouteError.notFound, from: currentController) return } do { try route.navigate(to: location, from: currentController) } catch { - errorRoute?.handle(error, from: currentController) + errorRoute?.handle(routeError: error, from: currentController) } } } diff --git a/Sources/String+Extensions.swift b/Sources/String+Extensions.swift index eacc3e9..726e51c 100644 --- a/Sources/String+Extensions.swift +++ b/Sources/String+Extensions.swift @@ -2,25 +2,21 @@ import Foundation extension String { - func split(delimiter: String) -> [String] { - let components = componentsSeparatedByString(delimiter) + func split(_ delimiter: String) -> [String] { + let components = self.components(separatedBy: delimiter) return components != [""] ? components : [] } - func replace(string: String, with withString: String) -> String { - return stringByReplacingOccurrencesOfString(string, withString: withString) - } - func queryParameters() -> [String: String] { var parameters = [String: String]() - let separatorCharacters = NSCharacterSet(charactersInString: "&;") - self.componentsSeparatedByCharactersInSet(separatorCharacters).forEach { (pair) in + let separatorCharacters = CharacterSet(charactersIn: "&;") + self.components(separatedBy: separatorCharacters).forEach { (pair) in - if let equalSeparator = pair.rangeOfString("=") { - let name = pair.substringToIndex(equalSeparator.startIndex) - let value = pair.substringFromIndex(equalSeparator.startIndex.advancedBy(1)) - let cleaned = value.stringByRemovingPercentEncoding ?? value + if let equalSeparator = pair.range(of: "=") { + let name = pair.substring(to: equalSeparator.lowerBound) + let value = pair.substring(from: pair.index(equalSeparator.lowerBound, offsetBy: 1)) + let cleaned = value.removingPercentEncoding ?? value parameters[name] = cleaned } diff --git a/Sources/TypeAlias.swift b/Sources/TypeAlias.swift index 2fcabf7..00b8f50 100644 --- a/Sources/TypeAlias.swift +++ b/Sources/TypeAlias.swift @@ -5,15 +5,15 @@ #endif #if os(OSX) - public typealias Controller = NSViewController + public typealias CurrentController = NSViewController - func openURL(URL: NSURL) { - NSWorkspace.sharedWorkspace().openURL(URL) + func open(url: URL) { + NSWorkspace.shared().open(url) } #else - public typealias Controller = UIViewController + public typealias CurrentController = UIViewController - func openURL(URL: NSURL) { - UIApplication.sharedApplication().openURL(URL) + func open(url: URL) { + UIApplication.shared.openURL(url) } #endif diff --git a/Tests/Compass/CompassTests.swift b/Tests/Compass/CompassTests.swift index 21a3fe0..38a127c 100644 --- a/Tests/Compass/CompassTests.swift +++ b/Tests/Compass/CompassTests.swift @@ -27,9 +27,9 @@ class CompassTests: XCTestCase { } func testParseArguments() { - let url = NSURL(string: "compassTests://profile:testUser")! + let url = URL(string: "compassTests://profile:testUser")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -39,11 +39,11 @@ class CompassTests: XCTestCase { } func testParsePayload() { - let url = NSURL(string: "compassTests://profile:testUser")! + let url = URL(string: "compassTests://profile:testUser")! typealias Payload = (firstName: String, lastName: String) - guard let location = Compass.parse(url, payload: Payload(firstName: "foo", lastName: "bar")) else { + guard let location = Compass.parse(url: url, payload: Payload(firstName: "foo", lastName: "bar")) else { XCTFail("Compass parsing failed") return } @@ -55,9 +55,9 @@ class CompassTests: XCTestCase { } func testParseRouteConcreateMatchCount() { - let url = NSURL(string: "compassTests://profile:admin")! + let url = URL(string: "compassTests://profile:admin")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -67,9 +67,9 @@ class CompassTests: XCTestCase { } func testParseRouteWildcardMatchCount() { - let url = NSURL(string: "compassTests://profile:jack")! + let url = URL(string: "compassTests://profile:jack")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -79,9 +79,9 @@ class CompassTests: XCTestCase { } func testParseRouteSamePrefix() { - let url = NSURL(string: "compassTests://user:list")! + let url = URL(string: "compassTests://user:list")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -91,9 +91,9 @@ class CompassTests: XCTestCase { } func testParseMultipleArguments() { - let url = NSURL(string: "compassTests://user:list:1:admin")! + let url = URL(string: "compassTests://user:list:1:admin")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -104,9 +104,9 @@ class CompassTests: XCTestCase { } func testParseMultipleArgumentsWithFirstWildcard() { - let url = NSURL(string: "compassTests://12:user:list:1:admin")! + let url = URL(string: "compassTests://12:user:list:1:admin")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -118,9 +118,9 @@ class CompassTests: XCTestCase { } func testParseWithoutArguments() { - let url = NSURL(string: "compassTests://login")! + let url = URL(string: "compassTests://login")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -130,9 +130,9 @@ class CompassTests: XCTestCase { } func testParseRegularURLWithFragments() { - let url = NSURL(string: "compassTests://callback/#access_token=IjvcgrkQk1p7TyJxKa26rzM1wBMFZW6XoHK4t5Gkt1xQLTN8l7ppR0H3EZXpoP0uLAN49oCDqTHsvnEV&token_type=Bearer&expires_in=3600")! + let url = URL(string: "compassTests://callback/#access_token=IjvcgrkQk1p7TyJxKa26rzM1wBMFZW6XoHK4t5Gkt1xQLTN8l7ppR0H3EZXpoP0uLAN49oCDqTHsvnEV&token_type=Bearer&expires_in=3600")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -145,9 +145,9 @@ class CompassTests: XCTestCase { } func testParseRegularURLWithFragmentsAndGoogleOAuth2AccessToken() { - let url = NSURL(string: "compassTests://callback/#access_token=ya29.Ci8nA1pNVMFffHkS5-sXooNGvTB9q8QPtoM56sWpipRyjhwwEiKyZxvRQTR8saqWzQ&token_type=Bearer&expires_in=3600")! + let url = URL(string: "compassTests://callback/#access_token=ya29.Ci8nA1pNVMFffHkS5-sXooNGvTB9q8QPtoM56sWpipRyjhwwEiKyZxvRQTR8saqWzQ&token_type=Bearer&expires_in=3600")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -160,9 +160,9 @@ class CompassTests: XCTestCase { } func testParseRegularURLWithFragmentsAndAlternativeAccessToken() { - let url = NSURL(string: "compassTests://callback/#access_token=ya29.Ci8nA1pNVMFffHkS5-sXooNGvTB9q8QPtoM56sWpipRyjhwwEiKyZxvRQTR8saqWzQ=&token_type=Bearer&expires_in=3600")! + let url = URL(string: "compassTests://callback/#access_token=ya29.Ci8nA1pNVMFffHkS5-sXooNGvTB9q8QPtoM56sWpipRyjhwwEiKyZxvRQTR8saqWzQ=&token_type=Bearer&expires_in=3600")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -175,9 +175,9 @@ class CompassTests: XCTestCase { } func testParseRegularURLWithSlashQuery() { - let url = NSURL(string: "compassTests://callback/?access_token=Yo0OMrVZbRWNmgA6BT99hyuTUTNRGvqEEAQyeN1eslclzhFD0M8AidB4Z7Vs2NU8WoSNW0vYb961O38l&token_type=Bearer&expires_in=3600")! + let url = URL(string: "compassTests://callback/?access_token=Yo0OMrVZbRWNmgA6BT99hyuTUTNRGvqEEAQyeN1eslclzhFD0M8AidB4Z7Vs2NU8WoSNW0vYb961O38l&token_type=Bearer&expires_in=3600")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -190,9 +190,9 @@ class CompassTests: XCTestCase { } func testParseRegularURLWithSlashQueryAndGoogleOAuth2AccessToken() { - let url = NSURL(string: "compassTests://callback/?access_token=ya29.Ci8nA1pNVMFffHkS5-sXooNGvTB9q8QPtoM56sWpipRyjhwwEiKyZxvRQTR8saqWzQ&token_type=Bearer&expires_in=3600")! + let url = URL(string: "compassTests://callback/?access_token=ya29.Ci8nA1pNVMFffHkS5-sXooNGvTB9q8QPtoM56sWpipRyjhwwEiKyZxvRQTR8saqWzQ&token_type=Bearer&expires_in=3600")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -205,9 +205,9 @@ class CompassTests: XCTestCase { } func testParseRegularURLWithSlashQueryAndAlternativeAccessToken() { - let url = NSURL(string: "compassTests://callback/?access_token=ya29.Ci8nA1pNVMFffHkS5-sXooNGvTB9q8QPtoM56sWpipRyjhwwEiKyZxvRQTR8saqWzQ=&token_type=Bearer&expires_in=3600")! + let url = URL(string: "compassTests://callback/?access_token=ya29.Ci8nA1pNVMFffHkS5-sXooNGvTB9q8QPtoM56sWpipRyjhwwEiKyZxvRQTR8saqWzQ=&token_type=Bearer&expires_in=3600")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -220,9 +220,9 @@ class CompassTests: XCTestCase { } func testParseRegularURLWithQuery() { - let url = NSURL(string: "compassTests://callback?access_token=Yo0OMrVZbRWNmgA6BT99hyuTUTNRGvqEEAQyeN1eslclzhFD0M8AidB4Z7Vs2NU8WoSNW0vYb961O38l&token_type=Bearer&expires_in=3600")! + let url = URL(string: "compassTests://callback?access_token=Yo0OMrVZbRWNmgA6BT99hyuTUTNRGvqEEAQyeN1eslclzhFD0M8AidB4Z7Vs2NU8WoSNW0vYb961O38l&token_type=Bearer&expires_in=3600")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -235,9 +235,9 @@ class CompassTests: XCTestCase { } func testParseRegularURLWithQueryAndGoogleOAuth2AccessToken() { - let url = NSURL(string: "compassTests://callback?access_token=ya29.Ci8nA1pNVMFffHkS5-sXooNGvTB9q8QPtoM56sWpipRyjhwwEiKyZxvRQTR8saqWzQ&token_type=Bearer&expires_in=3600")! + let url = URL(string: "compassTests://callback?access_token=ya29.Ci8nA1pNVMFffHkS5-sXooNGvTB9q8QPtoM56sWpipRyjhwwEiKyZxvRQTR8saqWzQ&token_type=Bearer&expires_in=3600")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } @@ -250,9 +250,9 @@ class CompassTests: XCTestCase { } func testParseRegularURLWithQueryAndAlternativeAccessToken() { - let url = NSURL(string: "compassTests://callback?access_token=ya29.Ci8nA1pNVMFffHkS5-sXooNGvTB9q8QPtoM56sWpipRyjhwwEiKyZxvRQTR8saqWzQ=&token_type=Bearer&expires_in=3600")! + let url = URL(string: "compassTests://callback?access_token=ya29.Ci8nA1pNVMFffHkS5-sXooNGvTB9q8QPtoM56sWpipRyjhwwEiKyZxvRQTR8saqWzQ=&token_type=Bearer&expires_in=3600")! - guard let location = Compass.parse(url) else { + guard let location = Compass.parse(url: url) else { XCTFail("Compass parsing failed") return } diff --git a/Tests/Compass/Helpers.swift b/Tests/Compass/Helpers.swift index 68e7061..2585b38 100644 --- a/Tests/Compass/Helpers.swift +++ b/Tests/Compass/Helpers.swift @@ -7,53 +7,45 @@ class TestRoute: Routable { var resolved = false - func navigate(to location: Location, from currentController: Controller) throws { + func navigate(to location: Location, from currentController: CurrentController) throws { resolved = true } } class ThrowableRoute: Routable { - enum Error: ErrorType { + enum InternalError: Error { case Unknown } - func navigate(to location: Location, from currentController: Controller) throws { - throw Error.Unknown + func navigate(to location: Location, from currentController: CurrentController) throws { + throw InternalError.Unknown } } class ErrorRoute: ErrorRoutable { - var error: ErrorType? + var error: Error? - func handle(routeError: ErrorType, from currentController: Controller) { - error = routeError + func handle(routeError error: Error, from currentController: CurrentController) { + self.error = error } } // MARK: - Shuffle -extension CollectionType { +extension Array { /// Return a copy of `self` with its elements shuffled - func shuffle() -> [Generator.Element] { + func shuffle() -> [Element] { var list = Array(self) - list.shuffleInPlace() - return list - } -} - -extension MutableCollectionType where Index == Int { - /// Shuffle the elements of `self` in-place. - mutating func shuffleInPlace() { - // empty and single-element collections don't shuffle - if count < 2 { return } - for i in 0..