├── .swift-version
├── Logo
├── logo.png
└── logo.sketch
├── Package.swift
├── SwiftlyExt.xcworkspace
└── contents.xcworkspacedata
├── SwiftlyExt.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcshareddata
│ └── xcschemes
│ │ ├── SwiftlyExt watchOS.xcscheme
│ │ ├── SwiftlyExt macOS.xcscheme
│ │ ├── SwiftlyExt iOS.xcscheme
│ │ └── SwiftlyExt tvOS.xcscheme
└── project.pbxproj
├── Source
├── Swiftly.h
├── CGFloatExtensions.swift
├── Swiftly.swift
├── Info.plist
├── CGPointExtensions.swift
├── Info-tvOS.plist
├── DictionaryExtensions.swift
├── RangeExtensions.swift
├── IntExtensions.swift
├── StringExtensions.swift
├── DateExtensions.swift
└── ArrayExtensions.swift
├── .swiftlint.yml
├── SwiftlyExt.podspec
├── .gitignore
├── Tests
├── Info.plist
├── CGFloatTests.swift
├── CGPointTests.swift
├── SwiftlyTests.swift
├── DictionaryTests.swift
├── IntTests.swift
├── DateTests.swift
├── StringTests.swift
└── ArrayTests.swift
├── CHANGELOG.md
├── LICENSE
├── .travis.yml
└── README.md
/.swift-version:
--------------------------------------------------------------------------------
1 | 3.0
--------------------------------------------------------------------------------
/Logo/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khoi-backyard/SwiftlyExt/HEAD/Logo/logo.png
--------------------------------------------------------------------------------
/Logo/logo.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/khoi-backyard/SwiftlyExt/HEAD/Logo/logo.sketch
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | import PackageDescription
2 |
3 | let package = Package(
4 | name: "SwiftlyExt",
5 | exclude: ["Tests"]
6 | )
7 |
--------------------------------------------------------------------------------
/SwiftlyExt.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftlyExt.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Source/Swiftly.h:
--------------------------------------------------------------------------------
1 | //
2 | // Swiftly.h
3 | // Swiftly
4 | //
5 | // Created by Khoi Lai on 9/19/16.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | @import Foundation;
10 |
11 | FOUNDATION_EXPORT double SwiftlyVersionNumber;
12 |
13 | FOUNDATION_EXPORT const unsigned char SwiftlyVersionString[];
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | included:
2 | - Source
3 | excluded:
4 | - Tests
5 | opt_in_rules:
6 | - attributes
7 | - empty_count
8 | - vertical_whitespace
9 | - closure_end_indentation
10 | - closure_spacing
11 | - explicit_init
12 | - first_where
13 | - operator_usage_whitespace
14 | - overridden_super_call
15 | - prohibited_super_call
16 | - redundant_nil_coalescing
17 | - switch_case_on_newline
18 | disabled_rules:
19 | - variable_name
20 | - file_length
21 | line_length: 200
22 |
23 |
--------------------------------------------------------------------------------
/SwiftlyExt.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'SwiftlyExt'
3 | s.version = '1.3.0'
4 | s.license = 'MIT'
5 | s.summary = 'Extensions for native standard Swift types and classes'
6 | s.homepage = 'https://github.com/khoiln/Swiftly'
7 | s.authors = {'Khoi Lai' => 'k@khoi.io' }
8 | s.source = { :git => 'https://github.com/khoiln/Swiftly.git', :tag => s.version }
9 | s.ios.deployment_target = '9.0'
10 | s.osx.deployment_target = '10.11'
11 | s.tvos.deployment_target = '9.0'
12 | s.watchos.deployment_target = '2.0'
13 | s.source_files = 'Source/*.swift'
14 | end
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Mac OS X
2 | .DS_Store
3 |
4 | # Xcode
5 |
6 | ## Build generated
7 | build/
8 | DerivedData
9 |
10 | ## Various settings
11 | *.pbxuser
12 | !default.pbxuser
13 | *.mode1v3
14 | !default.mode1v3
15 | *.mode2v3
16 | !default.mode2v3
17 | *.perspectivev3
18 | !default.perspectivev3
19 | xcuserdata
20 |
21 | ## Other
22 | *.xccheckout
23 | *.moved-aside
24 | *.xcuserstate
25 | *.xcscmblueprint
26 |
27 | ## Obj-C/Swift specific
28 | *.hmap
29 | *.ipa
30 |
31 | ## Playgrounds
32 | timeline.xctimeline
33 | playground.xcworkspace
34 |
35 | # Swift Package Manager
36 | .build/
37 |
38 | # Carthage
39 | Carthage/Build
--------------------------------------------------------------------------------
/Source/CGFloatExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGFloatExtensions.swift
3 | // SwiftlyExt
4 | //
5 | // Created by Khoi Lai on 12/30/16.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreGraphics
11 |
12 | public extension CGFloat {
13 |
14 | /// Convert from degrees to radians
15 | ///
16 | /// - Returns: Radian value
17 | func degreesToRadians() -> CGFloat {
18 | return (self * .pi) / 180.0
19 | }
20 |
21 | /// Convert from radians to degrees
22 | ///
23 | /// - Returns: Degree value
24 | func radiansToDegrees() -> CGFloat {
25 | return (self * 180.0) / .pi
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/Source/Swiftly.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Swiftly.swift
3 | // SwiftlyExt
4 | //
5 | // Created by Khoi Lai on 1/30/17.
6 | // Copyright © 2017 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Swiftly {
12 | /// Return a random integer from lowerbound to upperbound inclusively
13 | ///
14 | /// - Parameters:
15 | /// - lower: The lower bound
16 | /// - upper: The upper bound
17 | /// - Returns: A random Int
18 | public static func random(lower: Int = 0, upper: Int = 1) -> Int {
19 | precondition(upper >= lower, "Lowerbound must be smaller than upper bound")
20 | return Int(arc4random_uniform(UInt32(upper - lower + 1))) + lower
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Source/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Tests/CGFloatTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGFloatTests.swift
3 | // SwiftlyExt
4 | //
5 | // Created by Khoi Lai on 12/30/16.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class CGFloatTests: XCTestCase {
12 |
13 | override func setUp() {
14 | super.setUp()
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | super.tearDown()
21 | }
22 |
23 | func testDegreeToRadians() {
24 | XCTAssert(CGFloat(180).degreesToRadians() == CGFloat.pi)
25 | }
26 |
27 | func testRadiansToDegrees() {
28 | XCTAssert(CGFloat(CGFloat.pi).radiansToDegrees() == 180)
29 |
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Tests/CGPointTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGPointTests.swift
3 | // SwiftlyExt
4 | //
5 | // Created by Khoi Lai on 12/30/16.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class CGPointTests: XCTestCase {
12 |
13 | override func setUp() {
14 | super.setUp()
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | super.tearDown()
21 | }
22 |
23 | func testDistance() {
24 | XCTAssert(CGPoint(x: 0, y: 0).distance(to: CGPoint(x: 0, y: 0)) == 0)
25 | XCTAssert(CGPoint(x: 4, y: 4).distance(to: CGPoint(x: 4, y: 4)) == 0)
26 | XCTAssert(CGPoint(x: 2, y: 8).distance(to: CGPoint(x: 5, y: 4)) == 5)
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/Source/CGPointExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGPointExtensions.swift
3 | // SwiftlyExt
4 | //
5 | // Created by Khoi Lai on 12/30/16.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreGraphics
11 |
12 | public extension CGPoint {
13 |
14 | /// Calculate distance from self to another point.
15 | ///
16 | /// - Parameter to: destination point
17 | /// - Returns: distance between two points.
18 | func distance(to point: CGPoint) -> CGFloat {
19 | return CGPoint.distance(from: self, to: point)
20 | }
21 |
22 | /// Calculate distance between 2 CGPoint.
23 | ///
24 | /// - Parameters:
25 | /// - from: origin point
26 | /// - to: destination point
27 | /// - Returns: distance between two points.
28 | static func distance(from: CGPoint, to: CGPoint) -> CGFloat {
29 | return sqrt(pow(to.x - from.x, 2) + pow(to.y - from.y, 2))
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Source/Info-tvOS.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 | UIRequiredDeviceCapabilities
24 |
25 | arm64
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | `SwiftlyExt` adheres to [Semantic Versioning](http://semver.org/).
3 |
4 | ## [1.3.0](https://github.com/khoiln/SwiftlyExt/releases/tag/1.3.0)
5 |
6 | Remove Equatable on E of .groupBy
7 |
8 | ## [1.2.0](https://github.com/khoiln/SwiftlyExt/releases/tag/1.2.0)
9 |
10 | Added .groupBy, .reject
11 |
12 | ## [1.1.0](https://github.com/khoiln/SwiftlyExt/releases/tag/1.1.0)
13 |
14 | Added Swiftly struct for public static methods
15 | Added Array .random, .sample, .sampleSize
16 | Added String .between, .count, .initials, .isEmail, .hasNumbers, .hasLetters, .isAlpha, .isAlphaNumeric
17 |
18 | ## [1.0.2](https://github.com/khoiln/SwiftlyExt/releases/tag/1.0.2)
19 |
20 | Added Array .without, .shuffle, .shuffled
21 |
22 | ## [1.0.1](https://github.com/khoiln/SwiftlyExt/releases/tag/1.0.1)
23 |
24 | Added Array static methods: xor, xorBy, xorWith
25 |
26 | ## [1.0](https://github.com/khoiln/SwiftlyExt/releases/tag/1.0)
27 |
28 | First stable release 🚀
29 |
30 |
--------------------------------------------------------------------------------
/Tests/SwiftlyTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftlyTests.swift
3 | // SwiftlyExt
4 | //
5 | // Created by Khoi Lai on 1/30/17.
6 | // Copyright © 2017 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftlyExt
11 |
12 | class SwiftlyTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testRandom(){
25 | (0...100).forEach { _ in
26 | let random = Swiftly.random(lower: 0, upper: 1)
27 | XCTAssert( [0,1].some{ $0 == random } , "Should return number 0 or 1")
28 | }
29 |
30 | XCTAssert(Swiftly.random(lower: 2, upper: 2) == 2, "Return the same number if lower == upper")
31 | }
32 |
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Swiftly
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/Source/DictionaryExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DictionaryExtensions.swift
3 | // SwiftlyExt
4 | //
5 | // Created by Khoi Lai on 12/30/16.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Dictionary {
12 |
13 | init?(fromJsonString json: String) {
14 | guard let data = (try? JSONSerialization.jsonObject(with: json.data(using: String.Encoding.utf8, allowLossyConversion: true)!,
15 | options: JSONSerialization.ReadingOptions.mutableContainers)) as? Dictionary else {
16 | return nil
17 | }
18 | self = data
19 | }
20 |
21 | /// Convert from dictionary to Json String
22 | ///
23 | /// - Parameter prettify: Pretty printed or not
24 | /// - Returns: Json String if exist
25 | func toJsonString(prettify: Bool = false) -> String? {
26 | guard let data = try? JSONSerialization.data(withJSONObject: self, options: prettify ? .prettyPrinted : JSONSerialization.WritingOptions()) else { return nil }
27 | let str = String(data: data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
28 | return String(str ?? "")
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/Source/RangeExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RangeExtensions.swift
3 | // Swiftly
4 | //
5 | // Created by Khoi Lai on 9/19/16.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | internal extension CountableRange {
12 |
13 | func each(callback: (Bound) -> Void) {
14 | for i in self {
15 | callback(i)
16 | }
17 | }
18 |
19 | func each(callback: () -> Void) {
20 | for _ in self {
21 | callback()
22 | }
23 | }
24 |
25 | func toArray() -> [Bound] {
26 | var result = [Bound]()
27 | self.each { (bound) in
28 | result.append(bound)
29 | }
30 | return result
31 | }
32 | }
33 |
34 | internal extension CountableClosedRange {
35 | func each(callback: (Bound) -> Void) {
36 | for i in self {
37 | callback(i)
38 | }
39 | }
40 |
41 | func each(callback: () -> Void) {
42 | for _ in self {
43 | callback()
44 | }
45 | }
46 |
47 | func toArray() -> [Bound] {
48 | var result = [Bound]()
49 | self.each { (bound) in
50 | result.append(bound)
51 | }
52 | return result
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/Tests/DictionaryTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DictionaryTests.swift
3 | // SwiftlyExt
4 | //
5 | // Created by Khoi Lai on 12/30/16.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class DictionaryTests: XCTestCase {
12 |
13 |
14 | typealias JSONDict = [String: Any]
15 |
16 | let jsonString = "{\"list\":[1234,45.234],\"number\":5443.1,\"name\":\"Swiftly\",\"object\":{\"sub_number\":877.2323,\"sub_name\":\"SwiftlySub\"},\"bool\":true}"
17 |
18 | override func setUp() {
19 | super.setUp()
20 | // Put setup code here. This method is called before the invocation of each test method in the class.
21 | }
22 |
23 | override func tearDown() {
24 | // Put teardown code here. This method is called after the invocation of each test method in the class.
25 | super.tearDown()
26 | }
27 |
28 |
29 | func testInitFromJSON() {
30 | let dict = JSONDict(fromJsonString: jsonString)!
31 | XCTAssert((dict["number"] as! Double) == 5443.1, "number prop")
32 | XCTAssert((dict["name"] as! String) == "Swiftly", "string prop")
33 |
34 |
35 | XCTAssert((dict["list"] as! [Double])[0] == 1234, "array list prop int")
36 | XCTAssert((dict["list"] as! [Double])[1] == 45.234, "array list prop double")
37 | XCTAssert((dict["object"] as! JSONDict)["sub_number"] as! Double == 877.2323, "subobject number")
38 | XCTAssert((dict["object"] as! JSONDict)["sub_name"] as! String == "SwiftlySub", "subobject string")
39 | XCTAssert(dict["bool"] as! Bool, "bool prop")
40 | }
41 |
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode8
3 | env:
4 | global:
5 | - LC_CTYPE=en_US.UTF-8
6 | - LANG=en_US.UTF-8
7 | - WORKSPACE=SwiftlyExt.xcworkspace
8 | - IOS_FRAMEWORK_SCHEME="SwiftlyExt iOS"
9 | - MACOS_FRAMEWORK_SCHEME="SwiftlyExt macOS"
10 | - TVOS_FRAMEWORK_SCHEME="SwiftlyExt tvOS"
11 | - WATCHOS_FRAMEWORK_SCHEME="SwiftlyExt watchOS"
12 | - IOS_SDK=iphonesimulator10.0
13 | - MACOS_SDK=macosx10.12
14 | - TVOS_SDK=appletvsimulator10.0
15 | - WATCHOS_SDK=watchsimulator3.0
16 | matrix:
17 | - DESTINATION="OS=3.0,name=Apple Watch - 42mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK" RUN_TESTS="NO" POD_LINT="NO"
18 | - DESTINATION="OS=2.0,name=Apple Watch - 42mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK" RUN_TESTS="NO" POD_LINT="NO"
19 |
20 | - DESTINATION="OS=10.0,name=iPhone 7 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" POD_LINT="YES"
21 | - DESTINATION="OS=9.0,name=iPhone 5" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" POD_LINT="NO"
22 |
23 | - DESTINATION="OS=10.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="YES" POD_LINT="NO"
24 | - DESTINATION="OS=9.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="YES" POD_LINT="NO"
25 |
26 | - DESTINATION="arch=x86_64" SCHEME="$MACOS_FRAMEWORK_SCHEME" SDK="$MACOS_SDK" RUN_TESTS="YES" POD_LINT="NO"
27 | before_install:
28 | - gem install cocoapods --pre --no-rdoc --no-ri --no-document --quiet
29 | script:
30 | - set -o pipefail
31 | - xcodebuild -version
32 | - xcodebuild -showsdks
33 |
34 | # Build Framework in Debug and Run Tests if specified
35 | - if [ $RUN_TESTS == "YES" ]; then
36 | travis_retry xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty;
37 | else
38 | travis_retry xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty;
39 | fi
40 |
41 | # Build Framework in Release and Run Tests if specified
42 | - if [ $RUN_TESTS == "YES" ]; then
43 | travis_retry xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty;
44 | else
45 | travis_retry xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO build | xcpretty;
46 | fi
47 |
48 | # Run `pod lib lint` if specified
49 | - if [ $POD_LINT == "YES" ]; then
50 | pod lib lint;
51 | fi
52 | after_success:
53 | - bash <(curl -s https://codecov.io/bash)
--------------------------------------------------------------------------------
/Source/IntExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IntExtensions.swift
3 | // Swiftly
4 | //
5 | // Created by Khoi Lai on 9/19/16.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Int {
12 | /// Convert current int to an [Int]
13 | ///
14 | /// - returns: An array of digits
15 | func digits() -> [Int] {
16 |
17 | var digits: [Int] = []
18 | var curr = self
19 |
20 | while curr > 0 {
21 | digits.append(curr % 10)
22 | curr /= 10
23 | }
24 |
25 | return digits.reversed()
26 | }
27 |
28 | /// Invoke the callback with indexes from current int down to and including n.
29 | ///
30 | /// - parameter lowBound: The lowBound to go to
31 | /// - parameter callback: The block to invoke with current index
32 | func downTo(_ lowBound: Int, callback: (Int) -> Void) {
33 | var curr = self
34 | while curr >= lowBound {
35 | callback(curr)
36 | curr -= 1
37 | }
38 | }
39 |
40 | /// Return the factorial of int
41 | ///
42 | /// - returns: Factorial of int
43 | func factorial() -> Int {
44 | guard self > 0 else { return 1 }
45 | return self * (self - 1).factorial()
46 | }
47 |
48 | /// Check if the integer is even.
49 | ///
50 | /// - returns: Bool whether the int is even
51 | func isEven() -> Bool {
52 | return self % 2 == 0
53 | }
54 |
55 | /// Test whether the int is in a range
56 | ///
57 | /// - parameter range: The range to check
58 | ///
59 | /// - returns: BOOL whether the int is in range
60 | func isIn(range: Range) -> Bool {
61 | return range ~= self
62 | }
63 |
64 | /// Test whether the int is in a range
65 | ///
66 | /// - parameter range: The range to check
67 | ///
68 | /// - returns: BOOL whether the int is in range
69 | func isIn(range: ClosedRange) -> Bool {
70 | return range ~= self
71 | }
72 |
73 | /// Check if the integer is odd.
74 | ///
75 | /// - returns: Bool whether the int is odd
76 | func isOdd() -> Bool {
77 | return !isEven()
78 | }
79 |
80 | /// Invoke the callback function n times with zero-based indexes.
81 | ///
82 | /// - parameter callback: The block with current index
83 | func times(_ callback: (Int) -> Void) {
84 | guard self > 0 else { return }
85 | (0.. Void) {
95 | guard self <= upBound else { return }
96 | (self...upBound).each { (idx) in
97 | callback(idx)
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/SwiftlyExt.xcodeproj/xcshareddata/xcschemes/SwiftlyExt watchOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
35 |
36 |
46 |
47 |
53 |
54 |
55 |
56 |
57 |
58 |
64 |
65 |
71 |
72 |
73 |
74 |
76 |
77 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/Tests/IntTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IntTests.swift
3 | // IntTests
4 | //
5 | // Created by Khoi Lai on 9/19/16.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import SwiftlyExt
11 |
12 | class IntTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 |
17 | 1.upTo(3) { print($0) }
18 | // Put setup code here. This method is called before the invocation of each test method in the class.
19 | }
20 |
21 | override func tearDown() {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | super.tearDown()
24 | }
25 |
26 | func testTimes() {
27 | var count = 0
28 |
29 | 5.times { _ in
30 | count += 1
31 | }
32 | XCTAssertEqual(count, 5)
33 |
34 | var zero = 0
35 | 0.times { _ in
36 | zero += 1
37 | }
38 | XCTAssertEqual(zero, 0)
39 |
40 |
41 | (-9).times { _ in
42 | zero += 1
43 | }
44 | XCTAssertEqual(zero, 0)
45 |
46 | }
47 |
48 | func testTimesWithIndex() {
49 | var indexes = [Int]()
50 |
51 | 5.times { (idx) in
52 | indexes.append(idx)
53 | }
54 |
55 | XCTAssertEqual(indexes, [0, 1,2, 3,4])
56 | }
57 |
58 | func testOddAndEven() {
59 | XCTAssert(4.isEven())
60 | XCTAssert(0.isEven())
61 | XCTAssertFalse(1.isEven())
62 | XCTAssertFalse(3.isEven())
63 |
64 | XCTAssertFalse(4.isOdd())
65 | XCTAssertFalse(0.isOdd())
66 | XCTAssert(1.isOdd())
67 | XCTAssert(3.isOdd())
68 | }
69 |
70 | func testUpTo() {
71 | var count = 0
72 | 0.upTo(5) { _ in
73 | count += 1
74 | }
75 | XCTAssertEqual(count, 6)
76 |
77 | var zero = 0
78 | 0.upTo(-1) { _ in
79 | zero += 1
80 | }
81 | XCTAssertEqual(zero, 0)
82 |
83 | 12.upTo(-1) { _ in
84 | zero += 1
85 | }
86 | XCTAssertEqual(zero, 0)
87 |
88 | var indexes = [Int]()
89 |
90 | 0.upTo(5) { (idx) in
91 | indexes.append(idx)
92 | }
93 |
94 | XCTAssertEqual(indexes, [0, 1,2, 3,4, 5])
95 | }
96 |
97 |
98 | func testDownTo() {
99 | var count = 0
100 | 5.downTo(0) { _ in
101 | count += 1
102 | }
103 | XCTAssertEqual(count, 6)
104 |
105 | var zero = 0
106 | (-1).downTo(0) { _ in
107 | zero += 1
108 | }
109 | XCTAssertEqual(zero, 0)
110 |
111 | (-1).downTo(12) { _ in
112 | zero += 1
113 | }
114 | XCTAssertEqual(zero, 0)
115 |
116 | var indexes = [Int]()
117 | 5.downTo(0) { (idx) in
118 | indexes.append(idx)
119 | }
120 | XCTAssertEqual(indexes, [5, 4,3, 2,1, 0])
121 | }
122 |
123 |
124 |
125 | func testDigits() {
126 | XCTAssertEqual(1234.digits(), [1, 2,3, 4])
127 | XCTAssertEqual(01234.digits(), [1, 2,3, 4])
128 | }
129 |
130 | func testIsIn() {
131 | XCTAssert(0.isIn(range: 0..<200))
132 | XCTAssertFalse(200.isIn(range: 0..<200))
133 | XCTAssertFalse((-1).isIn(range: 0..<200))
134 |
135 | XCTAssert(0.isIn(range: 0...200))
136 | XCTAssert(200.isIn(range: 0...200))
137 | XCTAssertFalse((-1).isIn(range: 0...200))
138 | XCTAssertFalse(201.isIn(range: 0...200))
139 | }
140 |
141 | func testFactorial() {
142 | XCTAssertEqual(5.factorial(), 120)
143 | XCTAssertEqual(10.factorial(), 3628800)
144 | XCTAssertEqual(0.factorial(), 1)
145 |
146 |
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/Tests/DateTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateTests.swift
3 | // Swiftly
4 | //
5 | // Created by Khoi Lai on 08/10/2016.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class DateTests: XCTestCase {
12 |
13 |
14 | let dateFormatString = "dd/MM/yyyy HH:mm:ss"
15 |
16 | let currentDate = Date()
17 |
18 | var sameDate: Date?
19 | var futureDate: Date?
20 | var pastDate: Date?
21 |
22 |
23 | let calendar = Calendar.current
24 |
25 | override func setUp() {
26 | super.setUp()
27 | sameDate = calendar.date(byAdding: .day, value: 0, to: currentDate)
28 | futureDate = calendar.date(byAdding: .day, value: 1, to: currentDate)
29 | pastDate = calendar.date(byAdding: .day, value: -1, to: currentDate)
30 | }
31 |
32 | func testBefore() {
33 | XCTAssert(currentDate.isBefore(futureDate!))
34 | XCTAssertFalse(currentDate.isBefore(pastDate!))
35 | XCTAssertFalse(currentDate.isBefore(sameDate!))
36 | }
37 |
38 | func testAfter() {
39 | XCTAssert(currentDate.isAfter(pastDate!))
40 | XCTAssertFalse(currentDate.isAfter(futureDate!))
41 | XCTAssertFalse(currentDate.isAfter(sameDate!))
42 | }
43 |
44 | func testCustomInit() {
45 | XCTAssertNotNil(Date(from: "01/01/1970 00:34:22", format: dateFormatString), "Should return a valid date")
46 | XCTAssertNil(Date(from: "01/34/1970 00:34:22", format: dateFormatString), "Should return a invalid date")
47 | }
48 |
49 | func testCustomerProperty() {
50 | let aDate = Date(from: "01/01/1970 00:34:22", format: dateFormatString)
51 | XCTAssertEqual(aDate?.era, 1)
52 | XCTAssertEqual(aDate?.year, 1970)
53 | XCTAssertEqual(aDate?.month, 1)
54 | XCTAssertEqual(aDate?.day, 1)
55 | XCTAssertEqual(aDate?.hour, 00)
56 | XCTAssertEqual(aDate?.minute, 34)
57 | XCTAssertEqual(aDate?.second, 22)
58 | XCTAssertEqual(aDate?.weekday, 5)
59 | }
60 |
61 | func testStringFormat() {
62 | let aDate = Date(from: "30/12/2016 14:34:22", format: dateFormatString)
63 | XCTAssertEqual(aDate?.toString(format: dateFormatString), "30/12/2016 14:34:22")
64 | XCTAssertEqual(aDate?.toString(format: "MMM yyyy"), "Dec 2016")
65 | }
66 |
67 | func testAddingDate() {
68 | let aDate = Date(from: "30/12/2016 14:34:22", format: dateFormatString)
69 | XCTAssertEqual(aDate?.date(byAddingYears: 1)?.year, 2017)
70 | XCTAssertEqual(aDate?.date(byAddingYears: -1)?.year, 2015)
71 |
72 | XCTAssertEqual(aDate?.date(byAddingMonths: 1)?.year, 2017, "Should switch to next year if current month is 12")
73 | XCTAssertEqual(aDate?.date(byAddingMonths: 1)?.month, 1)
74 | XCTAssertEqual(aDate?.date(byAddingMonths: -1)?.month, 11)
75 |
76 | XCTAssertEqual(aDate?.date(byAddingDays: 1)?.day, 31)
77 | XCTAssertEqual(aDate?.date(byAddingDays: 2)?.day, 1)
78 | XCTAssertEqual(aDate?.date(byAddingDays: -1)?.day, 29)
79 |
80 | XCTAssertEqual(aDate?.date(byAddingHours: 1)?.hour, 15)
81 | XCTAssertEqual(aDate?.date(byAddingHours: -1)?.hour, 13)
82 | XCTAssertEqual(aDate?.date(byAddingHours: 10)?.hour, 0)
83 | XCTAssertEqual(aDate?.date(byAddingHours: 10)?.day, 31)
84 |
85 | XCTAssertEqual(aDate?.date(byAddingMinutes: 1)?.minute, 35)
86 | XCTAssertEqual(aDate?.date(byAddingMinutes: -1)?.minute, 33)
87 | XCTAssertEqual(aDate?.date(byAddingMinutes: 26)?.hour, 15)
88 | XCTAssertEqual(aDate?.date(byAddingMinutes: 26)?.minute, 0)
89 |
90 | XCTAssertEqual(aDate?.date(byAddingSeconds: 1)?.second, 23)
91 | XCTAssertEqual(aDate?.date(byAddingSeconds: -1)?.second, 21)
92 | XCTAssertEqual(aDate?.date(byAddingSeconds: 38)?.second, 0)
93 | XCTAssertEqual(aDate?.date(byAddingSeconds: 38)?.minute, 35)
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/SwiftlyExt.xcodeproj/xcshareddata/xcschemes/SwiftlyExt macOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/Tests/StringTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringTests.swift
3 | // SwiftlyExt
4 | //
5 | // Created by Khoi Lai on 10/10/2016.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class StringTests: XCTestCase {
12 |
13 | let dateFormatString = "dd/MM/yyyy HH:mm:ss"
14 |
15 | override func setUp() {
16 | super.setUp()
17 | }
18 |
19 | func testInitials(){
20 | XCTAssert("John Doe".initials == "JD")
21 | XCTAssert("John With Middle Name Doe".initials == "JWMND")
22 | }
23 |
24 | func testIsEmail(){
25 | XCTAssertTrue("swift@swiftly.com".isEmail)
26 | XCTAssertFalse("swift@@swiftly.com".isEmail)
27 | }
28 |
29 | func testCount(){
30 | XCTAssert("Swiftlylylyly ly".count("as") == 0)
31 | XCTAssert("Swiftlylylyly ly".count("ly") == 5)
32 | }
33 |
34 | func testBetween(){
35 | XCTAssert("foo".between("", "") == "foo")
36 | XCTAssert("foo".between("", "") == "foo")
37 | XCTAssert("foo".between("", "") == nil)
38 | XCTAssert("Some strings } are very {weird}, dont you think?".between("{", "}") == "weird")
39 | XCTAssert("".between("", "") == nil)
40 | XCTAssert("foo".between("", "") == nil)
41 | }
42 |
43 | func testDate() {
44 | guard let date = "01/01/1970 00:34:22".date(format: dateFormatString) else {
45 | XCTFail("Can't parse a valid date")
46 | return
47 | }
48 | XCTAssertNil("13/14/1780 14:38:88".date(format: dateFormatString), "Should return nil for invalid date")
49 | XCTAssertEqual(date.year, 1970)
50 | XCTAssertEqual(date.month, 1)
51 | XCTAssertEqual(date.day, 1)
52 | XCTAssertEqual(date.hour, 0)
53 | XCTAssertEqual(date.minute, 34)
54 | XCTAssertEqual(date.second, 22)
55 | XCTAssertEqual(date.nanosecond, 0)
56 | }
57 |
58 | func testBase64Encode() {
59 | XCTAssert("a".base64Encoded == "YQ==", "2 padding chars")
60 | XCTAssert("aa".base64Encoded == "YWE=", "1 padding char")
61 | XCTAssert("aaa".base64Encoded == "YWFh", "0 padding char")
62 | XCTAssert("foo\0".base64Encoded == "Zm9vAA==", "U+0000")
63 | XCTAssert("foo\0\0".base64Encoded == "Zm9vAAA=", "0 padding char")
64 | XCTAssert("https://github.com/Swiftly".base64Encoded == "aHR0cHM6Ly9naXRodWIuY29tL1N3aWZ0bHk=")
65 | }
66 |
67 | func testBase64Decode() {
68 | XCTAssert("aHR0cHM6Ly9naXRodWIuY29tL1N3aWZ0bHk=".base64Decoded == "https://github.com/Swiftly")
69 | }
70 |
71 | func testReversed() {
72 | XCTAssert("Swiftly".reversed == "yltfiwS")
73 | XCTAssert("abcdef".reversed == "fedcba")
74 | XCTAssert("a".reversed == "a")
75 | XCTAssert("".reversed == "")
76 | }
77 |
78 | func testTrimmed() {
79 | XCTAssert("\r\n\n\n Swiftly \r\n\n".trimmed == "Swiftly")
80 | XCTAssert("\n\n\n Swiftly ".trimmed.reversed == "yltfiwS")
81 | }
82 |
83 | func testURLEncoded() {
84 | XCTAssert("abcd".urlEncoded == "abcd", "String must be unchanged")
85 | XCTAssert("\n\t".urlEncoded == "%0A%09")
86 | XCTAssert("Swiftly\t\nString\nTest".urlEncoded == "Swiftly%09%0AString%0ATest")
87 | }
88 |
89 | func testURLDecoded() {
90 | XCTAssert("https%3A%2F%2Fgithub.com%2Fkhoiln%2FSwiftlyEXT".urlDecoded == "https://github.com/khoiln/SwiftlyEXT")
91 | }
92 |
93 | func testHasNumbers(){
94 | XCTAssertTrue("hoho2".hasNumbers)
95 | XCTAssertFalse("swiftly".hasNumbers)
96 | }
97 |
98 | func testHasLetters(){
99 | XCTAssertFalse("12389723".hasLetters)
100 | XCTAssertTrue("3749347a98423".hasLetters)
101 | XCTAssertFalse("!@#$%^&*()🐶".hasLetters)
102 | }
103 |
104 | func testIsAlpha(){
105 | XCTAssertFalse("fdafaf3".isAlpha)
106 | XCTAssert("afaf".isAlpha)
107 | XCTAssertFalse("dfda@#(*&@#dfd".isAlpha)
108 | }
109 |
110 | func testIsAlphaNumeric() {
111 | XCTAssert("afaf35353afaf".isAlphaNumeric)
112 | XCTAssert("FFFF99fff".isAlphaNumeric)
113 | XCTAssert("99".isAlphaNumeric)
114 | XCTAssert("afff".isAlphaNumeric)
115 | XCTAssertFalse("-33".isAlphaNumeric)
116 | XCTAssertFalse("aaff..".isAlphaNumeric)
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/Source/StringExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StringExtensions.swift
3 | // SwiftlyExt
4 | //
5 | // Created by Khoi Lai on 10/10/2016.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension String {
12 |
13 | /// Find the string between two string.
14 | ///
15 | /// - Parameters:
16 | /// - left: The left bookend
17 | /// - right: The right bookend
18 | /// - Returns: Return the String if found else `nil`
19 | func between(_ left: String, _ right: String) -> String? {
20 | guard let leftRange = range(of: left),
21 | let rightRange = range(of: right, options: .backwards),
22 | left != right && leftRange.upperBound != rightRange.lowerBound else {
23 | return nil
24 | }
25 | return self[leftRange.upperBound...index(before: rightRange.lowerBound)]
26 | }
27 |
28 | /// Count number of occurences for a substring
29 | ///
30 | /// - Parameter str: The string to count
31 | /// - Returns: Number of occurences
32 | func count(_ str: String) -> Int {
33 | return components(separatedBy: str).count - 1
34 | }
35 |
36 | /// Return a date from current string.
37 | ///
38 | /// - parameter format: The date format
39 | /// - parameter locale: The locale. default to `.current`
40 | ///
41 | /// - returns: A Date.
42 | func date(format: String, locale: Locale = .current) -> Date? {
43 | let df = DateFormatter()
44 | df.dateFormat = format
45 | df.locale = locale
46 | return df.date(from: self)
47 | }
48 |
49 | /// Check whether the string is an email or not.
50 | var isEmail: Bool {
51 | let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
52 | let predicate = NSPredicate(format:"SELF MATCHES %@", regex)
53 | return predicate.evaluate(with: self)
54 | }
55 |
56 | /// Check whether the string contains one or more letters.
57 | public var hasLetters: Bool {
58 | return rangeOfCharacter(from: .letters, options: .literal) != nil
59 | }
60 |
61 | /// Check whether the string contains one or more numbers.
62 | public var hasNumbers: Bool {
63 | return rangeOfCharacter(from: .decimalDigits, options: .literal) != nil
64 | }
65 |
66 | /// Check whether the string contains only letters.
67 | var isAlpha: Bool {
68 | for c in characters {
69 | if !(c >= "a" && c <= "z") && !(c >= "A" && c <= "Z") {
70 | return false
71 | }
72 | }
73 | return true
74 | }
75 |
76 | /// Check if string contains at least one letter and one number
77 | var isAlphaNumeric: Bool {
78 | return components(separatedBy: .alphanumerics).joined(separator: "").characters.isEmpty
79 | }
80 |
81 | /// Return the initials of the String
82 | var initials: String {
83 | return self.components(separatedBy: " ").reduce("") { $0 + $1[0...0] }
84 | }
85 |
86 | /// Decoded Base 64 String if applicable
87 | var base64Decoded: String? {
88 | guard let decodedData = Data(base64Encoded: self) else { return nil }
89 | return String(data: decodedData, encoding: .utf8)
90 | }
91 |
92 | /// Encoded Base 64 String if applicable
93 | var base64Encoded: String? {
94 | return data(using: .utf8)?.base64EncodedString()
95 | }
96 |
97 | /// Reversed String
98 | var reversed: String {
99 | return String(self.characters.reversed())
100 | }
101 |
102 | /// A String without white spaces and new lines
103 | var trimmed: String {
104 | return trimmingCharacters(in: .whitespacesAndNewlines)
105 | }
106 |
107 | /// Decoded URL String
108 | var urlDecoded: String {
109 | return removingPercentEncoding ?? self
110 | }
111 |
112 | /// Encoded URL String
113 | var urlEncoded: String {
114 | return self.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? self
115 | }
116 |
117 | }
118 |
119 | extension String {
120 | subscript (r: CountableClosedRange) -> String {
121 | get {
122 | let startIndex = self.index(self.startIndex, offsetBy: r.lowerBound)
123 | let endIndex = self.index(startIndex, offsetBy: r.upperBound - r.lowerBound)
124 | return self[startIndex...endIndex]
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/SwiftlyExt.xcodeproj/xcshareddata/xcschemes/SwiftlyExt iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
79 |
80 |
81 |
82 |
83 |
84 |
90 |
91 |
97 |
98 |
99 |
100 |
102 |
103 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/SwiftlyExt.xcodeproj/xcshareddata/xcschemes/SwiftlyExt tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
79 |
80 |
81 |
82 |
83 |
84 |
90 |
91 |
97 |
98 |
99 |
100 |
102 |
103 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://travis-ci.org/khoiln/SwiftlyExt)
4 | [](https://codecov.io/gh/khoiln/SwiftlyExt)
5 | [](https://img.shields.io/cocoapods/v/SwiftlyExt.svg)
6 | [](https://github.com/Carthage/Carthage)
7 | 
8 | [](http://cocoadocs.org/docsets/SwiftlyExt)
9 | [](https://opensource.org/licenses/MIT)
10 |
11 | SwiftlyExt is a library that extends certain Swift standard types and classes using extension feature in the Swift language.
12 |
13 | - [Requirements](#requirements)
14 | - [Installation](#installation)
15 | - [Documentation via CocoaDocs](http://cocoadocs.org/docsets/SwiftlyExt/)
16 | - [FAQ](#faq)
17 | - [License](#license)
18 |
19 | ## Requirements
20 |
21 | - iOS 9.0+ / macOS 10.11+ / tvOS 9.0+ / watchOS 2.0+
22 | - Xcode 8.0+
23 | - Swift 3.0+
24 |
25 | ## Installation
26 |
27 | ### CocoaPods
28 |
29 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:
30 |
31 | ```bash
32 | $ gem install cocoapods
33 | ```
34 |
35 | > CocoaPods 1.1.0+ is required to build SwiftlyExt.
36 |
37 | To integrate SwiftlyExt into your Xcode project using CocoaPods, specify it in your `Podfile`:
38 |
39 | ```ruby
40 | source 'https://github.com/CocoaPods/Specs.git'
41 | platform :ios, '10.0'
42 | use_frameworks!
43 |
44 | target '' do
45 | pod 'SwiftlyExt', '~> 1.3'
46 | end
47 | ```
48 |
49 | Then, run the following command:
50 |
51 | ```bash
52 | $ pod install
53 | ```
54 |
55 | ### Carthage
56 |
57 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
58 |
59 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
60 |
61 | ```bash
62 | $ brew update
63 | $ brew install carthage
64 | ```
65 |
66 | To integrate SwiftlyExt into your Xcode project using Carthage, specify it in your `Cartfile`:
67 |
68 | ```ogdl
69 | github "khoiln/SwiftlyExt" ~> 1.3
70 | ```
71 |
72 | Run `carthage update` to build the framework and drag the built `SwiftlyExt.framework` into your Xcode project. U
73 |
74 | ### Swift Package Manager
75 |
76 | Adding SwiftlyExt as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift` file.
77 |
78 | ```swift
79 | dependencies: [
80 | .Package(url: "https://github.com/khoiln/SwiftlyExt.git", majorVersion: 1)
81 | ]
82 | ```
83 |
84 | Note that the [Swift Package Manager](https://swift.org/package-manager/) is still in early design and development, but SwiftlyExt does support its use on supported platforms.
85 |
86 | ## Usage
87 |
88 | There are many handy usages of `SwiftlyExt`, head over [CocoaDocs](http://cocoadocs.org/docsets/SwiftlyExt/) for the comprehensive documentation.
89 |
90 | We'll try to list some of the cool examples here.
91 |
92 | ### Array Extensions
93 |
94 | ```swift
95 | [😀,🤡,❤️,💋].sample() // Return a random element
96 | // => 💋
97 | [😀,🤡,❤️,💋].sampleSize(2) // Return n random elements
98 | // => [🤡, 💋]
99 | [1, 2, 3, 4, 5].dropWhile { $0 < 3 } //Drop elements that passes the predicate from the beginning to end
100 | // => [3, 4, 5]
101 | [1, 2, 3, 4, 5].dropWhile { $0 < 3 }.some {$0 % 2 == 0} //And YES you can use method chaining too 👍
102 | // => true
103 | [0, 11, 28, 10].every { $0 % 2 == 0 } //Check if all elements in the array passed the condition
104 | // => false
105 | [0, 11, 28, 10].some { $0 % 2 != 0 } //Check if one of the element passes the condition
106 | // => true
107 | [1, 2, 3, 4, 5].findLastIndex {$0 % 2 == 0} //Find index of the last number which predicate return true for.
108 | // => 3
109 | [1, 2, 3, 4, 5].groupBy { $0 % 2 == 0 ? "even" : "odd"} //Group common elements from an array to a dictionary of [Hashable : [Element]]
110 | // => ["even": [2,4], "odd": [1,3,5]]
111 | // Any many more....
112 | ```
113 |
114 | ### Date Extensions
115 |
116 | ```swift
117 | let now = Date()
118 | let tmr = now.date(byAddingDays: 1)
119 | .date(byAddingMinutes: 20) // You could also add year, month... and other time units
120 |
121 | now.isBefore(tmr)
122 | // => true
123 |
124 | now?.toString(format: "dd/MM/yyyy HH:mm:ss") // Return the string representation for a date.
125 | // => "03/15/2017 14:34:22"
126 |
127 | tmr.year == 2017 // Access time unit properties
128 | tmr.hour == 14
129 | tmr.minute == 54
130 | ```
131 |
132 | ### String Extensions
133 |
134 | ```swift
135 | "John Doe".initials // Return the initials of the String
136 | // => "JD"
137 | "swift@swiftly.com".isEmail // Email validation
138 | // => true
139 | "💯
".between("", "
") // Find the string between two string
140 | // => "💯"
141 | "01/01/1970 00:34:22".date(format: "dd/MM/yyyy HH:mm:ss") // Return a date from current string
142 | // => Date("01/01/1970 00:34:22")
143 | "https://github.com/Swiftly".base64Encoded // Return base64encoded string
144 | // => "aHR0cHM6Ly9naXRodWIuY29tL1N3aWZ0bHk="
145 | "\n\n\n Swiftly ".trimmed.reversed // Trim newline and spaces and reverse the string
146 | // => "yltfiwS"
147 | "Swiftly\t\nString\nTest".urlEncoded // URL Encoded
148 | // => "Swiftly%09%0AString%0ATest"
149 | "https%3A%2F%2Fgithub.com%2Fkhoiln%2FSwiftlyEXT".urlDecoded // URL Decoded
150 | // => "https://github.com/khoiln/SwiftlyEXT"
151 | // Any many more....
152 | ```
153 |
154 | ### Int Extensions
155 |
156 | ```swift
157 | 1.upTo(3) { print($0) }
158 | // print 1, 2, 3
159 | 5.times { print("🐶") } // Run a block n times
160 | // print 🐶 5 times
161 | 1234.digits() // Convert integer to array of digits
162 | // => [1,2,3,4]
163 | 201.isIn(range: 200..<300) // Test whether a int is in a range
164 | // => true
165 | // And many more
166 | ```
167 |
168 | ## How to contribute
169 | Any help or feedback is highly appreciated. Please refer to the [contributing guidelines](https://github.com/khoiln/SwiftlyExt/blob/master/CONTRIBUTING.md) for more information.
170 |
171 | ## License
172 |
173 | SwiftyExt is released under the MIT license. See [LICENSE](https://github.com/khoiln/SwiftlyExt/blob/master/LICENSE) for details.
174 |
--------------------------------------------------------------------------------
/Source/DateExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DateExtensions.swift
3 | // Swiftly
4 | //
5 | // Created by Khoi Lai on 08/10/2016.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Date {
12 |
13 | fileprivate var components: DateComponents {
14 | return calendar.dateComponents([.era, .year, .month, .day, .hour, .minute, .second, .nanosecond, .weekday, .weekdayOrdinal, .quarter, .weekOfMonth, .weekOfYear], from: self)
15 | }
16 |
17 | fileprivate var calendar: Calendar {
18 | return Calendar.current
19 | }
20 |
21 | /// An era or count of eras.
22 | /// - note: This value is for Calendar.current
23 | var era: Int {
24 | return components.era!
25 | }
26 |
27 | /// A year or count of years.
28 | /// - note: This value is for Calendar.current
29 | var year: Int {
30 | return components.year!
31 | }
32 |
33 | /// A month or count of months.
34 | /// - note: This value is for Calendar.current
35 | var month: Int {
36 | return components.month!
37 | }
38 |
39 | /// A day or count of days.
40 | /// - note: This value is for Calendar.current
41 | var day: Int {
42 | return components.day!
43 | }
44 |
45 | /// An hour or count of hours.
46 | /// - note: This value is for Calendar.current
47 | var hour: Int {
48 | return components.hour!
49 | }
50 |
51 | /// A minute or count of minutes.
52 | /// - note: This value is for Calendar.current
53 | var minute: Int {
54 | return components.minute!
55 | }
56 |
57 | /// A second or count of seconds.
58 | /// - note: This value is for Calendar.current
59 | var second: Int {
60 | return components.second!
61 | }
62 |
63 | /// A nanosecond or count of nanoseconds.
64 | /// - note: This value is for Calendar.current
65 | var nanosecond: Int {
66 | return components.nanosecond!
67 | }
68 |
69 | /// A weekday or count of weekdays.
70 | /// - note: This value is for Calendar.current
71 | var weekday: Int {
72 | return components.weekday!
73 | }
74 |
75 | /// A weekday ordinal or count of weekday ordinals.
76 | /// - note: This value is for Calendar.current
77 | var weekdayOrdinal: Int {
78 | return components.weekdayOrdinal!
79 | }
80 |
81 | /// A quarter or count of quarters.
82 | /// - note: This value is for Calendar.current
83 | var quarter: Int {
84 | return components.quarter!
85 | }
86 |
87 | /// A week of the month or a count of weeks of the month.
88 | /// - note: This value is for Calendar.current
89 | var weekOfMonth: Int {
90 | return components.weekOfMonth!
91 | }
92 |
93 | /// A week of the year or count of the weeks of the year.
94 | /// - note: This value is for Calendar.current
95 | var weekOfYear: Int {
96 | return components.weekOfYear!
97 | }
98 |
99 | public init?(from string: String, format: String) {
100 | let df = DateFormatter()
101 | df.dateFormat = format
102 | guard let date = df.date(from: string) else { return nil }
103 | self = date
104 | }
105 |
106 | /// Return the string representation for a date.
107 | ///
108 | /// - parameter dateStyle: A DateFormatter.Style for the date. default to `.medium`.
109 | /// - parameter timeStyle: A DateFormatter.Style for the time. default to `.medium`.
110 | /// - parameter locale: Locale of the string. default to the current locale.
111 | ///
112 | /// - returns: The date string.
113 | func toString(dateStyle: DateFormatter.Style = .medium, timeStyle: DateFormatter.Style = .medium, locale: Locale = Locale.current) -> String {
114 | let df = DateFormatter()
115 | df.dateStyle = dateStyle
116 | df.timeStyle = timeStyle
117 | df.locale = locale
118 | return df.string(from: self)
119 | }
120 |
121 | /// Return the string representation for a date using a date format.
122 | ///
123 | /// - parameter format: The date format string.
124 | /// - parameter locale: Locale of the string. default to `.locale`
125 | ///
126 | /// - returns: The date string.
127 | func toString(format: String, locale: Locale = Locale.current) -> String {
128 | let df = DateFormatter()
129 | df.dateFormat = format
130 | df.locale = locale
131 | return df.string(from: self)
132 | }
133 |
134 | /// Check if self is before input date.
135 | ///
136 | /// - parameter date: The date to compare to.
137 | ///
138 | /// - returns: True if self is before input date, false otherwise.
139 | func isBefore(_ date: Date) -> Bool {
140 | return self.compare(date) == .orderedAscending
141 | }
142 |
143 | /// Check if self is after input date.
144 | ///
145 | /// - parameter date: The date to compare to.
146 | ///
147 | /// - returns: True if self is after input date, false otherwise.
148 | func isAfter(_ date: Date) -> Bool {
149 | return self.compare(date) == .orderedDescending
150 | }
151 | }
152 |
153 | // MARK: Adding date
154 | public extension Date {
155 | fileprivate func _date(byAddingYears years: Int = 0,
156 | byAddingMonths months: Int = 0,
157 | byAddingDays days: Int = 0,
158 | byAddingHours hours: Int = 0,
159 | byAddingMinutes minutes: Int = 0,
160 | byAddingSeconds seconds: Int = 0) -> Date? {
161 | var dc = DateComponents()
162 | dc.year = years
163 | dc.month = months
164 | dc.day = days
165 | dc.hour = hours
166 | dc.minute = minutes
167 | dc.second = seconds
168 | return calendar.date(byAdding: dc, to: self)
169 | }
170 |
171 | /// Return a new Date instance by adding n years to it.
172 | ///
173 | /// - parameter years: Number of years to add.
174 | ///
175 | /// - returns: The new Date.
176 | func date(byAddingYears years: Int) -> Date? {
177 | return _date(byAddingYears: years)
178 | }
179 |
180 | /// Return a new Date instance by adding n months to it.
181 | ///
182 | /// - parameter months: Number of months to add.
183 | ///
184 | /// - returns: The new Date.
185 | func date(byAddingMonths months: Int) -> Date? {
186 | return _date(byAddingMonths: months)
187 | }
188 |
189 | /// Return a new Date instance by adding n days to it.
190 | ///
191 | /// - parameter days: Number of days to add.
192 | ///
193 | /// - returns: The new Date.
194 | func date(byAddingDays days: Int) -> Date? {
195 | return _date(byAddingDays: days)
196 | }
197 |
198 | /// Return a new Date instance by adding n hours to it.
199 | ///
200 | /// - parameter hours: Number of hours to add.
201 | ///
202 | /// - returns: The new Date.
203 | func date(byAddingHours hours: Int) -> Date? {
204 | return _date(byAddingHours: hours)
205 | }
206 |
207 | /// Return a new Date instance by adding n minutes to it.
208 | ///
209 | /// - parameter minutes: Number of minutes to add.
210 | ///
211 | /// - returns: The new Date.
212 | func date(byAddingMinutes minutes: Int) -> Date? {
213 | return _date(byAddingMinutes: minutes)
214 | }
215 |
216 | /// Return a new Date instance by adding n seconds to it.
217 | ///
218 | /// - parameter seconds: Number of seconds to add.
219 | ///
220 | /// - returns: The new Date.
221 | func date(byAddingSeconds seconds: Int) -> Date? {
222 | return _date(byAddingSeconds: seconds)
223 | }
224 |
225 | }
226 |
--------------------------------------------------------------------------------
/Tests/ArrayTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayTests.swift
3 | // Swiftly
4 | //
5 | // Created by Khoi Lai on 26/09/2016.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | import SwiftlyExt
11 |
12 | class ArrayTests: XCTestCase {
13 |
14 |
15 | var a = [Int]()
16 |
17 | override func setUp() {
18 | super.setUp()
19 | a = [1, 2,3, 4,5]
20 | }
21 |
22 | func testSlice() {
23 | XCTAssertEqual(a.slice(start: 0, end: 2), [1, 2])
24 | XCTAssertEqual(a.slice(start: 0), [1, 2, 3, 4, 5])
25 | XCTAssertEqual(a.slice(start: 3), [4, 5])
26 | XCTAssertEqual(a.slice(start: 8), [])
27 | XCTAssertEqual(a.slice(start: 8, end: 10), [])
28 | XCTAssertEqual(a.slice(start: 1, end: 1), [])
29 | XCTAssertEqual(a.slice(start: 8, end: 2), [])
30 | XCTAssertEqual(a.slice(start: 3, end: 3), [])
31 | XCTAssertEqual(a.slice(start: 2, end: 5), [3, 4,5])
32 | XCTAssertEqual(a.slice(start: 0, end: 9), [1, 2,3, 4,5])
33 |
34 | }
35 |
36 | func testChunk() {
37 | XCTAssertEqual([Int]().chunk().count, 0, "chunk for empty array returns an empty array")
38 | XCTAssertEqual(a.chunk(size: 0).count, 0, "chunk into parts of 0 elements returns empty array")
39 | XCTAssertEqual(a.chunk(size: -1).count, 0, "chunk into parts of negative amount of elements returns an empty array")
40 | XCTAssertEqual(a.chunk().count, 0, "defaults to empty array (chunk size 0)")
41 | XCTAssert(a.chunk(size: 1) == [[1], [2], [3], [4], [5]], "chunk into parts of 1 elements returns original array")
42 | XCTAssert(a.chunk(size: 5) == [[1, 2, 3, 4, 5]], "chunk into parts of current array length elements returns the original array")
43 | XCTAssert(a.chunk(size: 7) == [[1, 2, 3, 4, 5]], "chunk into parts of more then current array length elements returns the original array")
44 | XCTAssert([10, 20, 30, 40, 50, 60, 70].chunk(size: 2) == [[10, 20], [30, 40], [50, 60], [70]], "chunk into parts of less then current array length elements")
45 | XCTAssert([10, 20, 30, 40, 50, 60, 70].chunk(size: 3) == [[10, 20, 30], [40, 50, 60], [70]], "chunk into parts of less then current array length elements")
46 | }
47 |
48 | func testDifference() {
49 | XCTAssertEqual(a.difference([1, 2,3]), [4, 5])
50 | XCTAssertEqual(a.difference([Int]()), [1, 2,3, 4,5])
51 | XCTAssertEqual(a.difference([1, 2,3, 4,5]), [])
52 | XCTAssertEqual(a.difference([1, 2,3, 4,5, 6,8]), [])
53 | }
54 |
55 | func testDifferenceBy() {
56 | XCTAssertEqual([3.1, 2.2, 1.3].differenceBy([4.4, 2.5], iteratee: floor), [3.1, 1.3])
57 | }
58 |
59 | func testDifferenceWith() {
60 | XCTAssert([["x":1, "y":2], ["x":2, "y":1]].differenceWith([["x":1, "y":2]], comparator: compare) == [["x":2, "y":1]])
61 | }
62 |
63 | func testConcat() {
64 | XCTAssertEqual(a.concat(values: 6, 7,8), [1, 2,3, 4,5, 6,7, 8])
65 | XCTAssertEqual(a.concat(arrays: []), [1, 2,3, 4,5])
66 | XCTAssertEqual(a.concat(arrays: [1, 2], [3, 4], [0], []), [1, 2,3, 4,5, 1,2, 3,4, 0])
67 | }
68 |
69 | func testDrop() {
70 | XCTAssertEqual(a.drop(), [2, 3,4, 5], "Should drop first element")
71 | XCTAssertEqual(a.drop(2), [3, 4,5], "Should drop more than 1 element")
72 | XCTAssertEqual(a.drop(9), [], "Should drop if n > array length")
73 | XCTAssertEqual(a.drop(0), [1, 2,3, 4,5], "Should return array if n == 0")
74 | XCTAssertEqual(a.drop(-1), [1, 2,3, 4,5], "Should return array if n < 0")
75 | }
76 |
77 | func testDropRight() {
78 | XCTAssertEqual(a.dropRight(), [1, 2,3, 4], "Should drop last element")
79 | XCTAssertEqual(a.dropRight(2), [1, 2,3], "Should drop more than 1 element")
80 | XCTAssertEqual(a.dropRight(9), [], "Should drop if n > array count")
81 | XCTAssertEqual(a.dropRight(0), [1, 2,3, 4,5], "Should return array if n == 0")
82 | XCTAssertEqual(a.dropRight(-1), [1, 2,3, 4,5], "Should return array if n < 0")
83 | }
84 |
85 | func testDropWhile() {
86 | XCTAssertEqual(a.dropWhile { $0 < 3 }, [3, 4,5], "Should drop elements")
87 | XCTAssertEqual(a.dropWhile { _ in return false }, [1, 2,3, 4,5])
88 | XCTAssertEqual(a.dropWhile { _ in return true }, [])
89 | }
90 |
91 | func testDropRightWhile() {
92 | XCTAssertEqual(a.dropRightWhile { $0 > 3 }, [1, 2, 3], "Should drop elements")
93 | XCTAssertEqual(a.dropRightWhile { _ in return false }, [1, 2,3, 4,5])
94 | XCTAssertEqual(a.dropRightWhile { _ in return true }, [])
95 | }
96 |
97 | func testFindIndex() {
98 | XCTAssertEqual(a.findIndex {$0 % 2 == 0}, 1, "Should find first index")
99 | XCTAssertEqual([1, 2,2].findIndex {$0 % 2 == 0}, 1, "Should return the first index")
100 | XCTAssertNil(a.findIndex {$0 > 5}, "Should return nil if not found")
101 | }
102 |
103 | func testFindLastIndex() {
104 | XCTAssertEqual(a.findLastIndex {$0 % 2 == 0}, 3, "Should find last index")
105 | XCTAssertEqual([1, 2,2].findLastIndex {$0 % 2 == 0}, 2, "Should return the last index")
106 | XCTAssertNil(a.findLastIndex {$0 > 5}, "Should return nil if not found")
107 | }
108 |
109 | func testIntersection() {
110 | XCTAssertEqual([2, 1].intersection([4, 2], [1, 2]), [2], "Should return the array of common elements")
111 | XCTAssertEqual([2, 7,5, 1].intersection([5, 9,4, 2], [5, 10, 1,2]), [2, 5], "Should return the array of common elements")
112 | XCTAssertEqual(a.intersection([1, 2,3, 4,5]), [1, 2,3, 4,5], "Should return the array")
113 | XCTAssertEqual(a.intersection([Int]()), [], "Should return blank array")
114 | }
115 |
116 | func testIntersectionBy() {
117 | XCTAssertEqual([2.1, 1.2].intersectionBy([4.3, 2.4], iteratee: floor), [2.1], "Should intersect with one array")
118 | XCTAssertEqual([2.1, 1.2].intersectionBy([4.5, 3.4, 2.3], [2.8, 3.2, 4.7], iteratee: floor), [2.1], "Should intersect with more than 1 array")
119 | }
120 |
121 | func testIntersectionWith() {
122 | let objects1 = [["x":1, "y":2], ["x":2, "y":1]]
123 | let objects2 = [["x":1, "y":1], ["x":1, "y":2]]
124 | XCTAssert(objects1.intersectionWith(objects2, comparator: compare) == [["x":1, "y":2]])
125 | }
126 |
127 | func testEvery() {
128 | XCTAssert([true, true, true].every { $0 == true }, "every true values")
129 | XCTAssertFalse([true, false, true].every { $0 == true }, "one false value")
130 |
131 | var count = 0
132 | XCTAssert([0, 10, 28].every {
133 | count += 1
134 | return $0 % 2 == 0
135 | }, "Even number")
136 |
137 | XCTAssert(count == 3, "Runtime \(count) should be 3")
138 |
139 | count = 0
140 |
141 | XCTAssertFalse([0, 11, 28, 10].every {
142 | count += 1
143 | return $0 % 2 == 0
144 | }, "An odd number")
145 |
146 | XCTAssert(count == 2, "Runtime \(count) should be 1")
147 | }
148 |
149 | func testSome() {
150 | XCTAssert([false, true, false].some { $0 == true }, "one true value")
151 | XCTAssertFalse([false, false, false].some { $0 == true }, "all false value")
152 |
153 |
154 | var count = 0
155 |
156 | XCTAssert([11, 23, 25, 26].some {
157 | count += 1
158 | return $0 % 2 == 0 }, "Even number at the end")
159 | XCTAssert(count == 4, "Runtime \(count) should be 4")
160 |
161 | count = 0
162 | XCTAssert([0, 11, 28, 10].some {
163 | count += 1
164 | return $0 % 2 == 0
165 | }, "An odd number")
166 | XCTAssert(count == 1, "Runtime \(count) should be 1")
167 | }
168 |
169 | func testXor() {
170 | let objects1 = [["x":1, "y":2], ["x":2, "y":1]]
171 | let objects2 = [["x":1, "y":1], ["x":1, "y":2]]
172 |
173 |
174 |
175 | XCTAssert([Int].xor(arrays: [2, 1], [4, 2]) == [1, 4])
176 | XCTAssert([Int].xor(arrays: []) == [])
177 | XCTAssert([Int].xor(arrays: [1, 2,3], [4, 5,6]) == [1, 2,3, 4,5, 6])
178 | XCTAssert([Double].xorBy(arrays: [2.1, 1.2], [4.3, 2.4], iteratee: floor) == [1.2, 4.3])
179 | XCTAssert([Dictionary].xorWith(arrays: objects1, objects2, comparator: compare) == [["x":2, "y":1], ["x":1, "y":1]])
180 | }
181 |
182 | func testWithout() {
183 | XCTAssert([1, 2, 1, 0, 3, 1, 4].without(0, 1) == [2, 3,4])
184 |
185 | }
186 |
187 | func testShuffle() {
188 | XCTAssert([1].shuffled() == [1], "Behave correctly with 1 element")
189 |
190 | let oneToTwentys = Array(1...20)
191 | let shuffled = oneToTwentys.shuffled()
192 | XCTAssert(shuffled != oneToTwentys, "does change the order") // Chance of false negative: 1 in ~2.4*10^18
193 | XCTAssert(oneToTwentys == shuffled.sorted(), "Same number of elements and value after sorted")
194 | }
195 |
196 | func testRandomAndSample() {
197 | let a = [0, 1]
198 | (0...100).forEach { _ in
199 | let rand = a.random()
200 | let sample = a.sample()
201 | XCTAssert(a.some { $0 == rand }, "Must be either 0 or 1")
202 | XCTAssert(a.some { $0 == sample }, "Same goes for sample")
203 | }
204 |
205 |
206 | }
207 |
208 | func testsampleSize() {
209 | let a = [0,1,2,3,4,5]
210 |
211 | XCTAssert(a.sampleSize() == [], "Default must be zero")
212 | XCTAssert(a.sampleSize(0) == [], "passing 0 should works too")
213 | XCTAssert(a.sampleSize(-10) == [], "Negative size works")
214 |
215 | (0...10).forEach{ _ in
216 | let numberOfElement = Swiftly.random(lower: 0, upper: a.count - 1)
217 | XCTAssert(a.sampleSize(numberOfElement).every{ aSample in
218 | return a.some {$0 == aSample}
219 | }, "Taking samples should work")
220 | }
221 |
222 | }
223 |
224 | func testGroupBy(){
225 | let a = [1,2,3,4,5]
226 | let test = a.groupBy { $0 % 2 == 0 ? "even" : "odd"}
227 | XCTAssertEqual(test["even"]!, [2,4], "SHould have even group")
228 | XCTAssertEqual(test["odd"]!, [1,3,5], "SHould have odd group")
229 |
230 | }
231 |
232 | func testReject(){
233 | XCTAssertEqual([1,2,3,4,5].reject{$0 % 2 == 0}, [1,3,5], "should return odd values")
234 | XCTAssertEqual([1,2,3,4,5].reject{$0 % 2 != 0}, [2,4], "should return even values")
235 |
236 | }
237 | }
238 |
239 |
240 | fileprivate func compare(obj1: [String:Int], obj2: [String:Int]) -> Bool {
241 | return obj1["x"] == obj2["x"] && obj1["y"] == obj2["y"]
242 | }
243 |
244 | fileprivate func ==(lhs: [[String: Int]], rhs: [[String: Int]]) -> Bool {
245 | guard lhs.count == rhs.count else { return false }
246 | var currentIdx = 0
247 | while currentIdx < lhs.count {
248 | if !compare(obj1: lhs[currentIdx], obj2: rhs[currentIdx]) {
249 | return false
250 | }
251 | currentIdx += 1
252 | }
253 | return true
254 | }
255 |
256 | fileprivate func ==(lhs: [[Int]], rhs: [[Int]]) -> Bool {
257 | guard lhs.count == rhs.count else { return false }
258 | var currentIdx = 0
259 | while currentIdx < lhs.count {
260 | if lhs[currentIdx] != rhs[currentIdx] {
261 | return false
262 | }
263 | currentIdx += 1
264 | }
265 | return true
266 | }
267 |
--------------------------------------------------------------------------------
/Source/ArrayExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ArrayExtensions.swift
3 | // Swiftly
4 | //
5 | // Created by Khoi Lai on 26/09/2016.
6 | // Copyright © 2016 Khoi Lai. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Array {
12 | /// Create an array of elements split in to groups by the length of size.
13 | /// If array can't be split evenly, the final chunk contains all the remain elements.
14 | /// - parameter size: The length of each chunk. 0 by default.
15 | ///
16 | /// - returns: The new array contains chunks
17 | func chunk(size: Int = 0) -> [[Element]] {
18 | var result = [[Element]]()
19 | if size < 1 {
20 | return result
21 | }
22 | let length = self.count
23 | var i = 0
24 | while i < length {
25 | let start = i
26 | i += size
27 | result.append(self.slice(start: start, end: i))
28 | }
29 | return result
30 | }
31 |
32 | /// Creates a new array concatenating additional values.
33 | ///
34 | /// - parameter values: The values to concatenate.
35 | ///
36 | /// - returns: The new concatenated array.
37 | func concat(values: Element...) -> [Element] {
38 | var result = Array(self)
39 | result.append(contentsOf: values)
40 | return result
41 | }
42 |
43 | /// This method is like difference except that it accepts comparator which is invoked to compare elements of array to values.
44 | ///
45 | /// - parameter values: The values to exclude.
46 | /// - parameter comparator: The comparator invoked per element.
47 | ///
48 | /// - returns: Returns the new array of filtered values.
49 | func differenceWith(_ values: [Element], comparator: (Element, Element) -> Bool) -> [Element] {
50 | return self._baseDifference(with: values, comparator: comparator, iteratee: nil)
51 | }
52 |
53 | /// Creates a new array concatenating additional arrays.
54 | ///
55 | /// - parameter arrays: The arrays to concatenate.
56 | ///
57 | /// - returns: The new concatenated array.
58 | func concat(arrays: [Element]...) -> [Element] {
59 | var result = Array(self)
60 | for arr in arrays {
61 | result.append(contentsOf: arr)
62 | }
63 | return result
64 | }
65 |
66 | /// Creates a new sliced array with n elements dropped from the beginning.
67 | ///
68 | /// - parameter n: The number of elements to drop. default to `1`
69 | ///
70 | /// - returns: Returns the new sliced array.
71 | func drop(_ n: Int = 1) -> [Element] {
72 | return self.slice(start: Swift.max(n, 0), end: self.count)
73 | }
74 |
75 | /// Creates a slice of array with n elements dropped from the end.
76 | ///
77 | /// - parameter n: The number of elements to drop.
78 | ///
79 | /// - returns: Returns the new sliced array.
80 | func dropRight(_ n: Int = 1) -> [Element] {
81 | let end = self.count - n
82 | return self.slice(start: 0, end: end < 0 ? 0 : end)
83 | }
84 |
85 | /// Creates a slice of array excluding elements dropped from the end. Elements are dropped until predicate returns false.
86 | ///
87 | /// - parameter predicate: The function invoked per iteration.
88 | ///
89 | /// - returns: Returns the new sliced array.
90 | func dropRightWhile(_ predicate: (Element) -> Bool) -> [Element] {
91 | return self._baseWhile(predicate, isDrop: true, fromRight: true)
92 | }
93 |
94 | /// Creates a slice of array excluding elements dropped from the beginning. Elements are dropped until predicate returns false.
95 | ///
96 | /// - parameter predicate: The function invoked per iteration.
97 | ///
98 | /// - returns: Returns the new sliced array.
99 | func dropWhile(_ predicate: (Element) -> Bool) -> [Element] {
100 | return self._baseWhile(predicate, isDrop: true)
101 | }
102 |
103 | /// Returns true if all of the values in the array pass the predicate test. Stop traversing the list once a falsey value found.
104 | ///
105 | /// - Parameter predicate: The function invoked per iteration.
106 | /// - Returns: Boolean
107 | func every(_ predicate: (Element) -> Bool) -> Bool {
108 | for elem in self {
109 | if !predicate(elem) {
110 | return false
111 | }
112 | }
113 | return true
114 | }
115 |
116 | /// Returns the index of the first element predicate returns true for.
117 | ///
118 | /// - parameter predicate: The function invoked per iteration.
119 | ///
120 | /// - returns: Returns the index of the found element, else nil.
121 | func findIndex(_ predicate: (Element) -> Bool) -> Int? {
122 | return _baseFindIndex(predicate)
123 | }
124 |
125 | /// This method is like .findIndex except it iterates over the elements of array from right to left.
126 | ///
127 | /// - parameter predicate: The function invoked per iteration.
128 | ///
129 | /// - returns: Returns the index of the found element, else nil.
130 | func findLastIndex(_ predicate: (Element) -> Bool) -> Int? {
131 | return _baseFindIndex(predicate, fromRight: true)
132 | }
133 |
134 | /// This method is like .intersection except that it accepts comparator which is invoked to compare elements of arrays.
135 | ///
136 | /// - parameter arrays: The arrays to inspect.
137 | /// - parameter comparator: The comparator invoked per element.
138 | ///
139 | /// - returns: Returns the new array of shared values.
140 | func intersectionWith(_ arrays: [Element]..., comparator: (Element, Element) -> Bool) -> [Element] {
141 | return self._baseIntersection(arrays: arrays, comparator: comparator)
142 | }
143 |
144 | /// Create a dictionary where the key is a Hashable got by run iteratee through the element, and the value is the arrays of the elements responsible for getting that key
145 | ///
146 | /// - Parameter iteratee: The iteratee invoked per element.
147 | /// - Returns: Returns the dictionary [Hashable: [Element]]
148 | func groupBy(_ iteratee: (Element) -> T) -> [T: [Element]] {
149 | var result = [T: [Element]]()
150 | for elem in self {
151 | let key = iteratee(elem)
152 | result[key] == nil ? result[key] = [elem] : result[key]?.append(elem)
153 | }
154 | return result
155 | }
156 |
157 | /// Return an array by slicing from start up to, but not including, end.
158 | ///
159 | /// - parameter start: The start position.
160 | /// - parameter end: The end position. `nil` by default
161 | ///
162 | /// - returns: The sliced array
163 | func slice(start: Int, end: Int? = nil) -> [Element] {
164 | let end = Swift.min(end ?? self.count, self.count)
165 | if start > self.count || start > end {
166 | return []
167 | }
168 | return Array(self[start.. Bool) -> Bool {
176 | for elem in self {
177 | if predicate(elem) {
178 | return true
179 | }
180 | }
181 | return false
182 | }
183 |
184 | /// Returns a shuffled copy of the array, using a version of the Fisher-Yates shuffle.
185 | ///
186 | /// - Returns: Returns the new shuffled array.
187 | func shuffled() -> [Element] {
188 | var newArray = Array(self)
189 | newArray.shuffle()
190 | return newArray
191 | }
192 |
193 | /// Shuffle the elements of the collection in place.
194 | mutating func shuffle() {
195 | for index in 0.. Bool) -> [Element] {
208 | return _baseXor(arrays: arrays, isXorBy:false, comparator:comparator)
209 | }
210 | }
211 |
212 | // MARK: Element: Equatable
213 | public extension Array where Element: Equatable {
214 |
215 | /// Creates an array of unique array values not included in the other provided arrays.
216 | ///
217 | /// - parameter values: The values to exclude.
218 | ///
219 | /// - returns: Returns the new array of filtered values.
220 | func difference(_ values: [Element]) -> [Element] {
221 | return self._baseDifference(with: values, comparator: ==)
222 | }
223 |
224 | /// This method is like difference except that it accepts iteratee which is invoked for each element of array and values to generate the criterion by which uniqueness is computed.
225 | ///
226 | /// - parameter values: The values to exclude.
227 | /// - parameter iteratee: The iteratee invoked per element.
228 | ///
229 | /// - returns: Returns the new array of filtered values.
230 | func differenceBy(_ values: [Element], iteratee: @escaping ((Element) -> Element)) -> [Element] {
231 | return self._baseDifference(with: values, comparator: ==, iteratee: iteratee)
232 | }
233 |
234 | /// Creates an array of unique values that are included in all of the provided arrays.
235 | ///
236 | /// - parameter arrays: The arrays to inspect.
237 | ///
238 | /// - returns: Returns the new array of shared values.
239 | func intersection(_ arrays: [Element]...) -> [Element] {
240 | return self._baseIntersection(arrays: arrays, comparator: ==)
241 | }
242 |
243 | /// This method is like .intersection except that it accepts iteratee which is invoked for each element of each arrays to generate the criterion by which uniqueness is computed.
244 | ///
245 | /// - parameter arrays: The arrays to inspect.
246 | /// - parameter iteratee: The iteratee invoked per element.
247 | ///
248 | /// - returns: Returns the new array of shared values.
249 | func intersectionBy(_ arrays: [Element]..., iteratee: @escaping (Element) -> Element) -> [Element] {
250 | return self._baseIntersection(arrays: arrays, comparator: ==, iteratee: iteratee)
251 | }
252 |
253 | /// Creates an array excluding all given values using == comparator
254 | ///
255 | /// - Parameter values: The values to exclude.
256 | /// - Returns: Returns the new array of filtered values.
257 | func without(_ values: Element...) -> [Element] {
258 | return _baseDifference(with: values, comparator: ==)
259 | }
260 |
261 | /// Alias of .sample
262 | ///
263 | /// - Returns: A random element
264 | func random() -> Element {
265 | return sample()
266 | }
267 |
268 | /// Return a random value from the array
269 | ///
270 | /// - Returns: A random value
271 | func sample() -> Element {
272 | return self[Swiftly.random(lower: 0, upper: count - 1)]
273 | }
274 |
275 | /// Gets n random elements from collection.
276 | /// Using [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
277 | ///
278 | /// - Parameter n: The number of elements to sample. 0 by default.
279 | /// - Returns: Array of random elements
280 | func sampleSize(_ n: Int = 0) -> [Element] {
281 | let number = Swift.max(0, Swift.min(n, count))
282 | return shuffled().slice(start: 0, end: number)
283 | }
284 |
285 | /// The opposite of .filter; this method returns the elements of collection that predicate does not return true for.
286 | ///
287 | /// - Parameter predicate: The function invoked per iteration
288 | /// - Returns: Returns the new filtered array.
289 | func reject(_ predicate: (Element) -> Bool) -> [Element] {
290 | return filter { !predicate($0) }
291 | }
292 |
293 | /// Creates an array of unique values that is the symmetric difference of the provided arrays.
294 | ///
295 | /// - Parameter arrays: The arrays to inspect.
296 | /// - Returns: Returns the new array of values.
297 | static func xor(arrays: [Element]...) -> [Element] {
298 | return _baseXor(arrays: arrays, isXorBy: false, comparator: ==, iteratee: nil)
299 | }
300 |
301 | /// This method is like .xor except that it accepts iteratee which is invoked for each element of each arrays to generate the criterion by which uniqueness is computed.
302 | ///
303 | /// - Parameters:
304 | /// - arrays: The arrays to inspect.
305 | /// - iteratee: The iteratee invoked per element.
306 | /// - Returns: Returns the new array of values.
307 | static func xorBy(arrays: [Element]..., iteratee: @escaping ((Element) -> Element)) -> [Element] {
308 | return _baseXor(arrays: arrays, isXorBy: true, comparator: ==, iteratee: iteratee)
309 | }
310 | }
311 |
312 | // MARK: fileprivate helper methods
313 | fileprivate extension Array {
314 | func _baseDifference(with values: [Element],
315 | comparator: (Element, Element) -> Bool,
316 | iteratee: ((Element) -> Element)? = nil) -> [Element] {
317 | var result = [Element]()
318 | for elem1 in self {
319 | var isUnique = true
320 | let val1 = iteratee != nil ? iteratee!(elem1) : elem1
321 | for elem2 in values {
322 | let val2 = iteratee != nil ? iteratee!(elem2) : elem2
323 | if comparator(val1, val2) {
324 | isUnique = false
325 | break
326 | }
327 | }
328 | if isUnique { result.append(elem1) }
329 | }
330 | return result
331 | }
332 |
333 | func _baseWhile(_ predicate: ((Element) -> Bool),
334 | isDrop: Bool = false,
335 | fromRight: Bool = false) -> [Element] {
336 | let length = self.count
337 | var index = fromRight ? length : -1
338 |
339 | repeat {
340 | index += fromRight ? -1 : 1
341 | } while (fromRight ? index >= 0 : index < length) && predicate(self[index])
342 |
343 | return isDrop ? self.slice(start: fromRight ? 0 : index, end: fromRight ? index + 1 : length) : self.slice(start: fromRight ? index + 1 : 0, end: fromRight ? length : index)
344 | }
345 |
346 | func _baseFindIndex(_ predicate: ((Element) -> Bool),
347 | fromRight: Bool = false) -> Int? {
348 | let length = self.count
349 |
350 | let strideRange = fromRight ? stride(from: length - 1, to: 0, by: -1) : stride(from: 0, to: length - 1, by: 1)
351 |
352 | for i in strideRange {
353 | if predicate(self[i]) { return i }
354 | }
355 |
356 | return nil
357 | }
358 |
359 | func _baseIntersection(arrays: [[Element]],
360 | comparator: (Element, Element) -> Bool,
361 | iteratee: ((Element) -> Element)? = nil) -> [Element] {
362 | var result = self
363 |
364 | for i in 0..(arrays: [[T]], isXorBy: Bool, comparator: (T, T) -> Bool, iteratee: ((T) -> T)?=nil) -> [T] {
387 | guard !arrays.isEmpty else { return [] }
388 | guard arrays.count > 1 else { return arrays[0] }
389 |
390 | var result = arrays[arrays.count - 1]
391 |
392 | for i in (0...arrays.count - 2).reversed() {
393 | let nextArr = arrays[i]
394 | var tmp = [T]()
395 | var uniqueElemMarker = [Bool](repeating: true, count: result.count)
396 |
397 | for elem1 in nextArr {
398 | var isUnique = true
399 | for j in 0..