From 909f75f4417609fe11934be1eabf0eb9244d08f4 Mon Sep 17 00:00:00 2001
From: Quan Vo <quan.vo@ibm.com>
Date: Tue, 12 Sep 2017 07:46:33 -0500
Subject: [PATCH] Add Swift 4 support (#102)

* Swift 4 Linux fixes

* Linux Swift 4 fixes

* Remove comments

* Reduce force unwraps
---
 Package@swift-4.swift                         | 28 +++++++++++
 Sources/SwiftKuery/ConditionalClause.swift    | 19 ++++----
 Sources/SwiftKuery/Utils.swift                | 41 +++++++++++++----
 Tests/LinuxMain.swift                         | 46 +++++++++++++------
 .../VerifyLinuxTestCount.swift                |  2 +-
 5 files changed, 105 insertions(+), 31 deletions(-)
 create mode 100644 Package@swift-4.swift

diff --git a/Package@swift-4.swift b/Package@swift-4.swift
new file mode 100644
index 00000000..59979b31
--- /dev/null
+++ b/Package@swift-4.swift
@@ -0,0 +1,28 @@
+// swift-tools-version:4.0
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+    name: "SwiftKuery",
+    products: [
+        // Products define the executables and libraries produced by a package, and make them visible to other packages.
+        .library(
+            name: "SwiftKuery",
+            targets: ["SwiftKuery"]),
+        ],
+    dependencies: [
+        // Dependencies declare other packages that this package depends on.
+        // .package(url: /* package url */, from: "1.0.0"),
+    ],
+    targets: [
+        // Targets are the basic building blocks of a package. A target defines a module or a test suite.
+        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
+        .target(
+            name: "SwiftKuery",
+            dependencies: []),
+        .testTarget(
+            name: "SwiftKueryTests",
+            dependencies: ["SwiftKuery"]),
+        ]
+)
diff --git a/Sources/SwiftKuery/ConditionalClause.swift b/Sources/SwiftKuery/ConditionalClause.swift
index 785f6ca2..81609097 100644
--- a/Sources/SwiftKuery/ConditionalClause.swift
+++ b/Sources/SwiftKuery/ConditionalClause.swift
@@ -45,28 +45,29 @@ public extension ConditionalClause {
     /// - Returns: A String representation of the clause.
     /// - Throws: QueryError.syntaxError if query build fails.
     public func build(queryBuilder: QueryBuilder) throws -> String {
-        guard lhs != nil else {
+        guard let _lhs = lhs else {
             if condition == .exists || condition == .notExists {
-                guard rhs != nil else {
+                guard let _rhs = rhs else {
                     throw QueryError.syntaxError("No right hand side operand in conditional clause.")
                 }
-                return try condition.build(queryBuilder: queryBuilder) + " " + rhs!.build(queryBuilder: queryBuilder)
+
+                return try condition.build(queryBuilder: queryBuilder) + " " + _rhs.build(queryBuilder: queryBuilder)
             }
             throw QueryError.syntaxError("No left hand side operand in conditional clause.")
         }
         
-        guard rhs != nil else {
+        guard let _rhs = rhs else {
             if condition == .isNull || condition == .isNotNull {
-                return try lhs!.build(queryBuilder: queryBuilder) + " " + condition.build(queryBuilder: queryBuilder)
+                return try _lhs.build(queryBuilder: queryBuilder) + " " + condition.build(queryBuilder: queryBuilder)
             }
             throw QueryError.syntaxError("No right hand side operand in conditional clause.")
         }        
         
-        let lhsBuilt = try lhs!.build(queryBuilder: queryBuilder)
+        let lhsBuilt = try _lhs.build(queryBuilder: queryBuilder)
         let conditionBuilt = condition.build(queryBuilder: queryBuilder)
         var rhsBuilt = ""
         if condition == .between || condition == .notBetween {
-            switch rhs! {
+            switch _rhs {
             case .arrayOfString(let array):
                 rhsBuilt = Utils.packType(array[0]) + " AND " + Utils.packType(array[1])
             case .arrayOfInt(let array):
@@ -86,7 +87,7 @@ public extension ConditionalClause {
             }
         }
         else if condition == .in || condition == .notIn {
-            switch rhs! {
+            switch _rhs {
             case .arrayOfString(let array):
                 rhsBuilt = "(\(array.map { Utils.packType($0) }.joined(separator: ", ")))"
             case .arrayOfInt(let array):
@@ -108,7 +109,7 @@ public extension ConditionalClause {
             }
         }
         else {
-            rhsBuilt = try rhs!.build(queryBuilder: queryBuilder)
+            rhsBuilt = try _rhs.build(queryBuilder: queryBuilder)
         }
         return lhsBuilt + " " + conditionBuilt + " " + rhsBuilt
     }
diff --git a/Sources/SwiftKuery/Utils.swift b/Sources/SwiftKuery/Utils.swift
index f8ea778f..9f949a21 100644
--- a/Sources/SwiftKuery/Utils.swift
+++ b/Sources/SwiftKuery/Utils.swift
@@ -66,10 +66,17 @@ struct Utils {
         var inputQuery = query
         var range = inputQuery.range(of: Parameter.numberedParameterMarker)
         var index = 1
-        while range != nil {
-            resultQuery += inputQuery.substring(to: range!.lowerBound) + marker + "\(index)"
+        while let _range = range {
+            #if swift(>=3.2)
+                resultQuery += inputQuery[..<_range.lowerBound] + marker + "\(index)"
+                inputQuery = String(inputQuery[_range.upperBound...])
+            #else
+                resultQuery += inputQuery.substring(to: _range.lowerBound) + marker + "\(index)"
+                inputQuery = inputQuery.substring(from: _range.upperBound)
+            #endif
+
             index += 1
-            inputQuery = inputQuery.substring(from: range!.upperBound)
+
             range = inputQuery.range(of: Parameter.numberedParameterMarker)
         }
         resultQuery += inputQuery
@@ -84,21 +91,39 @@ struct Utils {
         var inputQuery = query
         var startRange = inputQuery.range(of: Parameter.namedParameterStart)
         var endRange = inputQuery.range(of: Parameter.namedParameterEnd)
-        while startRange != nil && endRange != nil {
-            resultQuery += inputQuery.substring(to: startRange!.lowerBound) + marker
+        while let _startRange = startRange, let _endRange = endRange {
+            #if swift(>=3.2)
+                resultQuery += inputQuery[..<_startRange.lowerBound] + marker
+            #else
+                resultQuery += inputQuery.substring(to: _startRange.lowerBound) + marker
+            #endif
+
             if queryBuilder.addNumbersToParameters {
                 resultQuery += "\(index)"
             }
-            let nameRange: Range = startRange!.upperBound..<endRange!.lowerBound
-            let name = inputQuery.substring(with: nameRange)
+
+            #if swift(>=3.2)
+                let name = String(inputQuery[_startRange.upperBound..<_endRange.lowerBound])
+            #else
+                let nameRange: Range = _startRange.upperBound..<_endRange.lowerBound
+                let name = inputQuery.substring(with: nameRange)
+            #endif
+
             if let _ = nameToNumber[name] {
                 nameToNumber[name]!.append(index)
             }
             else {
                 nameToNumber[name] = [index]
             }
+
             index += 1
-            inputQuery = inputQuery.substring(from: endRange!.upperBound)
+
+            #if swift(>=3.2)
+                inputQuery = String(inputQuery[_endRange.upperBound...])
+            #else
+                inputQuery = inputQuery.substring(from: _endRange.upperBound)
+            #endif
+
             startRange = inputQuery.range(of: Parameter.namedParameterStart)
             endRange = inputQuery.range(of: Parameter.namedParameterEnd)
         }
diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift
index 69fd7e16..b6b9d7a4 100644
--- a/Tests/LinuxMain.swift
+++ b/Tests/LinuxMain.swift
@@ -18,22 +18,42 @@ import XCTest
 import Glibc
 @testable import SwiftKueryTests
 
-srand(UInt32(time(nil)))
-
 // http://stackoverflow.com/questions/24026510/how-do-i-shuffle-an-array-in-swift
-extension MutableCollection where Indices.Iterator.Element == Index {
-    mutating func shuffle() {
-        let c = count
-        guard c > 1 else { return }
-        
-        for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
-            let d: IndexDistance = numericCast(random() % numericCast(unshuffledCount))
-            guard d != 0 else { continue }
-            let i = index(firstUnshuffled, offsetBy: d)
-            swap(&self[firstUnshuffled], &self[i])
+#if swift(>=4)
+
+    extension MutableCollection {
+        mutating func shuffle() {
+            let c = count
+            guard c > 1 else { return }
+
+            srand(UInt32(time(nil)))
+            for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
+                let d: IndexDistance = numericCast(random() % numericCast(unshuffledCount))
+                guard d != 0 else { continue }
+                let i = index(firstUnshuffled, offsetBy: d)
+                swapAt(firstUnshuffled, i)
+            }
         }
     }
-}
+
+#else
+
+    extension MutableCollection where Indices.Iterator.Element == Index {
+        mutating func shuffle() {
+            let c = count
+            guard c > 1 else { return }
+
+            srand(UInt32(time(nil)))
+            for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
+                let d: IndexDistance = numericCast(random() % numericCast(unshuffledCount))
+                guard d != 0 else { continue }
+                let i = index(firstUnshuffled, offsetBy: d)
+                swap(&self[firstUnshuffled], &self[i])
+            }
+        }
+    }
+
+#endif
 
 extension Sequence {
     func shuffled() -> [Iterator.Element] {
diff --git a/Tests/SwiftKueryTests/VerifyLinuxTestCount.swift b/Tests/SwiftKueryTests/VerifyLinuxTestCount.swift
index 6d66f0f2..4dc60add 100644
--- a/Tests/SwiftKueryTests/VerifyLinuxTestCount.swift
+++ b/Tests/SwiftKueryTests/VerifyLinuxTestCount.swift
@@ -14,7 +14,7 @@
  * limitations under the License.
  **/
 
-#if os(OSX)
+#if os(OSX) && !swift(>=3.2)
     import XCTest
     
     class VerifyLinuxTestCount: XCTestCase {