diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 0000000..3104629 --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,17 @@ +disabled_rules: + - line_length + - function_body_length + - trailing_comma + +opt_in_rules: + +included: + +excluded: + +identifier_name: + min_length: 1 + +cyclomatic_complexity: + warning: 12 + error: 20 diff --git a/CHANGELOG.md b/CHANGELOG.md index eaf9be1..e06f62d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. + +# [1.1.1](https://github.com/SwiftScream/URITemplate/compare/1.1.0...1.1.1) (2018-07-01) + +- Support back to iOS 9, tvOS 9, watchOS 2, macOS 10.11 +- Correct dynamic framework packaging + + # [1.1.0](https://github.com/SwiftScream/URITemplate/compare/1.0.0...1.1.0) (2018-05-19) diff --git a/Configuration/DynamicLibTarget-tv.xcconfig b/Configuration/DynamicLibTarget-tv.xcconfig index a3ab568..c5fcd7b 100644 --- a/Configuration/DynamicLibTarget-tv.xcconfig +++ b/Configuration/DynamicLibTarget-tv.xcconfig @@ -7,6 +7,8 @@ DYLIB_INSTALL_NAME_BASE = @rpath OTHER_LDFLAGS = -ObjC -all_load LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks +CODE_SIGN_IDENTITY = + DEFINES_MODULE = YES INFOPLIST_FILE = Source/DynamicFrameworkInfo.plist PRODUCT_BUNDLE_IDENTIFIER = com.swiftscream.URITemplate diff --git a/Configuration/DynamicLibTarget-watch.xcconfig b/Configuration/DynamicLibTarget-watch.xcconfig index 726760d..edffd10 100644 --- a/Configuration/DynamicLibTarget-watch.xcconfig +++ b/Configuration/DynamicLibTarget-watch.xcconfig @@ -7,6 +7,8 @@ DYLIB_INSTALL_NAME_BASE = @rpath OTHER_LDFLAGS = -ObjC -all_load LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks +CODE_SIGN_IDENTITY = + DEFINES_MODULE = YES INFOPLIST_FILE = Source/DynamicFrameworkInfo.plist PRODUCT_BUNDLE_IDENTIFIER = com.swiftscream.URITemplate diff --git a/Configuration/Project.xcconfig b/Configuration/Project.xcconfig index 0b2f031..9a4ef0d 100644 --- a/Configuration/Project.xcconfig +++ b/Configuration/Project.xcconfig @@ -10,4 +10,6 @@ ENABLE_STRICT_OBJC_MSGSEND = YES SWIFT_VERSION = 4.1 SWIFT_TREAT_WARNINGS_AS_ERRORS = YES +CURRENT_PROJECT_VERSION = 1.1.1 + #include "Warnings.xcconfig" diff --git a/Configuration/StaticLibTarget-ios.xcconfig b/Configuration/StaticLibTarget-ios.xcconfig index 31c99c6..ead8506 100644 --- a/Configuration/StaticLibTarget-ios.xcconfig +++ b/Configuration/StaticLibTarget-ios.xcconfig @@ -1,5 +1,5 @@ SDKROOT = iphoneos -IPHONEOS_DEPLOYMENT_TARGET = 10.0 +IPHONEOS_DEPLOYMENT_TARGET = 9.0 TARGETED_DEVICE_FAMILY = 1,2 SKIP_INSTALL = YES diff --git a/Configuration/StaticLibTarget-mac.xcconfig b/Configuration/StaticLibTarget-mac.xcconfig index 74af6b0..fd0ec53 100644 --- a/Configuration/StaticLibTarget-mac.xcconfig +++ b/Configuration/StaticLibTarget-mac.xcconfig @@ -1,5 +1,5 @@ SDKROOT = macosx -MACOSX_DEPLOYMENT_TARGET = 10.12 +MACOSX_DEPLOYMENT_TARGET = 10.11 EXECUTABLE_PREFIX = lib SKIP_INSTALL = YES diff --git a/Configuration/StaticLibTarget-tv.xcconfig b/Configuration/StaticLibTarget-tv.xcconfig index 1f26422..1326179 100644 --- a/Configuration/StaticLibTarget-tv.xcconfig +++ b/Configuration/StaticLibTarget-tv.xcconfig @@ -1,5 +1,5 @@ SDKROOT = appletvos -TVOS_DEPLOYMENT_TARGET = 10.0 +TVOS_DEPLOYMENT_TARGET = 9.0 TARGETED_DEVICE_FAMILY = 3 SKIP_INSTALL = YES diff --git a/Configuration/StaticLibTarget-watch.xcconfig b/Configuration/StaticLibTarget-watch.xcconfig index e565b5f..ea23236 100644 --- a/Configuration/StaticLibTarget-watch.xcconfig +++ b/Configuration/StaticLibTarget-watch.xcconfig @@ -1,5 +1,5 @@ SDKROOT = watchos -WATCHOS_DEPLOYMENT_TARGET = 3.0 +WATCHOS_DEPLOYMENT_TARGET = 2.0 TARGETED_DEVICE_FAMILY = 4 APPLICATION_EXTENSION_API_ONLY = YES diff --git a/Configuration/TestTarget-ios.xcconfig b/Configuration/TestTarget-ios.xcconfig index 7b9cf34..1a395d0 100644 --- a/Configuration/TestTarget-ios.xcconfig +++ b/Configuration/TestTarget-ios.xcconfig @@ -1,5 +1,5 @@ SDKROOT = iphoneos -IPHONEOS_DEPLOYMENT_TARGET = 10.0 +IPHONEOS_DEPLOYMENT_TARGET = 9.0 TARGETED_DEVICE_FAMILY = 1,2 LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks diff --git a/Configuration/TestTarget-tv.xcconfig b/Configuration/TestTarget-tv.xcconfig index c1fb8ac..3715af3 100644 --- a/Configuration/TestTarget-tv.xcconfig +++ b/Configuration/TestTarget-tv.xcconfig @@ -1,5 +1,5 @@ SDKROOT = appletvos -TVOS_DEPLOYMENT_TARGET = 10.0 +TVOS_DEPLOYMENT_TARGET = 9.0 TARGETED_DEVICE_FAMILY = 3 LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks diff --git a/Example-ios/AppDelegate.swift b/Example-ios/AppDelegate.swift index dcfb9b8..f0e9a78 100644 --- a/Example-ios/AppDelegate.swift +++ b/Example-ios/AppDelegate.swift @@ -18,10 +18,10 @@ import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - self.window = UIWindow(frame: UIScreen.main.bounds); - self.window?.rootViewController = ViewController(); + self.window = UIWindow(frame: UIScreen.main.bounds) + self.window?.rootViewController = ViewController() self.window?.makeKeyAndVisible() return true diff --git a/Example-ios/ViewController.swift b/Example-ios/ViewController.swift index bfff6da..4580c35 100644 --- a/Example-ios/ViewController.swift +++ b/Example-ios/ViewController.swift @@ -22,16 +22,24 @@ class ViewController: UIViewController { let view = UIView(frame: UIScreen.main.bounds) self.view = view - let template = try! URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") - let variables = ["owner": "SwiftScream", - "repo": "URITemplate", - "username": "alexdeem"] + do { + let template = try URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") + let variables = ["owner": "SwiftScream", + "repo": "URITemplate", + "username": "alexdeem"] - let urlString = try! template.process(variables: variables) + let urlString = try template.process(variables: variables) - let url = URL(string:urlString)! - print("Expanding \(template)\n with \(variables):\n") - print(url.absoluteString) + let url = URL(string: urlString)! + print("Expanding \(template)\n with \(variables):\n") + print(url.absoluteString) + } catch URITemplate.Error.malformedTemplate(let position, let reason) { + print("Failed parsing template (\(reason))") + } catch URITemplate.Error.expansionFailure(let position, let reason) { + print("Failed expanding template (\(reason))") + } catch { + print("Unexpected Failure") + } } } diff --git a/Example-mac/main.swift b/Example-mac/main.swift index e0133d5..700873b 100644 --- a/Example-mac/main.swift +++ b/Example-mac/main.swift @@ -22,7 +22,6 @@ let variables = ["owner": "SwiftScream", let urlString = try template.process(variables: variables) -let url = URL(string:urlString)! +let url = URL(string: urlString)! print("Expanding \(template)\n with \(variables):\n") print(url.absoluteString) - diff --git a/Example-tv/AppDelegate.swift b/Example-tv/AppDelegate.swift index 5f982a2..f0e9a78 100644 --- a/Example-tv/AppDelegate.swift +++ b/Example-tv/AppDelegate.swift @@ -20,8 +20,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - self.window = UIWindow(frame: UIScreen.main.bounds); - self.window?.rootViewController = ViewController(); + self.window = UIWindow(frame: UIScreen.main.bounds) + self.window?.rootViewController = ViewController() self.window?.makeKeyAndVisible() return true diff --git a/Example-tv/ViewController.swift b/Example-tv/ViewController.swift index bfff6da..4580c35 100644 --- a/Example-tv/ViewController.swift +++ b/Example-tv/ViewController.swift @@ -22,16 +22,24 @@ class ViewController: UIViewController { let view = UIView(frame: UIScreen.main.bounds) self.view = view - let template = try! URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") - let variables = ["owner": "SwiftScream", - "repo": "URITemplate", - "username": "alexdeem"] + do { + let template = try URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") + let variables = ["owner": "SwiftScream", + "repo": "URITemplate", + "username": "alexdeem"] - let urlString = try! template.process(variables: variables) + let urlString = try template.process(variables: variables) - let url = URL(string:urlString)! - print("Expanding \(template)\n with \(variables):\n") - print(url.absoluteString) + let url = URL(string: urlString)! + print("Expanding \(template)\n with \(variables):\n") + print(url.absoluteString) + } catch URITemplate.Error.malformedTemplate(let position, let reason) { + print("Failed parsing template (\(reason))") + } catch URITemplate.Error.expansionFailure(let position, let reason) { + print("Failed expanding template (\(reason))") + } catch { + print("Unexpected Failure") + } } } diff --git a/Example-watch/Extension/InterfaceController.swift b/Example-watch/Extension/InterfaceController.swift index db71071..5684b37 100644 --- a/Example-watch/Extension/InterfaceController.swift +++ b/Example-watch/Extension/InterfaceController.swift @@ -21,16 +21,24 @@ class InterfaceController: WKInterfaceController { override func awake(withContext context: Any?) { super.awake(withContext: context) - let template = try! URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") - let variables = ["owner": "SwiftScream", - "repo": "URITemplate", - "username": "alexdeem"] + do { + let template = try URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") + let variables = ["owner": "SwiftScream", + "repo": "URITemplate", + "username": "alexdeem"] - let urlString = try! template.process(variables: variables) + let urlString = try template.process(variables: variables) - let url = URL(string:urlString)! - print("Expanding \(template)\n with \(variables):\n") - print(url.absoluteString) + let url = URL(string: urlString)! + print("Expanding \(template)\n with \(variables):\n") + print(url.absoluteString) + } catch URITemplate.Error.malformedTemplate(let position, let reason) { + print("Failed parsing template (\(reason))") + } catch URITemplate.Error.expansionFailure(let position, let reason) { + print("Failed expanding template (\(reason))") + } catch { + print("Unexpected Failure") + } } } diff --git a/ScreamURITemplate.podspec b/ScreamURITemplate.podspec index 8e0fa35..5b346ba 100644 --- a/ScreamURITemplate.podspec +++ b/ScreamURITemplate.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "ScreamURITemplate" - s.version = "1.1.0" + s.version = "1.1.1" s.summary = "Robust and performant Swift implementation of RFC6570 URI Template" s.homepage = "https://github.com/SwiftScream/URITemplate" s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" } @@ -8,8 +8,8 @@ Pod::Spec.new do |s| s.source = { :git => "https://github.com/SwiftScream/URITemplate.git", :tag => "#{s.version}" } s.source_files = "Source/*.swift", "Source/Internal/*.swift" s.swift_version = "4.1" - s.ios.deployment_target = "10.0" - s.osx.deployment_target = "10.12" - s.watchos.deployment_target = "3.0" - s.tvos.deployment_target = "10.0" + s.ios.deployment_target = "9.0" + s.osx.deployment_target = "10.11" + s.watchos.deployment_target = "2.0" + s.tvos.deployment_target = "9.0" end diff --git a/Source/Internal/CharacterSets.swift b/Source/Internal/CharacterSets.swift index 0908871..16e221d 100644 --- a/Source/Internal/CharacterSets.swift +++ b/Source/Internal/CharacterSets.swift @@ -14,15 +14,15 @@ import Foundation -internal let unreservedCharacterSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn:"-._~")) -private let genDelimsCharacterSet = CharacterSet(charactersIn:":/?#[]@") -private let subDelimsCharacterSet = CharacterSet(charactersIn:"!$&'()*+,;=") +internal let unreservedCharacterSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "-._~")) +private let genDelimsCharacterSet = CharacterSet(charactersIn: ":/?#[]@") +private let subDelimsCharacterSet = CharacterSet(charactersIn: "!$&'()*+,;=") internal let reservedCharacterSet = genDelimsCharacterSet.union(subDelimsCharacterSet) internal let reservedAndUnreservedCharacterSet = reservedCharacterSet.union(unreservedCharacterSet) -internal let invertedLiteralCharacterSet = CharacterSet.illegalCharacters.union(CharacterSet.controlCharacters).union(CharacterSet(charactersIn:" \"'%<>\\^`{|}")) +internal let invertedLiteralCharacterSet = CharacterSet.illegalCharacters.union(CharacterSet.controlCharacters).union(CharacterSet(charactersIn: " \"'%<>\\^`{|}")) internal let literalCharacterSet = invertedLiteralCharacterSet.inverted -internal let hexCharacterSet = CharacterSet(charactersIn:"0123456789abcdefABCDEF") -internal let varnameCharacterSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn:"_%.")) +internal let hexCharacterSet = CharacterSet(charactersIn: "0123456789abcdefABCDEF") +internal let varnameCharacterSet = CharacterSet.alphanumerics.union(CharacterSet(charactersIn: "_%.")) internal let invertedVarnameCharacterSet = varnameCharacterSet.inverted -internal let expressionOperatorCharacterSet = CharacterSet(charactersIn:"+#./;?&=,!@|") +internal let expressionOperatorCharacterSet = CharacterSet(charactersIn: "+#./;?&=,!@|") internal let invertedDecimalDigitsCharacterSet = CharacterSet.decimalDigits.inverted diff --git a/Source/Internal/Components.swift b/Source/Internal/Components.swift index 9c2def5..a31f4bc 100644 --- a/Source/Internal/Components.swift +++ b/Source/Internal/Components.swift @@ -15,43 +15,43 @@ import Foundation internal protocol Component { - func expand(variables: [String:VariableValue]) throws -> String - var variableNames : [String] { get } + func expand(variables: [String: VariableValue]) throws -> String + var variableNames: [String] { get } } extension Component { - var variableNames : [String] { + var variableNames: [String] { return [] } } -internal struct LiteralComponent : Component { +internal struct LiteralComponent: Component { let literal: Substring init (_ string: Substring) { literal = string } - func expand(variables _: [String:VariableValue]) throws -> String { + func expand(variables _: [String: VariableValue]) throws -> String { let expansion = String(literal) guard let encodedExpansion = expansion.addingPercentEncoding(withAllowedCharacters: reservedAndUnreservedCharacterSet) else { throw URITemplate.Error.expansionFailure(position: literal.startIndex, reason: "Percent Encoding Failed") } - return encodedExpansion; + return encodedExpansion } } -internal struct LiteralPercentEncodedTripletComponent : Component { +internal struct LiteralPercentEncodedTripletComponent: Component { let literal: Substring init (_ string: Substring) { literal = string } - func expand(variables _: [String:VariableValue]) throws -> String { + func expand(variables _: [String: VariableValue]) throws -> String { return String(literal) } } -internal struct ExpressionComponent : Component { +internal struct ExpressionComponent: Component { let expressionOperator: ExpressionOperator let variableList: [VariableSpec] let templatePosition: String.Index @@ -62,11 +62,11 @@ internal struct ExpressionComponent : Component { self.templatePosition = templatePosition } - func expand(variables: [String:VariableValue]) throws -> String { + func expand(variables: [String: VariableValue]) throws -> String { let configuration = expressionOperator.expansionConfiguration() let expansions = try variableList.compactMap { variableSpec -> String? in guard let value = variables[String(variableSpec.name)] else { - return nil; + return nil } do { if let stringValue = value as? String { @@ -80,7 +80,7 @@ internal struct ExpressionComponent : Component { case .none: return try arrayValue.formatForTemplateExpansion(variableSpec: variableSpec, expansionConfiguration: configuration) } - } else if let dictionaryValue = value as? [String:String] { + } else if let dictionaryValue = value as? [String: String] { switch variableSpec.modifier { case .prefix: throw FormatError.failure(reason: "Prefix operator can only be applied to string") @@ -97,18 +97,18 @@ internal struct ExpressionComponent : Component { } } - if (expansions.count == 0) { + if expansions.count == 0 { return "" } - let joinedExpansions = expansions.joined(separator:configuration.separator) + let joinedExpansions = expansions.joined(separator: configuration.separator) if let prefix = configuration.prefix { return prefix + joinedExpansions } - return joinedExpansions; + return joinedExpansions } - var variableNames : [String] { + var variableNames: [String] { return variableList.map { variableSpec in return String(variableSpec.name) } diff --git a/Source/Internal/ExpressionOperator.swift b/Source/Internal/ExpressionOperator.swift index 8bb20d2..cfe9257 100644 --- a/Source/Internal/ExpressionOperator.swift +++ b/Source/Internal/ExpressionOperator.swift @@ -14,7 +14,7 @@ import Foundation -internal enum ExpressionOperator : Unicode.Scalar { +internal enum ExpressionOperator: Unicode.Scalar { case simple = "\0" case reserved = "+" case fragment = "#" @@ -27,53 +27,53 @@ internal enum ExpressionOperator : Unicode.Scalar { func expansionConfiguration() -> ExpansionConfiguration { switch self { case .simple: - return ExpansionConfiguration(percentEncodingAllowedCharacterSet:unreservedCharacterSet, - prefix:nil, - separator:",", - named:false, - omittOrphanedEquals:false) + return ExpansionConfiguration(percentEncodingAllowedCharacterSet: unreservedCharacterSet, + prefix: nil, + separator: ",", + named: false, + omittOrphanedEquals: false) case .reserved: - return ExpansionConfiguration(percentEncodingAllowedCharacterSet:reservedAndUnreservedCharacterSet, - prefix:nil, - separator:",", - named:false, - omittOrphanedEquals:false) + return ExpansionConfiguration(percentEncodingAllowedCharacterSet: reservedAndUnreservedCharacterSet, + prefix: nil, + separator: ",", + named: false, + omittOrphanedEquals: false) case .fragment: - return ExpansionConfiguration(percentEncodingAllowedCharacterSet:reservedAndUnreservedCharacterSet, - prefix:"#", - separator:",", - named:false, - omittOrphanedEquals:false) + return ExpansionConfiguration(percentEncodingAllowedCharacterSet: reservedAndUnreservedCharacterSet, + prefix: "#", + separator: ",", + named: false, + omittOrphanedEquals: false) case .label: - return ExpansionConfiguration(percentEncodingAllowedCharacterSet:unreservedCharacterSet, - prefix:".", - separator:".", - named:false, - omittOrphanedEquals:false) + return ExpansionConfiguration(percentEncodingAllowedCharacterSet: unreservedCharacterSet, + prefix: ".", + separator: ".", + named: false, + omittOrphanedEquals: false) case .pathSegment: - return ExpansionConfiguration(percentEncodingAllowedCharacterSet:unreservedCharacterSet, - prefix:"/", - separator:"/", - named:false, - omittOrphanedEquals:false) + return ExpansionConfiguration(percentEncodingAllowedCharacterSet: unreservedCharacterSet, + prefix: "/", + separator: "/", + named: false, + omittOrphanedEquals: false) case .pathStyle: - return ExpansionConfiguration(percentEncodingAllowedCharacterSet:unreservedCharacterSet, - prefix:";", - separator:";", - named:true, - omittOrphanedEquals:true) + return ExpansionConfiguration(percentEncodingAllowedCharacterSet: unreservedCharacterSet, + prefix: ";", + separator: ";", + named: true, + omittOrphanedEquals: true) case .query: - return ExpansionConfiguration(percentEncodingAllowedCharacterSet:unreservedCharacterSet, - prefix:"?", - separator:"&", - named:true, - omittOrphanedEquals:false) + return ExpansionConfiguration(percentEncodingAllowedCharacterSet: unreservedCharacterSet, + prefix: "?", + separator: "&", + named: true, + omittOrphanedEquals: false) case .queryContinuation: - return ExpansionConfiguration(percentEncodingAllowedCharacterSet:unreservedCharacterSet, - prefix:"&", - separator:"&", - named:true, - omittOrphanedEquals:false) + return ExpansionConfiguration(percentEncodingAllowedCharacterSet: unreservedCharacterSet, + prefix: "&", + separator: "&", + named: true, + omittOrphanedEquals: false) } } } diff --git a/Source/Internal/Scanner.swift b/Source/Internal/Scanner.swift index daa161f..223f46f 100644 --- a/Source/Internal/Scanner.swift +++ b/Source/Internal/Scanner.swift @@ -19,9 +19,9 @@ private func ~= (lhs: CharacterSet, rhs: Unicode.Scalar) -> Bool { } internal struct Scanner { - let string : String - let unicodeScalars : String.UnicodeScalarView - var currentIndex : String.Index + let string: String + let unicodeScalars: String.UnicodeScalarView + var currentIndex: String.Index public init(string: String) { self.string = string @@ -29,10 +29,8 @@ internal struct Scanner { self.currentIndex = string.startIndex } - public var isComplete : Bool { - get { - return currentIndex >= unicodeScalars.endIndex - } + public var isComplete: Bool { + return currentIndex >= unicodeScalars.endIndex } public mutating func scanComponent() throws -> Component { @@ -53,7 +51,7 @@ internal struct Scanner { private mutating func scanExpressionComponent() throws -> Component { assert(unicodeScalars[currentIndex] == "{") let expressionStartIndex = currentIndex - currentIndex = unicodeScalars.index(after:currentIndex) + currentIndex = unicodeScalars.index(after: currentIndex) let expressionOperator = try scanExpressionOperator() let variableList = try scanVariableList() @@ -62,13 +60,13 @@ internal struct Scanner { } private mutating func scanExpressionOperator() throws -> ExpressionOperator { - let expressionOperator : ExpressionOperator - if (expressionOperatorCharacterSet.contains(unicodeScalars[currentIndex])) { - guard let op = ExpressionOperator(rawValue:unicodeScalars[currentIndex]) else { + let expressionOperator: ExpressionOperator + if expressionOperatorCharacterSet.contains(unicodeScalars[currentIndex]) { + guard let op = ExpressionOperator(rawValue: unicodeScalars[currentIndex]) else { throw URITemplate.Error.malformedTemplate(position: currentIndex, reason: "Unsupported Operator") } expressionOperator = op - currentIndex = unicodeScalars.index(after:currentIndex) + currentIndex = unicodeScalars.index(after: currentIndex) } else { expressionOperator = .simple } @@ -79,20 +77,20 @@ internal struct Scanner { var variableList: [VariableSpec] = [] var complete = false - while (!complete) { + while !complete { let variableName = try scanVariableName() - if (currentIndex == unicodeScalars.endIndex) { + if currentIndex == unicodeScalars.endIndex { throw URITemplate.Error.malformedTemplate(position: currentIndex, reason: "Unterminated Expression") } let modifier = try scanVariableModifier() - if (currentIndex == unicodeScalars.endIndex) { + if currentIndex == unicodeScalars.endIndex { throw URITemplate.Error.malformedTemplate(position: currentIndex, reason: "Unterminated Expression") } - variableList.append(VariableSpec(name:variableName, modifier:modifier)) + variableList.append(VariableSpec(name: variableName, modifier: modifier)) switch unicodeScalars[currentIndex] { case ",": @@ -111,20 +109,20 @@ internal struct Scanner { private mutating func scanVariableName() throws -> Substring { let endIndex = scanUpTo(characterSet: invertedVarnameCharacterSet) let variableName = string[currentIndex.. String { guard let encoded = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) else { - throw FormatError.failure(reason:"Percent Encoding Failed") + throw FormatError.failure(reason: "Percent Encoding Failed") } return encoded } internal extension StringProtocol { internal func formatForTemplateExpansion(variableSpec: VariableSpec, expansionConfiguration: ExpansionConfiguration) throws -> String { - let modifiedValue : String - if let l = variableSpec.prefixLength() { - modifiedValue = String(self.prefix(l)) + let modifiedValue: String + if let prefixLength = variableSpec.prefixLength() { + modifiedValue = String(self.prefix(prefixLength)) } else { modifiedValue = String(self) } let encodedExpansion = try percentEncode(string: modifiedValue, withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet) - if (expansionConfiguration.named) { - if (encodedExpansion.isEmpty && expansionConfiguration.omittOrphanedEquals) { + if expansionConfiguration.named { + if encodedExpansion.isEmpty && expansionConfiguration.omittOrphanedEquals { return String(variableSpec.name) } return "\(variableSpec.name)=\(encodedExpansion)" @@ -53,9 +54,9 @@ internal extension Array where Element: StringProtocol { if encodedExpansions.count == 0 { return nil } - let expansion = encodedExpansions.joined(separator:separator) - if (expansionConfiguration.named) { - if (expansion.isEmpty && expansionConfiguration.omittOrphanedEquals) { + let expansion = encodedExpansions.joined(separator: separator) + if expansionConfiguration.named { + if expansion.isEmpty && expansionConfiguration.omittOrphanedEquals { return String(variableSpec.name) } return "\(variableSpec.name)=\(expansion)" @@ -67,8 +68,8 @@ internal extension Array where Element: StringProtocol { let separator = expansionConfiguration.separator let encodedExpansions = try self.map { element -> String in let encodedElement = try percentEncode(string: String(element), withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet) - if (expansionConfiguration.named) { - if (encodedElement.isEmpty && expansionConfiguration.omittOrphanedEquals) { + if expansionConfiguration.named { + if encodedElement.isEmpty && expansionConfiguration.omittOrphanedEquals { return String(variableSpec.name) } return "\(variableSpec.name)=\(encodedElement)" @@ -78,7 +79,7 @@ internal extension Array where Element: StringProtocol { if encodedExpansions.count == 0 { return nil } - return encodedExpansions.joined(separator:separator) + return encodedExpansions.joined(separator: separator) } } @@ -92,8 +93,8 @@ internal extension Dictionary where Key: StringProtocol, Value: StringProtocol { if encodedExpansions.count == 0 { return nil } - let expansion = encodedExpansions.joined(separator:",") - if (expansionConfiguration.named) { + let expansion = encodedExpansions.joined(separator: ",") + if expansionConfiguration.named { return "\(variableSpec.name)=\(expansion)" } return expansion @@ -104,7 +105,7 @@ internal extension Dictionary where Key: StringProtocol, Value: StringProtocol { let encodedExpansions = try self.map { key, value -> String in let encodedKey = try percentEncode(string: String(key), withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet) let encodedValue = try percentEncode(string: String(value), withAllowedCharacters: expansionConfiguration.percentEncodingAllowedCharacterSet) - if (expansionConfiguration.named && encodedValue.isEmpty && expansionConfiguration.omittOrphanedEquals) { + if expansionConfiguration.named && encodedValue.isEmpty && expansionConfiguration.omittOrphanedEquals { return String(variableSpec.name) } return "\(encodedKey)=\(encodedValue)" @@ -112,6 +113,6 @@ internal extension Dictionary where Key: StringProtocol, Value: StringProtocol { if encodedExpansions.count == 0 { return nil } - return encodedExpansions.joined(separator:separator) + return encodedExpansions.joined(separator: separator) } } diff --git a/Source/Internal/VariableSpec.swift b/Source/Internal/VariableSpec.swift index e171f3f..803c1a9 100644 --- a/Source/Internal/VariableSpec.swift +++ b/Source/Internal/VariableSpec.swift @@ -16,13 +16,14 @@ import Foundation internal struct VariableSpec { enum Modifier { + // swiftlint:disable:next identifier_name superfluous_disable_command case prefix(length: Int) case explode case none } - let name : Substring - let modifier : Modifier + let name: Substring + let modifier: Modifier func prefixLength() -> Int? { guard case .prefix(let length) = self.modifier else { diff --git a/Source/URITemplate.swift b/Source/URITemplate.swift index c2000af..1c658fb 100644 --- a/Source/URITemplate.swift +++ b/Source/URITemplate.swift @@ -15,21 +15,23 @@ import Foundation public protocol VariableValue {} -extension String : VariableValue {} -extension Array : VariableValue where Element: StringProtocol {} -extension Dictionary : VariableValue where Key: StringProtocol, Value: StringProtocol {} +extension String: VariableValue {} +extension Array: VariableValue where Element: StringProtocol {} +extension Dictionary: VariableValue where Key: StringProtocol, Value: StringProtocol {} public struct URITemplate { - public enum Error : Swift.Error { + public enum Error: Swift.Error { + // swiftlint:disable identifier_name superfluous_disable_command case malformedTemplate(position: String.Index, reason: String) case expansionFailure(position: String.Index, reason: String) + // swiftlint:enable identifier_name superfluous_disable_command } private let string: String - private let components : [Component] + private let components: [Component] public init(string: String) throws { - var components : [Component] = [] + var components: [Component] = [] var scanner = Scanner(string: string) while !scanner.isComplete { try components.append(scanner.scanComponent()) @@ -38,10 +40,10 @@ public struct URITemplate { self.components = components } - public func process(variables: [String:VariableValue]) throws -> String { + public func process(variables: [String: VariableValue]) throws -> String { var result = "" for component in components { - result += try component.expand(variables:variables) + result += try component.expand(variables: variables) } return result } @@ -53,35 +55,36 @@ public struct URITemplate { } } -extension URITemplate : CustomStringConvertible { +extension URITemplate: CustomStringConvertible { public var description: String { return string } } -extension URITemplate : ExpressibleByStringLiteral { +extension URITemplate: ExpressibleByStringLiteral { public init(stringLiteral value: StaticString) { - try! self.init(string:"\(value)") + //swiftlint:disable:next force_try + try! self.init(string: "\(value)") } } -extension URITemplate : Equatable { +extension URITemplate: Equatable { public static func == (lhs: URITemplate, rhs: URITemplate) -> Bool { return lhs.string == rhs.string } } -extension URITemplate : Hashable { +extension URITemplate: Hashable { public var hashValue: Int { return string.hashValue } } -extension URITemplate : Codable { +extension URITemplate: Codable { public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let string = try container.decode(String.self) - try self.init(string:string) + try self.init(string: string) } public func encode(to encoder: Encoder) throws { diff --git a/Tests/JSONValue.swift b/Tests/JSONValue.swift index 580d7f9..2718401 100644 --- a/Tests/JSONValue.swift +++ b/Tests/JSONValue.swift @@ -16,12 +16,14 @@ import Foundation public enum JSONValue: Decodable { case null + //swiftlint:disable identifier_name superfluous_disable_command case string(String) case int(Int) case double(Double) case bool(Bool) case object([String: JSONValue]) case array([JSONValue]) + //swiftlint:enable identifier_name superfluous_disable_command public init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() diff --git a/Tests/TestFileTests.swift b/Tests/TestFileTests.swift index 3f02526..0e998dc 100644 --- a/Tests/TestFileTests.swift +++ b/Tests/TestFileTests.swift @@ -18,13 +18,13 @@ import URITemplate class TestFileTests: XCTestCase { private var templateString: String! - private var variables: [String:VariableValue]! + private var variables: [String: VariableValue]! private var acceptableExpansions: [String]! private var failPosition: Int? private var failReason: String? func testFileParseFailed() { - XCTFail() + XCTFail("Test File Parse Failed") } func testSuccessfulProcess() { @@ -33,7 +33,7 @@ class TestFileTests: XCTestCase { let result = try template.process(variables: variables) XCTAssertTrue(acceptableExpansions.contains(result)) } catch { - XCTFail() + XCTFail("Unexpected Throw") } } @@ -41,25 +41,25 @@ class TestFileTests: XCTestCase { do { let template = try URITemplate(string: templateString) _ = try template.process(variables: variables) - XCTFail() + XCTFail("Did not throw") } catch URITemplate.Error.malformedTemplate(let position, let reason) { - if (failReason != nil) { + if failReason != nil { XCTAssertEqual(failReason, reason) } - if (failPosition != nil) { + if failPosition != nil { let characters = templateString[.. VariableValue? { - switch(self) { + switch self { case .int(let int): return String(int) case .double(let double): @@ -49,7 +49,7 @@ extension JSONValue { return string case .object(let object): return object.mapValues { element -> String? in - switch(element) { + switch element { case .string(let string): return string default: @@ -59,7 +59,7 @@ extension JSONValue { .mapValues { $0! } case .array(let array): return array.compactMap { element -> String? in - switch(element) { + switch element { case .string(let string): return string default: @@ -73,13 +73,13 @@ extension JSONValue { } extension TestCase { - init?(_ data : [JSONValue]) { - if (data.count < 2) { + init?(_ data: [JSONValue]) { + if data.count < 2 { return nil } guard let first = data.first, case .string(let template) = first else { - return nil; + return nil } let expansionsData = data[1] @@ -91,7 +91,7 @@ extension TestCase { acceptableExpansions = array.compactMap { value in switch value { case .string(let string): - return string; + return string default: return nil } @@ -104,15 +104,15 @@ extension TestCase { self.template = template - var failPosition : Int? = nil - if (data.count > 2) { + var failPosition: Int? = nil + if data.count > 2 { if case .int(let position) = data[2] { failPosition = position } } - var failReason : String? = nil - if (data.count > 3) { + var failReason: String? = nil + if data.count > 3 { if case .string(let reason) = data[3] { failReason = reason } @@ -123,12 +123,13 @@ extension TestCase { } } -public func parseTestFile(URL:URL) -> [TestGroup] { - guard let testData = try? Data(contentsOf: URL) else { +public func parseTestFile(URL: URL) -> [TestGroup] { + guard let testData = try? Data(contentsOf: URL), + let testCollection = try? JSONDecoder().decode(TestFile.self, from: testData) else { + print("Failed to decode test file \(URL)") return [] } - let testCollection = try! JSONDecoder().decode(TestFile.self, from: testData) return testCollection.map { (testGroupName, testGroupData) in let variables = testGroupData.variables.mapValues { element in return element.toVariableValue() @@ -139,6 +140,6 @@ public func parseTestFile(URL:URL) -> [TestGroup] { return TestCase(element) } - return TestGroup(name: testGroupName, level: testGroupData.level, variables:variables, testcases:testcases) + return TestGroup(name: testGroupName, level: testGroupData.level, variables: variables, testcases: testcases) } } diff --git a/Tests/Tests.swift b/Tests/Tests.swift index 9d2368c..c372bea 100644 --- a/Tests/Tests.swift +++ b/Tests/Tests.swift @@ -16,23 +16,26 @@ import XCTest import URITemplate class Tests: XCTestCase { - + func testCustomStringConvertible() { - let template = try! URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") + let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" XCTAssertEqual(template.description, "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") } func testExpressibleByStringLiteral() { - let templateA : URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" - let templateB = try! URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") + let templateA: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" + guard let templateB = try? URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") else { + XCTFail("invalid template") + return + } XCTAssertEqual(templateA, templateB) } func testEquatable() { - let templateA = try! URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") - let templateB = try! URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") + let templateA: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" + let templateB: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" XCTAssertEqual(templateA, templateB) - let templateC = try! URITemplate(string: "https://api.github.com/repos/{owner}") + let templateC: URITemplate = "https://api.github.com/repos/{owner}" XCTAssertNotEqual(templateB, templateC) let templateD = templateC XCTAssertEqual(templateC, templateD) @@ -40,54 +43,62 @@ class Tests: XCTestCase { } func testHashable() { - let templateA = try! URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") - let templateB = try! URITemplate(string: "https://api.github.com/repos/{owner}") - let dictionary = [templateA : "A", templateB : "B"] + let templateA: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" + let templateB: URITemplate = "https://api.github.com/repos/{owner}" + let dictionary = [templateA: "A", templateB: "B"] XCTAssertEqual(dictionary[templateA], "A") XCTAssertEqual(dictionary[templateB], "B") } func testVariableNames() { - let template = try! URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") + let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" let variableNames = template.variableNames let expected = ["owner", "repo", "username"] XCTAssertEqual(variableNames, expected) } func testEncoding() { - let templateString = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" - let template = try! URITemplate(string: templateString) - let jsonData = try! JSONEncoder().encode(["a":template]) - let expectedData = try! JSONEncoder().encode(["a":templateString]) - XCTAssertEqual(jsonData, expectedData) + do { + let templateString = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" + let template = try URITemplate(string: templateString) + let jsonData = try JSONEncoder().encode(["a": template]) + let expectedData = try JSONEncoder().encode(["a": templateString]) + XCTAssertEqual(jsonData, expectedData) + } catch { + XCTFail("unexpected throw") + } } func testDecoding() { - let templateString = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" - let jsonString = "{\"a\":\"\(templateString)\"}" - let jsonData = jsonString.data(using: .utf8)! - let object = try! JSONDecoder().decode([String:URITemplate].self, from: jsonData) - let expectedTemplate = try! URITemplate(string:templateString) - XCTAssertEqual(object["a"], expectedTemplate) + do { + let templateString = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" + let jsonString = "{\"a\":\"\(templateString)\"}" + let jsonData = jsonString.data(using: .utf8)! + let object = try JSONDecoder().decode([String: URITemplate].self, from: jsonData) + let expectedTemplate = try URITemplate(string: templateString) + XCTAssertEqual(object["a"], expectedTemplate) + } catch { + XCTFail("unexpected throw") + } } func testInitPerformance() { self.measure { for _ in 1...5000 { - _ = try! URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") + _ = try? URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") } } } func testProcessPerformance() { - let template = try! URITemplate(string: "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}") + let template: URITemplate = "https://api.github.com/repos/{owner}/{repo}/collaborators/{username}" let variables = ["owner": "SwiftScream", "repo": "URITemplate", "username": "alexdeem"] self.measure { for _ in 1...5000 { - _ = try! template.process(variables: variables) + _ = try? template.process(variables: variables) } } } diff --git a/URITemplate.xcodeproj/project.pbxproj b/URITemplate.xcodeproj/project.pbxproj index 2440a62..053547a 100644 --- a/URITemplate.xcodeproj/project.pbxproj +++ b/URITemplate.xcodeproj/project.pbxproj @@ -793,6 +793,7 @@ 958812552096C3820080D3D4 /* Sources */, 958812562096C3820080D3D4 /* Frameworks */, 958812572096C3820080D3D4 /* CopyFiles */, + 95DC57B820CA587E0007C182 /* ShellScript */, ); buildRules = ( ); @@ -845,6 +846,7 @@ 958812942096E8EB0080D3D4 /* Sources */, 958812952096E8EB0080D3D4 /* Frameworks */, 958812962096E8EB0080D3D4 /* Headers */, + 95DC57B620CA586D0007C182 /* ShellScript */, ); buildRules = ( ); @@ -934,6 +936,7 @@ 95AE3DD22092FC0F004D6773 /* Sources */, 95AE3DD32092FC0F004D6773 /* Frameworks */, 95AE3DD42092FC0F004D6773 /* CopyFiles */, + 95DC57B720CA58770007C182 /* ShellScript */, ); buildRules = ( ); @@ -951,6 +954,7 @@ 95C5916E1F6F7A1100C4405A /* Sources */, 95C5916F1F6F7A1100C4405A /* Frameworks */, 95C591701F6F7A1100C4405A /* CopyFiles */, + 95DC57B520CA58500007C182 /* ShellScript */, ); buildRules = ( ); @@ -1224,6 +1228,77 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 95DC57B520CA58500007C182 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; + 95DC57B620CA586D0007C182 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; + 95DC57B720CA58770007C182 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; + 95DC57B820CA587E0007C182 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 9549657620A41AAD00BD6E16 /* Sources */ = { isa = PBXSourcesBuildPhase;