├── .github
└── workflows
│ └── swift.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── LICENSE
├── Package.swift
├── README.md
├── Sources
├── Extensions
│ └── Foundation
│ │ ├── Array
│ │ ├── Array+Data.swift
│ │ └── Array+Subscript.swift
│ │ ├── Bundle
│ │ └── Bundle+Extensions.swift
│ │ ├── Codable
│ │ └── Codable+JSON.swift
│ │ ├── Data
│ │ ├── Data+Extensions.swift
│ │ ├── Data+MimeType.swift
│ │ ├── Data+PrettyJSON.swift
│ │ └── MimeType.swift
│ │ ├── Date
│ │ ├── Date+Components.swift
│ │ └── Date+Extensions.swift
│ │ ├── Double
│ │ ├── Double+ChineseCurrency.swift
│ │ └── Double+Extensions.swift
│ │ ├── Int
│ │ ├── Int+ChineseCurrency.swift
│ │ └── Int+Extensions.swift
│ │ ├── Mirror
│ │ └── Mirror+Reflection.swift
│ │ ├── NSNumber
│ │ └── NSNumber+Display.swift
│ │ ├── NSObject
│ │ └── NSObject+Name.swift
│ │ ├── Optional
│ │ └── Optional+Transform.swift
│ │ ├── Range
│ │ └── Range+Extensions.swift
│ │ ├── Sequence
│ │ └── Sequence+Extensions.swift
│ │ └── String
│ │ ├── String+Emoji.swift
│ │ ├── String+Extensions.swift
│ │ ├── String+File.swift
│ │ ├── String+MD5.swift
│ │ ├── String+Modify.swift
│ │ ├── String+Subscript.swift
│ │ └── String+Validation.swift
├── Info.plist
└── SwiftWings.h
├── SwiftWings.podspec
├── SwiftWings.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── SwiftWings macOS.xcscheme
│ ├── SwiftWings tvOS.xcscheme
│ ├── SwiftWings watchOS.xcscheme
│ └── SwiftWings.xcscheme
└── Tests
├── Data
├── amrFile.amr
├── bmpFile.bmp
├── bz2File.bz2
├── flacFile.flac
├── gifFile.gif
├── gzFile.gz
├── icoFile.ico
├── jpegFile.jpeg
├── jpgFile.jpg
├── m4aFile.m4a
├── midFile.mid
├── mp3File.mp3
├── oggFile.ogg
├── opusFile.opus
├── pngFile.png
├── psdFile.psd
├── rarFile.rar
├── sqliteFile.sqlite
├── tarFile.tar
├── tiffFile.tiff
├── wavFile.wav
└── webpFile.webp
├── Extensions
├── Array
│ ├── Array+DataTests.swift
│ └── Array+SubscriptTests.swift
├── Bundle
│ └── Bundle+ExtensionsTests.swift
├── Codable
│ └── Codable+JSONTests.swift
├── Data
│ ├── AudioMimeTypeTests.swift
│ ├── Data+MimeTypeTests.swift
│ ├── Data+PrettyJSONTests.swift
│ └── MimeTypeTests.swift
├── Date
│ ├── Date+ComponentsTests.swift
│ └── Date+ExtensionsTests.swift
├── Double
│ ├── Double+ExtensionsTests.swift
│ └── Double_ChineseMoneyTests.swift
├── Int
│ ├── Int+ChineseCurrencyTests.swift
│ └── Int+ExtensionsTests.swift
├── Mirror
│ └── Mirror+ReflectionTests.swift
├── NSObject
│ └── NSObject+NameTests.swift
├── Optional
│ └── Optional+TransformTests.swift
├── Sequence
│ └── Sequence+ExtensionsTests.swift
└── String
│ ├── String+EmojiTests.swift
│ ├── String+ExtensionsTests.swift
│ ├── String+MD5Tests.swift
│ ├── String+ModifyTests.swift
│ ├── String+SubscriptTests.swift
│ └── String+ValidationTests.swift
├── Info.plist
└── TestTypes
├── Extensions
└── Data+File.swift
└── Models
└── CodableModel.swift
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | name: Swift
2 |
3 | on: [push]
4 |
5 | jobs:
6 | iOS:
7 | name: iOS
8 | runs-on: macOS-latest
9 | strategy:
10 | matrix:
11 | destination: ['platform=iOS Simulator,OS=14.4,name=iPhone 12 Pro']
12 | scheme: ['SwiftWings']
13 | steps:
14 | - name: Checkout
15 | uses: actions/checkout@master
16 | - name: Build and test
17 | run: |
18 | xcodebuild clean test -project SwiftWings.xcodeproj -scheme SwiftWings -destination "${destination}" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO
19 | env:
20 | destination: ${{ matrix.destination }}
21 | - name: Upload Code Coverage
22 | run: bash <(curl -s https://codecov.io/bash) -J 'SwiftWings' -t ${{ secrets.CODECOV_TOKEN }}
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | .DS_Store
6 |
7 | ## User settings
8 | xcuserdata/
9 |
10 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
11 | *.xcscmblueprint
12 | *.xccheckout
13 |
14 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
15 | build/
16 | DerivedData/
17 | *.moved-aside
18 | *.pbxuser
19 | !default.pbxuser
20 | *.mode1v3
21 | !default.mode1v3
22 | *.mode2v3
23 | !default.mode2v3
24 | *.perspectivev3
25 | !default.perspectivev3
26 |
27 | ## Obj-C/Swift specific
28 | *.hmap
29 |
30 | ## App packaging
31 | *.ipa
32 | *.dSYM.zip
33 | *.dSYM
34 |
35 | ## Playgrounds
36 | timeline.xctimeline
37 | playground.xcworkspace
38 |
39 | # Swift Package Manager
40 | #
41 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
42 | # Packages/
43 | # Package.pins
44 | # Package.resolved
45 | # *.xcodeproj
46 | #
47 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
48 | # hence it is not needed unless you have added a package configuration file to your project
49 | # .swiftpm
50 |
51 | .build/
52 |
53 | # CocoaPods
54 | #
55 | # We recommend against adding the Pods directory to your .gitignore. However
56 | # you should judge for yourself, the pros and cons are mentioned at:
57 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
58 | #
59 | # Pods/
60 | #
61 | # Add this line if you want to avoid checking in source code from the Xcode workspace
62 | # *.xcworkspace
63 |
64 | # Carthage
65 | #
66 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
67 | # Carthage/Checkouts
68 |
69 | Carthage/Build/
70 |
71 | # Accio dependency management
72 | Dependencies/
73 | .accio/
74 |
75 | # fastlane
76 | #
77 | # It is recommended to not store the screenshots in the git repo.
78 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
79 | # For more information about the recommended setup visit:
80 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
81 |
82 | fastlane/report.xml
83 | fastlane/Preview.html
84 | fastlane/screenshots/**/*.png
85 | fastlane/test_output
86 |
87 | # Code Injection
88 | #
89 | # After new code Injection tools there's a generated folder /iOSInjectionProject
90 | # https://github.com/johnno1962/injectionforxcode
91 |
92 | iOSInjectionProject/
93 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Chunyu Li
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(name: "SwiftWings",
7 | platforms: [.macOS(.v10_11),
8 | .iOS(.v12),
9 | .tvOS(.v9),
10 | .watchOS(.v2)],
11 | products: [.library(name: "SwiftWings",
12 | targets: ["SwiftWings"])],
13 | targets: [.target(name: "SwiftWings",
14 | path: "Sources")],
15 | swiftLanguageVersions: [.v5])
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftWings
2 |
3 | 
4 | [](https://cocoapods.org/pods/SwiftWings)
5 | [](https://github.com/leacode/SwiftWings/blob/master/LICENSE)
6 | [](https://cocoapods.org/pods/SwiftWings)
7 | [](https://codecov.io/gh/leacode/SwiftWings)
8 |
9 |
10 | `SwiftWings` is A collection of Swift extensions. It support iOS, macOS, tvOS, watchOS. You can also integrate the framework in your server-side project by using Swift Package Manager.
11 |
12 | ## Requirements
13 |
14 | - iOS 12.0+ / macOS 10.11+
15 | - Swift 5.0+
16 | - Watch OS 2.0+
17 | - TV OS 9.0+
18 | - Linux can run Swift
19 |
20 | If you're supporting iOS 8+ and macOS 10.xx, you can specify version of v1.5
21 |
22 | ## Installation
23 |
24 | ### CocoaPods
25 |
26 | [CocoaPods](https://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:
27 |
28 | ```bash
29 | $ gem install cocoapods
30 | ```
31 |
32 | SwiftWings is available through [CocoaPods](https://cocoapods.org). To install
33 | it, simply add the following line to your Podfile:
34 |
35 | ```ruby
36 | use_frameworks!
37 |
38 | target '' do
39 | pod 'SwiftWings'
40 | end
41 | ```
42 |
43 | Then, run the following command:
44 |
45 | ```bash
46 | $ pod install
47 | ```
48 |
49 | ### Carthage
50 |
51 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
52 |
53 | You can install Carthage with [Homebrew](https://brew.sh/) using the following command:
54 |
55 | ```bash
56 | $ brew update
57 | $ brew install carthage
58 | ```
59 |
60 | To integrate SwiftWings into your Xcode project using Carthage, specify it in your `Cartfile`:
61 |
62 | ```ogdl
63 | github "leacode/SwiftWings"
64 | ```
65 |
66 | Run `carthage update` to build the framework and drag the built `SwiftWings.framework` into your Xcode project.
67 |
68 | ### Swift Package Manager
69 |
70 | The [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
71 |
72 | #### iOS/ MacOS:
73 |
74 | To integrate `SwiftWings` into your project , specify it in `File > Swift Packages > Add`:
75 |
76 | ```
77 | https://github.com/leacode/SwiftWings
78 | ```
79 |
80 | From 1.6.0 the framework support Mac os >= 11.0.
81 |
82 |
83 | #### Server Side:
84 |
85 | Add dependency in Package.swift:
86 |
87 | ```
88 | dependencies: [
89 | .package(url: "https://github.com/leacode/SwiftWings.git", from: "1.9.0"),
90 | ]
91 |
92 | ...
93 |
94 | .target(name: "App", dependencies: ["SwiftWings", ...]),
95 |
96 | ...
97 |
98 | ```
99 |
100 | ## List of Extensions
101 |
102 |
103 | Extensions
104 |
105 |
137 |
138 |
139 | ## Author
140 |
141 | **Chunyu Li**
142 |
143 | - [Linked in](http://www.linkedin.com/in/春毓-李-96920b92/)
144 | - [简书](https://www.jianshu.com/u/1c5cb3408b0f)
145 | - [Twitter](https://twitter.com/leacode) / [Facebook](https://www.facebook.com/leacode.lea)
146 |
147 | ## License
148 |
149 | `SwiftWings` is available under the MIT license. See the [LICENSE](https://github.com/leacode/SwiftWings/blob/master/LICENSE) file for more info.
150 |
151 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Array/Array+Data.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+Data.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/20.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Array where Element == UInt8 {
12 | var data : Data{
13 | return Data(self)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Array/Array+Subscript.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+Subscript.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/19.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Array {
12 |
13 | /// when a index is out of bound, the subscription returns nil
14 | subscript(guard idx: Int) -> Element? {
15 | guard (startIndex..Element) -> Element? {
28 | return first.map {
29 | dropFirst().reduce($0, nextPartialResult)
30 | }
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Bundle/Bundle+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bundle+Extensions.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/9/3.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Bundle {
12 |
13 | var appVersion: String? {
14 | self.infoDictionary?["CFBundleShortVersionString"] as? String
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Codable/Codable+JSON.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Codable+JSON.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/20.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Encodable {
12 | var jsonString: String? {
13 | let jsonEncoder = JSONEncoder()
14 | do {
15 | let jsonData = try jsonEncoder.encode(self)
16 | return String(data: jsonData, encoding: .utf8)
17 | } catch {
18 | return nil
19 | }
20 | }
21 |
22 | func dictionary() throws -> [String: Any]? {
23 | let data = try JSONEncoder().encode(self)
24 |
25 | return try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String : Any]
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Data/Data+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data+Extensions.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/8.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Data {
12 | var bytes: [UInt8] {
13 | return [UInt8](self)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Data/Data+MimeType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data+MimeType.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/8.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Data {
12 |
13 | // MARK: - Audio Types
14 |
15 | var isAmr: Bool {
16 | matches(bytes: [0x23, 0x21, 0x41,
17 | 0x4D, 0x52, 0x0A])
18 | }
19 |
20 | var isMp3: Bool {
21 | matches(bytes: [0x49, 0x44, 0x33]) ||
22 | matches(bytes: [0xFF, 0xFB])
23 | }
24 |
25 | var isOgg: Bool {
26 | matches(bytes: [0x4F, 0x67, 0x67, 0x53])
27 | }
28 |
29 | var isFlac: Bool {
30 | matches(bytes: [0x66, 0x4C, 0x61, 0x43])
31 | }
32 |
33 | var isWav: Bool {
34 | matches(bytes: [0x52, 0x49, 0x46, 0x46]) &&
35 | matches(bytes: [0x57, 0x41, 0x56, 0x45], range: 8...11)
36 | }
37 |
38 | var isMid: Bool {
39 | matches(bytes: [0x4D, 0x54, 0x68, 0x64])
40 | }
41 |
42 | var isM4a: Bool {
43 | matches(bytes: [0x4D, 0x34, 0x41, 0x20]) ||
44 | matches(bytes: [0x66, 0x74, 0x79, 0x70,
45 | 0x4D, 0x34, 0x41],
46 | range: 4...10)
47 | }
48 |
49 | var isOpus: Bool {
50 | matches(bytes: [0x4F, 0x70, 0x75, 0x73,
51 | 0x48, 0x65, 0x61, 0x64],
52 | range: 28...35)
53 | }
54 |
55 | // MARK: - Image Types
56 |
57 | var isPng: Bool {
58 | matches(bytes: [0x89, 0x50, 0x4E, 0x47])
59 | }
60 |
61 | var isJPEG: Bool {
62 | matches(bytes: [0xFF, 0xD8, 0xFF])
63 | }
64 |
65 | var isJpg: Bool {
66 | isJPEG
67 | }
68 |
69 | var isGif: Bool {
70 | matches(bytes: [0x47, 0x49, 0x46])
71 | }
72 |
73 | var isWebp: Bool {
74 | matches(bytes: [0x57, 0x45, 0x42, 0x50], range: 8...11)
75 | }
76 |
77 | var isTiff: Bool {
78 | matches(bytes: [0x49, 0x49, 0x2A, 0x00]) ||
79 | matches(bytes: [0x4D, 0x4D, 0x00, 0x2A])
80 | }
81 |
82 | var isBmp: Bool {
83 | matches(bytes: [0x42, 0x4D])
84 | }
85 |
86 | var isPsd: Bool {
87 | return matches(bytes: [0x38, 0x42, 0x50, 0x53])
88 | }
89 |
90 | // MARK: - Application Types
91 |
92 | var isIco: Bool {
93 | matches(bytes: [0x00, 0x00, 0x01, 0x00])
94 | }
95 |
96 | var isSqlite: Bool {
97 | matches(bytes: [0x53, 0x51, 0x4C, 0x69])
98 | }
99 |
100 | var isTar: Bool {
101 | matches(bytes: [0x75, 0x73, 0x74, 0x61, 0x72],
102 | range: 257...261)
103 | }
104 |
105 | var isRar: Bool {
106 | matches(bytes: [0x52, 0x61, 0x72, 0x21, 0x1A, 0x07]) &&
107 | (matches(bytes: [0x0], range: 6...6) ||
108 | matches(bytes: [0x1], range: 6...6))
109 | }
110 |
111 | var isGzip: Bool {
112 | matches(bytes: [0x1F, 0x8B, 0x08])
113 | }
114 |
115 | var isBz2: Bool {
116 | matches(bytes: [0x42, 0x5A, 0x68])
117 | }
118 |
119 | private func matches(bytes: [UInt8], range: CountableClosedRange? = nil) -> Bool {
120 | Array(self.bytes[range ?? 0...(bytes.count - 1)]) == bytes
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Data/Data+PrettyJSON.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data+PrettyJSON.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/20.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Data {
12 | /// pring json data pretty
13 | ///
14 | /// If you use String instead with NSString,
15 | ///
16 | /// the output in consol will be escaped
17 | ///
18 | /// like “{\n \”userId\”: 1,\n \”id\”: 1,\n \”title\”: ...
19 | ///
20 | /// So don't use String for return type
21 | var prettyJSONString: NSString? {
22 | guard let jsonObject = try? JSONSerialization.jsonObject(with: self, options: [.allowFragments]),
23 | let data = try? JSONSerialization.data(withJSONObject: jsonObject, options: [.prettyPrinted]),
24 | let prettyPrintedString = String(data: data, encoding: .utf8) else { return nil }
25 | return prettyPrintedString as NSString
26 | }
27 | }
28 |
29 | public extension Data {
30 | init?(json: Any) {
31 | guard let data = try? JSONSerialization.data(withJSONObject: json, options: .fragmentsAllowed) else { return nil }
32 | self.init(data)
33 | }
34 |
35 | func jsonToDictionary() -> [String: Any]? {
36 | (try? JSONSerialization.jsonObject(with: self, options: .allowFragments)) as? [String: Any]
37 | }
38 |
39 | func jsonToArray() -> [Any]? {
40 | (try? JSONSerialization.jsonObject(with: self, options: .allowFragments)) as? [Any]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Data/MimeType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MimeType.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/10.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct MimeType {
12 | let name: String
13 | let suffix: String
14 | let fileType: FileType
15 | }
16 |
17 | private let mimeTypes = [
18 | // Audio Types
19 | "amr": MimeType(name: "audio/amr", suffix: "amr", fileType: .amr),
20 | "flac": MimeType(name: "audio/x-flac", suffix: "flac", fileType: .flac),
21 | "m4a": MimeType(name: "audio/m4a", suffix: "m4a", fileType: .m4a),
22 | "midi": MimeType(name: "audio/midi", suffix: "midi", fileType: .midi ),
23 | "mp3": MimeType(name: "audio/mpeg", suffix: "mp3", fileType: .mp3),
24 | "ogg": MimeType(name: "audio/ogg", suffix: "ogg", fileType: .ogg),
25 | "opus": MimeType(name: "audio/opus", suffix: "opus", fileType: .opus),
26 | "ra": MimeType(name: "audio/x-realaudio", suffix: "ra", fileType: .ra),
27 | "wav": MimeType(name: "audio/x-wav", suffix: "wav", fileType: .wav),
28 | // Video Types
29 | "3gpp": MimeType(name: "video/3gpp", suffix: "3gpp", fileType: .threegpp),
30 | "asf": MimeType(name: "video/x-ms-asf", suffix: "asf", fileType: .asf),
31 | "avi": MimeType(name: "video/x-msvideo", suffix: "avi", fileType: .avi),
32 | "flv": MimeType(name: "video/x-flv", suffix: "flv", fileType: .flv),
33 | "m4v": MimeType(name: "video/x-m4v", suffix: "m4v", fileType: .m4v),
34 | "mkv": MimeType(name: "video/x-matroska", suffix: "mkv", fileType: .mkv),
35 | "mng": MimeType(name: "video/x-mng", suffix: "mng", fileType: .mng),
36 | "mov": MimeType(name: "video/quicktime", suffix: "mov", fileType: .mov),
37 | "mp4": MimeType(name: "video/mp4", suffix: "mp4", fileType: .mp4),
38 | "mpg": MimeType(name: "video/mpeg", suffix: "mpg", fileType: .mpg),
39 | "ts": MimeType(name: "video/mp2t", suffix: "wav", fileType: .ts),
40 | "webm": MimeType(name: "video/webm", suffix: "webm", fileType: .webm),
41 | "wmv": MimeType(name: "video/x-ms-wmv", suffix: "wmv", fileType: .wmv),
42 | // Image Types
43 | "bmp": MimeType(name: "image/x-ms-bmp", suffix: "bmp", fileType: .bmp),
44 | "cr2": MimeType(name: "image/x-canon-cr2", suffix: "cr2", fileType: .cr2),
45 | "flif": MimeType(name: "image/flif", suffix: "flif", fileType: .flif),
46 | "gif": MimeType(name: "image/gif", suffix: "gif", fileType: .gif),
47 | "ico": MimeType(name: "image/x-icon", suffix: "ico", fileType: .ico),
48 | "jng": MimeType(name: "image/x-jng", suffix: "jng", fileType: .jng),
49 | "jpg": MimeType(name: "image/jpeg", suffix: "jpg", fileType: .jpg),
50 | "jxr": MimeType(name: "image/vnd.ms-photo", suffix: "jxr", fileType: .jxr),
51 | "png": MimeType(name: "image/png", suffix: "png", fileType: .png),
52 | "psd": MimeType(name: "image/vnd.adobe.photoshop", suffix: "psd", fileType: .psd),
53 | "svg": MimeType(name: "image/svg+xml", suffix: "svg", fileType: .svg),
54 | "tiff": MimeType(name: "image/tiff", suffix: "tiff", fileType: .tiff),
55 | "wbmp": MimeType(name: "image/vnd.wap.wbmp", suffix: "wbmp", fileType: .wbmp),
56 | "webp": MimeType(name: "image/webp", suffix: "webp", fileType: .webp),
57 | // Text Types
58 | "html": MimeType(name: "text/html", suffix: "html", fileType: .html),
59 | "css": MimeType(name: "text/css", suffix: "css", fileType: .css),
60 | "xml": MimeType(name: "text/xml", suffix: "xml", fileType: .xml),
61 | "mml": MimeType(name: "text/mathml", suffix: "mml", fileType: .mml),
62 | "txt": MimeType(name: "text/plain", suffix: "txt", fileType: .txt),
63 | "jad": MimeType(name: "text/vnd.sun.j2me.app-descriptor", suffix: "jad", fileType: .jad),
64 | "wml": MimeType(name: "text/vnd.wap.wml", suffix: "wml", fileType: .wml),
65 | "htc": MimeType(name: "text/x-component", suffix: "htc", fileType: .htc),
66 | // Application Types
67 | "js": MimeType(name: "application/javascript", suffix: "js", fileType: .js),
68 | "atom": MimeType(name: "application/atom+xml", suffix: "atom", fileType: .atom),
69 | "rss": MimeType(name: "application/rss+xml", suffix: "rss", fileType: .rss),
70 | "epub": MimeType(name: "application/epub+zip", suffix: "epub", fileType: .epub),
71 | "woff": MimeType(name: "application/font-woff", suffix: "woff", fileType: .woff),
72 | "javaArchive": MimeType(name: "application/java-archive", suffix: "javaArchive", fileType: .javaArchive),
73 | "json": MimeType(name: "application/json", suffix: "json", fileType: .json),
74 | "hqx": MimeType(name: "application/mac-binhex40", suffix: "hqx", fileType: .hqx),
75 | "doc": MimeType(name: "application/msword", suffix: "doc", fileType: .doc),
76 | "pdf": MimeType(name: "application/pdf", suffix: "pdf", fileType: .pdf),
77 | "eps": MimeType(name: "application/postscript", suffix: "eps", fileType: .eps),
78 | "rtf": MimeType(name: "application/rtf", suffix: "rtf", fileType: .rtf),
79 | "m3u8": MimeType(name: "application/vnd.apple.mpegurl", suffix: "m3u8", fileType: .m3u8),
80 | "xls": MimeType(name: "application/vnd.ms-excel", suffix: "xls", fileType: .xls),
81 | "eot": MimeType(name: "application/vnd.ms-fontobject", suffix: "eot", fileType: .eot),
82 | "ppt": MimeType(name: "application/vnd.ms-powerpoint", suffix: "ppt", fileType: .ppt),
83 | "wmlc": MimeType(name: "application/vnd.wap.wmlc", suffix: "wmlc", fileType: .wmlc),
84 | "kml": MimeType(name: "application/vnd.google-earth.kml+xml", suffix: "kml", fileType: .kml),
85 | "kmz": MimeType(name: "application/vnd.google-earth.kmz", suffix: "kmz", fileType: .kmz),
86 | "sevenZ": MimeType(name: "application/x-7z-compressed", suffix: "sevenZ", fileType: .sevenZ),
87 | "cco": MimeType(name: "application/x-cocoa", suffix: "cco", fileType: .cco),
88 | "jardiff": MimeType(name: "application/x-java-archive-diff", suffix: "jardiff", fileType: .jardiff),
89 | "jnlp": MimeType(name: "application/x-java-jnlp-file", suffix: "jnlp", fileType: .jnlp),
90 | "run": MimeType(name: "application/x-makeself", suffix: "run", fileType: .run),
91 | "pl": MimeType(name: "application/x-perl", suffix: "pl", fileType: .pl),
92 | "prc": MimeType(name: "application/x-pilot", suffix: "prc", fileType: .prc),
93 | "rar": MimeType(name: "application/x-rar-compressed", suffix: "rar", fileType: .rar),
94 | "rpm": MimeType(name: "application/x-redhat-package-manager", suffix: "rpm", fileType: .rpm),
95 | "sea": MimeType(name: "application/x-sea", suffix: "sea", fileType: .sea),
96 | "swf": MimeType(name: "application/x-shockwave-flash", suffix: "swf", fileType: .swf),
97 | "sit": MimeType(name: "application/x-stuffit", suffix: "sit", fileType: .sit),
98 | "tcl": MimeType(name: "application/x-tcl", suffix: "tcl", fileType: .tcl),
99 | "crt": MimeType(name: "application/x-x509-ca-cert", suffix: "crt", fileType: .crt),
100 | "xpi": MimeType(name: "application/x-xpinstall", suffix: "xpi", fileType: .xpi),
101 | "xhtml": MimeType(name: "application/xhtml+xml", suffix: "xhtml", fileType: .xhtml),
102 | "xspf": MimeType(name: "application/xspf+xml", suffix: "xspf", fileType: .xspf),
103 | "zip": MimeType(name: "application/zip", suffix: "zip", fileType: .zip),
104 | "tar": MimeType(name: "application/x-tar", suffix: "tar", fileType: .tar),
105 | "gz": MimeType(name: "application/gzip", suffix: "gz", fileType: .gz),
106 | "bz2": MimeType(name: "application/x-bzip2", suffix: "bz2", fileType: .bz2),
107 | "dmg": MimeType(name: "application/x-apple-diskimage", suffix: "dmg", fileType: .dmg),
108 | "bin": MimeType(name: "application/octet-stream", suffix: "bin", fileType: .bin),
109 | "nes": MimeType(name: "application/x-nintendo-nes-rom", suffix: "nes", fileType: .nes),
110 | "exe": MimeType(name: "application/x-msdownload", suffix: "exe", fileType: .exe),
111 | "crx": MimeType(name: "application/x-google-chrome-extension", suffix: "crx", fileType: .crx),
112 | "cab": MimeType(name: "application/vnd.ms-cab-compressed", suffix: "cab", fileType: .cab),
113 | "deb": MimeType(name: "application/x-deb", suffix: "deb", fileType: .deb),
114 | "ar": MimeType(name: "application/x-unix-archive", suffix: "ar", fileType: .ar),
115 | "docx": MimeType(name: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", suffix: "docx", fileType: .docx),
116 | "xlsx": MimeType(name: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", suffix: "xlsx", fileType: .xlsx),
117 | "pptx": MimeType(name: "application/vnd.openxmlformats-officedocument.presentationml.presentation", suffix: "pptx", fileType: .pptx),
118 | ]
119 |
120 | public enum AudioMimeType: String {
121 | case amr = "audio/amr"
122 | case midi = "audio/midi"
123 | case mp3 = "audio/mpeg"
124 | case ogg = "audio/ogg"
125 | case m4a = "audio/x-m4a"
126 | case ra = "audio/x-realaudio"
127 | case unknown = "unknown"
128 | }
129 |
130 | extension AudioMimeType: ExpressibleByStringLiteral {
131 | public init(stringLiteral value: String) {
132 | self = AudioMimeType(rawValue: value) ?? AudioMimeType.unknown
133 | }
134 | }
135 |
136 | public enum FileType: String {
137 | case amr = "audio/amr"
138 | case midi = "audio/midi"
139 | case mp3 = "audio/mpeg"
140 | case ogg = "audio/ogg"
141 | case opus = "audio/opus"
142 | case m4a = "audio/x-m4a"
143 | case flac = "audio/x-flac"
144 | case wav = "audio/x-wav"
145 | case ra = "audio/x-realaudio"
146 | case threegpp = "video/3gpp"
147 | case ts = "video/mp2t"
148 | case mp4 = "video/mp4"
149 | case mpg = "video/mpeg"
150 | case mov = "video/quicktime"
151 | case webm = "video/webm"
152 | case mkv = "video/x-matroska"
153 | case flv = "video/x-flv"
154 | case m4v = "video/x-m4v"
155 | case mng = "video/x-mng"
156 | case asf = "video/x-ms-asf"
157 | case wmv = "video/x-ms-wmv"
158 | case avi = "video/x-msvideo"
159 | case png = "image/png"
160 | case jpg = "image/jpeg"
161 | case gif = "image/gif"
162 | case flif = "image/flif"
163 | case tiff = "image/tiff"
164 | case wbmp = "image/vnd.wap.wbmp"
165 | case ico = "image/x-icon"
166 | case jng = "image/x-jng"
167 | case bmp = "image/x-ms-bmp"
168 | case cr2 = "image/x-canon-cr2"
169 | case svg = "image/svg+xml"
170 | case webp = "image/webp"
171 | case psd = "image/vnd.adobe.photoshop"
172 | case jxr = "image/vnd.ms-photo"
173 | case html = "text/html"
174 | case css = "text/css"
175 | case xml = "text/xml"
176 | case mml = "text/mathml"
177 | case txt = "text/plain"
178 | case jad = "text/vnd.sun.j2me.app-descriptor"
179 | case wml = "text/vnd.wap.wml"
180 | case htc = "text/x-component"
181 | case js = "application/javascript"
182 | case atom = "application/atom+xml"
183 | case rss = "application/rss+xml"
184 | case epub = "application/epub+zip"
185 | case woff = "application/font-woff"
186 | case javaArchive = "application/java-archive"
187 | case json = "application/json"
188 | case hqx = "application/mac-binhex40"
189 | case doc = "application/msword"
190 | case pdf = "application/pdf"
191 | case eps = "application/postscript"
192 | case rtf = "application/rtf"
193 | case m3u8 = "application/vnd.apple.mpegurl"
194 | case xls = "application/vnd.ms-excel"
195 | case eot = "application/vnd.ms-fontobject"
196 | case ppt = "application/vnd.ms-powerpoint"
197 | case wmlc = "application/vnd.wap.wmlc"
198 | case kml = "application/vnd.google-earth.kml+xml"
199 | case kmz = "application/vnd.google-earth.kmz"
200 | case sevenZ = "application/x-7z-compressed"
201 | case cco = "application/x-cocoa"
202 | case jardiff = "application/x-java-archive-diff"
203 | case jnlp = "application/x-java-jnlp-file"
204 | case run = "application/x-makeself"
205 | case pl = "application/x-perl"
206 | case prc = "application/x-pilot"
207 | case rar = "application/x-rar-compressed"
208 | case rpm = "application/x-redhat-package-manager"
209 | case sea = "application/x-sea"
210 | case swf = "application/x-shockwave-flash"
211 | case sit = "application/x-stuffit"
212 | case tcl = "application/x-tcl"
213 | case crt = "application/x-x509-ca-cert"
214 | case xpi = "application/x-xpinstall"
215 | case xhtml = "application/xhtml+xml"
216 | case xspf = "application/xspf+xml"
217 | case zip = "application/zip"
218 | case tar = "application/x-tar"
219 | case gz = "application/gzip"
220 | case bz2 = "application/x-bzip2"
221 | case dmg = "application/x-apple-diskimage"
222 | case bin = "application/octet-stream"
223 | case nes = "application/x-nintendo-nes-rom"
224 | case exe = "application/x-msdownload"
225 | case crx = "application/x-google-chrome-extension"
226 | case cab = "application/vnd.ms-cab-compressed"
227 | case deb = "application/x-deb"
228 | case ar = "application/x-unix-archive"
229 | case docx = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
230 | case xlsx = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
231 | case pptx = "application/vnd.openxmlformats-officedocument.presentationml.presentation"
232 |
233 | case unknown = "unknown"
234 |
235 | public init(pathOrFileName: String) {
236 | if let fileExtension = pathOrFileName.fileExtension,
237 | let fileMimeType = mimeTypes[fileExtension] {
238 | self = FileType(stringLiteral: fileMimeType.name)
239 | return
240 | } else {
241 | self = .unknown
242 | }
243 | }
244 | }
245 |
246 | extension FileType: ExpressibleByStringLiteral {
247 | public init(stringLiteral value: String) {
248 | self = FileType(rawValue: value) ?? FileType.unknown
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Date/Date+Components.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date+Components.swift
3 | // SwiftWings
4 | //
5 | // Created by Chunyu Li on 2021/3/22.
6 | // Copyright © 2021 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Date {
12 |
13 | var era: Int { Calendar.current.component(.era, from: self) }
14 |
15 | var year: Int { Calendar.current.component(.year, from: self) }
16 |
17 | var month: Int { Calendar.current.component(.month, from: self) }
18 |
19 | var day: Int { Calendar.current.component(.day, from: self) }
20 |
21 | var hour: Int { Calendar.current.component(.hour, from: self) }
22 |
23 | var minute: Int { Calendar.current.component(.minute, from: self) }
24 |
25 | var second: Int { Calendar.current.component(.second, from: self) }
26 |
27 | var weekday: Int { Calendar.current.component(.weekday, from: self) }
28 |
29 | var weekdayOrdinal: Int { Calendar.current.component(.weekdayOrdinal, from: self) }
30 |
31 | var weekOfMonth: Int { Calendar.current.component(.weekOfMonth, from: self) }
32 |
33 | var weekOfYear: Int { Calendar.current.component(.weekOfYear, from: self) }
34 |
35 | var yearForWeekOfYear: Int { Calendar.current.component(.yearForWeekOfYear, from: self) }
36 |
37 | var nanosecond: Int { Calendar.current.component(.nanosecond, from: self) }
38 |
39 | var calendar: Int { Calendar.current.component(.calendar, from: self) }
40 |
41 | var timeZone: Int { Calendar.current.component(.timeZone, from: self) }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Date/Date+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date+Extensions.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/14.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Date {
12 |
13 | static var currentTimeMillis: Int64 {
14 | Date().timeMillis
15 | }
16 |
17 | var timeMillis: Int64 {
18 | Int64(self.timeIntervalSince1970 * 1000)
19 | }
20 |
21 | var isToday: Bool {
22 | let calendar = Calendar.current
23 | let now = Date()
24 |
25 | let components: Set = [
26 | .era,
27 | .year,
28 | .month,
29 | .day,
30 | ]
31 |
32 | let today = calendar.dateComponents(components, from: now)
33 | let day = calendar.dateComponents(components, from: self)
34 |
35 | return today == day
36 | }
37 |
38 | var isTomorrow: Bool {
39 | let calendar = Calendar.current
40 | let tomorrowDate = Date().addingTimeInterval(60 * 60 * 24)
41 |
42 | let components: Set = [
43 | .era,
44 | .year,
45 | .month,
46 | .day,
47 | ]
48 |
49 | let tomorrow = calendar.dateComponents(components, from: tomorrowDate)
50 | let day = calendar.dateComponents(components, from: self)
51 |
52 | return tomorrow == day
53 | }
54 |
55 | var isInThisWeek: Bool {
56 | let calendar = Calendar.current
57 | let now = Date()
58 |
59 | let components: Set = [
60 | .era,
61 | .year,
62 | .month,
63 | .weekOfMonth,
64 | ]
65 |
66 | let today = calendar.dateComponents(components, from: now)
67 | let day = calendar.dateComponents(components, from: self)
68 |
69 | return today == day
70 | }
71 |
72 | var isInNextWeek: Bool {
73 | let calendar = Calendar.current
74 | let now = Date().addingTimeInterval(60 * 60 * 24 * 7)
75 |
76 | let components: Set = [
77 | .era,
78 | .year,
79 | .month,
80 | .weekOfMonth,
81 | ]
82 |
83 | let today = calendar.dateComponents(components, from: now)
84 | let day = calendar.dateComponents(components, from: self)
85 |
86 | return today == day
87 | }
88 |
89 | // MARK: - Date before & Date after
90 |
91 | var lastMonday: Date {
92 | let dayOfWeek = Calendar.current.component(.weekday, from: self)
93 |
94 | var components = DateComponents()
95 | components.day = -(dayOfWeek + 6 - 1)
96 |
97 | return Calendar.current.date(byAdding: components, to: self)!
98 | }
99 |
100 | var lastTuesday: Date {
101 | let dayOfWeek = Calendar.current.component(.weekday, from: self)
102 |
103 | var components = DateComponents()
104 | components.day = -(dayOfWeek + 6 - 2)
105 |
106 | return Calendar.current.date(byAdding: components, to: self)!
107 | }
108 |
109 | var lastWednesday: Date {
110 | let dayOfWeek = Calendar.current.component(.weekday, from: self)
111 |
112 | var components = DateComponents()
113 | components.day = -(dayOfWeek + 6 - 3)
114 |
115 | return Calendar.current.date(byAdding: components, to: self)!
116 | }
117 |
118 | var lastThursday: Date {
119 | let dayOfWeek = Calendar.current.component(.weekday, from: self)
120 |
121 | var components = DateComponents()
122 | components.day = -(dayOfWeek + 6 - 4)
123 |
124 | return Calendar.current.date(byAdding: components, to: self)!
125 | }
126 |
127 | var lastFriday: Date {
128 | let dayOfWeek = Calendar.current.component(.weekday, from: self)
129 |
130 | var components = DateComponents()
131 | components.day = -(dayOfWeek + 6 - 5)
132 |
133 | return Calendar.current.date(byAdding: components, to: self)!
134 | }
135 |
136 | var lastSatuaday: Date {
137 | let dayOfWeek = Calendar.current.component(.weekday, from: self)
138 |
139 | var components = DateComponents()
140 | components.day = -(dayOfWeek + 6 - 6)
141 |
142 | return Calendar.current.date(byAdding: components, to: self)!
143 | }
144 |
145 | var lastSunday: Date {
146 | let dayOfWeek = Calendar.current.component(.weekday, from: self)
147 |
148 | var components = DateComponents()
149 | components.day = -(dayOfWeek + 6 - 7)
150 |
151 | return Calendar.current.date(byAdding: components, to: self)!
152 | }
153 |
154 | var lastDay: Date? {
155 | Calendar.current.date(byAdding: .day, value: -1, to: self)
156 | }
157 |
158 | var nextDay: Date? {
159 | Calendar.current.date(byAdding: .day, value: 1, to: self)
160 | }
161 |
162 | var lastMonth: Date? {
163 | Calendar.current.date(byAdding: .month, value: -1, to: self)
164 | }
165 |
166 | var nextMonth: Date? {
167 | Calendar.current.date(byAdding: .month, value: 1, to: self)
168 | }
169 |
170 | var lastYear: Date? {
171 | Calendar.current.date(byAdding: .year, value: -1, to: self)
172 | }
173 |
174 | var nextYear: Date? {
175 | Calendar.current.date(byAdding: .year, value: 1, to: self)
176 | }
177 |
178 | }
179 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Double/Double+ChineseCurrency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Double+ChineseMoney.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/3/30.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Double {
12 | var chineseMoneySpell: String? {
13 |
14 | let decimalFormatter = NumberFormatter()
15 | decimalFormatter.maximumFractionDigits = 2
16 | decimalFormatter.minimumFractionDigits = 2
17 | let money = decimalFormatter.string(from: NSNumber(value: self))!
18 |
19 | let value: NSNumber = NSNumber(value: Double(money)!)
20 | let userLocale = Locale(identifier: "zh_Hans_CN")
21 | let formatter = NumberFormatter()
22 | formatter.numberStyle = .spellOut
23 | formatter.locale = userLocale
24 |
25 | let chinese = formatter.string(from: value)!.replacingOccurrences(of: "〇", with: "零")
26 |
27 | var chineseMoney = chinese
28 |
29 | let chineseMoneyDictionary = ["一": "壹",
30 | "二": "贰",
31 | "三": "叁",
32 | "四": "肆",
33 | "五": "伍",
34 | "六": "陆",
35 | "七": "柒",
36 | "八": "捌",
37 | "九": "玖",
38 | "十": "拾",
39 | "百": "佰",
40 | "千": "仟",
41 | "万": "万",
42 | "亿": "亿",
43 | "兆": "兆",
44 | "京": "京"]
45 | chineseMoney = chineseMoney.replacingCharacters(in: chineseMoneyDictionary)
46 |
47 | if chineseMoney.contains("点") {
48 | chineseMoney = chineseMoney.replacingOccurrences(of: "点", with: "圆")
49 | if chineseMoney.contains("零圆") {
50 | chineseMoney = chineseMoney.replacingOccurrences(of: "零圆", with: "")
51 | if chineseMoney.count > 0 {
52 | chineseMoney.insert("角", at: chineseMoney.index(chineseMoney.startIndex, offsetBy: 1))
53 | }
54 | if chineseMoney.count == 3 {
55 | chineseMoney += "分"
56 | }
57 | } else if chineseMoney.contains("零") {
58 | chineseMoney += "分"
59 | } else {
60 | let stringAfterDot = chinese.split(separator: "点").last!
61 | if stringAfterDot.count == 1 {
62 | chineseMoney += "角"
63 | } else {
64 | chineseMoney.insert("角", at: chinese.index(before: chinese.endIndex))
65 | chineseMoney += "分"
66 | }
67 | }
68 | } else {
69 | chineseMoney += "圆整"
70 | }
71 |
72 | if chineseMoney.prefix(1) == "拾" {
73 | chineseMoney.insert("壹", at: chineseMoney.startIndex)
74 | }
75 |
76 | return chineseMoney
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Double/Double+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Double+Extensions.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/9/3.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Double {
12 |
13 | func toString() -> String {
14 | String(format: "%.02f", self)
15 | }
16 |
17 | }
18 |
19 | public extension BinaryFloatingPoint {
20 |
21 | func toInt() -> Int {
22 | Int(self)
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Int/Int+ChineseCurrency.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Int+ChineseCurrency.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/3/30.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Int {
12 | var chineseMoneySpell: String? {
13 | Double(self).chineseMoneySpell
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Int/Int+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Int+Extensions.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/8.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Int {
12 |
13 | /// Array of bytes with optional padding (little-endian)
14 | func bytes(_ totalBytes: Int = MemoryLayout.size) -> [UInt8] {
15 | withUnsafeBytes(of: self.bigEndian) { Array($0) }
16 | }
17 |
18 | var half: Int? {
19 | guard self < -1 || self > 1 else { return nil}
20 | return self / 2
21 | }
22 |
23 | }
24 |
25 | public extension BinaryInteger {
26 |
27 | /// Spell Int Value in Simplified Chinese
28 | var simplifiedChinese: String? {
29 | let value = NSNumber(integerLiteral: Int(self))
30 | let userLocale = Locale(identifier: "zh_Hans_CN")
31 | let formatter = NumberFormatter()
32 | formatter.numberStyle = .spellOut
33 | formatter.locale = userLocale
34 | return formatter.string(from: value)?.replacingOccurrences(of: "〇", with: "零")
35 | }
36 |
37 | /// Spell Int Value in Traditional Chinese
38 | var traditionalChinese: String? {
39 | let value = NSNumber(integerLiteral: Int(self))
40 | let userLocale = Locale(identifier: "zh_Hant")
41 | let formatter = NumberFormatter()
42 | formatter.numberStyle = .spellOut
43 | formatter.locale = userLocale
44 | return formatter.string(from: value)?.replacingOccurrences(of: "〇", with: "零")
45 | }
46 |
47 | var isEven: Bool {
48 | self%2 == 0
49 | }
50 |
51 | func toDouble() -> Double {
52 | Double(self)
53 | }
54 |
55 | func toString() -> String {
56 | "\(self)"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Mirror/Mirror+Reflection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Mirror+Reflection.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/13.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Mirror {
12 | static func reflectProperties(
13 | of target: Any,
14 | matchingType type: T.Type = T.self,
15 | recursively: Bool = false,
16 | using closure: (T) -> Void
17 | ) {
18 | let mirror = Mirror(reflecting: target)
19 |
20 | for child in mirror.children {
21 | (child.value as? T).map(closure)
22 |
23 | if recursively {
24 | // To enable recursive reflection, all we have to do
25 | // is to call our own method again, using the value
26 | // of each child, and using the same closure.
27 | Mirror.reflectProperties(
28 | of: child.value,
29 | recursively: true,
30 | using: closure
31 | )
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/NSNumber/NSNumber+Display.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSNumber+Display.swift
3 | // SwiftWings
4 | //
5 | // Created by Chunyu Li on 2021/1/6.
6 | // Copyright © 2021 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NSNumber {
12 |
13 |
14 |
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/NSObject/NSObject+Name.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSObject+Name.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/3.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension NSObject {
12 | /// Name of class
13 | class var className: String {
14 | String(describing: self)
15 | }
16 |
17 | /// Name of self's class
18 | var className: String {
19 | String(describing: Self.self)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Optional/Optional+Transform.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Optional+Transform.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/19.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | infix operator ???: NilCoalescingPrecedence
12 |
13 |
14 | /// Giving different type of value when the value is nil
15 | ///
16 | /// - Parameters:
17 | /// - optional: original value
18 | /// - defaultValue: default value
19 | public func ???(optional: T?, defaultValue: @autoclosure () -> String) -> String {
20 | switch optional {
21 | case let value?: return String(describing: value)
22 | case nil: return defaultValue()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Range/Range+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Range+Extensions.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/2/29.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/Sequence/Sequence+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Sequence+Extensions.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/2/29.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Sequence where Element: Hashable {
12 | /// returns all unique elements in a Sequence while still maintaining the original order
13 | func unique() -> [Element] {
14 | var seen: Set = []
15 | return filter {
16 | // the insert(_:) method returns a tuple including an inserted boolean which is set to true if the object was inserted and false if not
17 | seen.insert($0).inserted
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/String/String+Emoji.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Emoji.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/21.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreText
11 |
12 | public extension UnicodeScalar {
13 | var isEmoji: Bool {
14 | switch self.value {
15 | case 0x1F600...0x1F64F,
16 | 0x1F300...0x1F5FF,
17 | 0x1F680...0x1F6FF,
18 | 0x1F1E6...0x1F1FF,
19 | 0xE0020...0xE007F,
20 | 0xFE00...0xFE0F,
21 | 0x1F900...0x1F9FF,
22 | 0x1F018...0x1F0F5,
23 | 0x1F200...0x1F270,
24 | 65024...65039,
25 | 9100...9300,
26 | 8400...8447,
27 | 0x1F004,
28 | 0x1F18E,
29 | 0x1F191...0x1F19A,
30 | 0x1F5E8:
31 | return true
32 | case 0x2603, 0x265F, 0x267E, 0x2692,
33 | 0x26C4, 0x26C8, 0x26CE, 0x26CF,
34 | 0x26D1...0x26D3, 0x26E9, 0x26F0...0x26F9,
35 | 0x2705, 0x270A, 0x270B, 0x2728,
36 | 0x274E, 0x2753...0x2755, 0x274C,
37 | 0x2795...0x2797, 0x27B0, 0x27BF:
38 | return true
39 | default:
40 | return false
41 | }
42 | }
43 |
44 | var maybeEmoji: Bool {
45 | switch self.value {
46 | case 0x2A, 0x23, 0x30...0x39,
47 | 0xA9, 0xAE:
48 | return true
49 | case 0x2600...0x26FF, 0x2700...0x27BF,
50 | 0x1F100...0x1F1FF:
51 | return true
52 | case 0x203C, 0x2049, 0x2122, 0x2194...0x2199,
53 | 0x21A9, 0x21AA, 0x2139, 0x2328,
54 | 0x231A, 0x231B, 0x24C2, 0x25AA,
55 | 0x25AB, 0x25B6, 0x25FB...0x25FE, 0x25C0,
56 | 0x2934, 0x2935, 0x2B05...0x2B07, 0x2B1B...0x2B1E,
57 | 0x2B50, 0x2B55, 0x3030, 0x3297, 0x3299:
58 | return true
59 | default:
60 | return false
61 | }
62 | }
63 |
64 | static var ZeroWidthJoiner = UnicodeScalar(0x200D)!
65 | static var VariationSelector = UnicodeScalar(0xFE0F)!
66 | }
67 |
68 | public extension String {
69 |
70 | private var emojis: [String] {
71 | var emojis: [String] = []
72 | self.enumerateSubstrings(in: self.startIndex ..< self.endIndex, options: .byComposedCharacterSequences) { substring, _, _, _ in
73 | if let substring = substring {
74 | emojis.append(substring)
75 | }
76 | }
77 | return emojis
78 | }
79 |
80 | /// A Boolean value indicating whether a string is single emoji.
81 | var isSingleEmoji: Bool {
82 | return self.emojis.count == 1 && self.containsEmoji
83 | }
84 |
85 | /// A Boolean value indicating whether a string contains emoji.
86 | var containsEmoji: Bool {
87 | return self.unicodeScalars.contains { $0.isEmoji }
88 | }
89 |
90 | /// A Boolean value indicating whether a string contains only emoji.
91 | var containsOnlyEmoji: Bool {
92 | guard !self.isEmpty else {
93 | return false
94 | }
95 | var nextShouldBeVariationSelector = false
96 | for scalar in self.unicodeScalars {
97 | if nextShouldBeVariationSelector {
98 | return false
99 | }
100 | if !scalar.isEmoji && scalar.maybeEmoji {
101 | nextShouldBeVariationSelector = true
102 | } else if !scalar.isEmoji &&
103 | scalar != UnicodeScalar.ZeroWidthJoiner {
104 | return false
105 | }
106 | }
107 | return !nextShouldBeVariationSelector
108 | }
109 |
110 | /// Returns a new string made by removing emojis
111 | var trimmingEmojis: String {
112 | var string: String = ""
113 | self.enumerateSubstrings(in: self.startIndex ..< self.endIndex, options: .byComposedCharacterSequences) { substring, _, _, _ in
114 | if let substring = substring, !substring.containsEmoji {
115 | string.append(substring)
116 | }
117 | }
118 | return string
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/String/String+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Extensions.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/9/3.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension String {
12 |
13 | var asURL: URL? {
14 | URL(string: self)
15 | }
16 |
17 | var asDict: [String: Any]? {
18 | guard let data = self.data(using: .utf8) else { return nil }
19 | return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
20 | }
21 |
22 | var asArray: [Any]? {
23 | guard let data = self.data(using: .utf8) else { return nil }
24 | return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [Any]
25 | }
26 |
27 | var localized: String {
28 | NSLocalizedString(self, comment: "")
29 | }
30 |
31 | }
32 |
33 | public extension String {
34 | init?(json: Any) {
35 | guard let data = Data(json: json) else { return nil }
36 | self.init(decoding: data, as: UTF8.self)
37 | }
38 |
39 | func jsonToDictionary() -> [String: Any]? {
40 | self.data(using: .utf8)?.jsonToDictionary()
41 | }
42 |
43 | func jsonToArray() -> [Any]? {
44 | self.data(using: .utf8)?.jsonToArray()
45 | }
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/String/String+File.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+File.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/2/29.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 | var fileExtension: String? {
13 | guard let period = lastIndex(of: ".") else {
14 | return nil
15 | }
16 | let extensionStart = index(after: period)
17 | return String(self[extensionStart...])
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/String/String+MD5.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+MD5.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/8.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension String {
12 |
13 | /// The md5 of a string
14 | var md5: String {
15 | // It's safe to force-unwrap here since it use .utf8 encoding, it's not save using Unicode encoding
16 | let message = data(using: .utf8)!.bytes
17 |
18 | let MD5Calculator = MD5(message)
19 | let MD5Data = MD5Calculator.calculate()
20 |
21 | let MD5String = NSMutableString()
22 | for c in MD5Data {
23 | MD5String.appendFormat("%02x", c)
24 | }
25 | return MD5String as String
26 | }
27 |
28 | }
29 |
30 | protocol HashProtocol {
31 | var message: Array { get }
32 |
33 | /** Common part for hash calculation. Prepare header data. */
34 | func prepare(_ len: Int) -> Array
35 | }
36 |
37 | extension HashProtocol {
38 | func prepare(_ len: Int) -> Array {
39 | var tmpMessage = message
40 |
41 | // Step 1. Append Padding Bits
42 | tmpMessage.append(0x80) // append one bit (UInt8 with one bit) to message
43 |
44 | // append "0" bit until message length in bits ≡ 448 (mod 512)
45 | var msgLength = tmpMessage.count
46 | var counter = 0
47 |
48 | while msgLength % len != (len - 8) {
49 | counter += 1
50 | msgLength += 1
51 | }
52 |
53 | tmpMessage += Array(repeating: 0, count: counter)
54 | return tmpMessage
55 | }
56 | }
57 |
58 | func toUInt32Array(_ slice: ArraySlice) -> Array {
59 | var result = Array()
60 | result.reserveCapacity(16)
61 |
62 | for idx in stride(from: slice.startIndex, to: slice.endIndex, by: MemoryLayout.size) {
63 | let d0 = UInt32(slice[idx.advanced(by: 3)]) << 24
64 | let d1 = UInt32(slice[idx.advanced(by: 2)]) << 16
65 | let d2 = UInt32(slice[idx.advanced(by: 1)]) << 8
66 | let d3 = UInt32(slice[idx])
67 | let val: UInt32 = d0 | d1 | d2 | d3
68 |
69 | result.append(val)
70 | }
71 | return result
72 | }
73 |
74 | struct BytesIterator: IteratorProtocol {
75 | let chunkSize: Int
76 | let data: [UInt8]
77 |
78 | init(chunkSize: Int, data: [UInt8]) {
79 | self.chunkSize = chunkSize
80 | self.data = data
81 | }
82 |
83 | var offset = 0
84 |
85 | mutating func next() -> ArraySlice? {
86 | let end = min(chunkSize, data.count - offset)
87 | let result = data[offset ..< offset + end]
88 | offset += result.count
89 | return result.count > 0 ? result : nil
90 | }
91 | }
92 |
93 | struct BytesSequence: Sequence {
94 | let chunkSize: Int
95 | let data: [UInt8]
96 |
97 | func makeIterator() -> BytesIterator {
98 | return BytesIterator(chunkSize: chunkSize, data: data)
99 | }
100 | }
101 |
102 | func rotateLeft(_ value: UInt32, bits: UInt32) -> UInt32 {
103 | return ((value << bits) & 0xFFFFFFFF) | (value >> (32 - bits))
104 | }
105 |
106 | class MD5: HashProtocol {
107 | static let size = 16 // 128 / 8
108 | let message: [UInt8]
109 |
110 | init(_ message: [UInt8]) {
111 | self.message = message
112 | }
113 |
114 | /** specifies the per-round shift amounts */
115 | private let shifts: [UInt32] = [
116 | 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
117 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
118 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
119 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
120 | ]
121 |
122 | /** binary integer part of the sines of integers (Radians) */
123 | private let sines: [UInt32] = [
124 | 0xD76AA478, 0xE8C7B756, 0x242070DB, 0xC1BDCEEE,
125 | 0xF57C0FAF, 0x4787C62A, 0xA8304613, 0xFD469501,
126 | 0x698098D8, 0x8B44F7AF, 0xFFFF5BB1, 0x895CD7BE,
127 | 0x6B901122, 0xFD987193, 0xA679438E, 0x49B40821,
128 | 0xF61E2562, 0xC040B340, 0x265E5A51, 0xE9B6C7AA,
129 | 0xD62F105D, 0x02441453, 0xD8A1E681, 0xE7D3FBC8,
130 | 0x21E1CDE6, 0xC33707D6, 0xF4D50D87, 0x455A14ED,
131 | 0xA9E3E905, 0xFCEFA3F8, 0x676F02D9, 0x8D2A4C8A,
132 | 0xFFFA3942, 0x8771F681, 0x6D9D6122, 0xFDE5380C,
133 | 0xA4BEEA44, 0x4BDECFA9, 0xF6BB4B60, 0xBEBFBC70,
134 | 0x289B7EC6, 0xEAA127FA, 0xD4EF3085, 0x4881D05,
135 | 0xD9D4D039, 0xE6DB99E5, 0x1FA27CF8, 0xC4AC5665,
136 | 0xF4292244, 0x432AFF97, 0xAB9423A7, 0xFC93A039,
137 | 0x655B59C3, 0x8F0CCC92, 0xFFEFF47D, 0x85845DD1,
138 | 0x6FA87E4F, 0xFE2CE6E0, 0xA3014314, 0x4E0811A1,
139 | 0xF7537E82, 0xBD3AF235, 0x2AD7D2BB, 0xEB86D391,
140 | ]
141 |
142 | private let hashes: [UInt32] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476]
143 |
144 | func calculate() -> [UInt8] {
145 | var tmpMessage = prepare(64)
146 | tmpMessage.reserveCapacity(tmpMessage.count + 4)
147 |
148 | // hash values
149 | var hh = hashes
150 |
151 | // Step 2. Append Length a 64-bit representation of lengthInBits
152 | let lengthInBits = (message.count * 8)
153 | let lengthBytes = lengthInBits.bytes(64 / 8)
154 | tmpMessage += lengthBytes.reversed()
155 |
156 | // Process the message in successive 512-bit chunks:
157 | let chunkSizeBytes = 512 / 8 // 64
158 |
159 | for chunk in BytesSequence(chunkSize: chunkSizeBytes, data: tmpMessage) {
160 | // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15
161 | let M = toUInt32Array(chunk)
162 |
163 | // Initialize hash value for this chunk:
164 | var A: UInt32 = hh[0]
165 | var B: UInt32 = hh[1]
166 | var C: UInt32 = hh[2]
167 | var D: UInt32 = hh[3]
168 |
169 | var dTemp: UInt32 = 0
170 |
171 | // Main loop
172 | for j in 0 ..< sines.count {
173 | var g = 0
174 | var F: UInt32 = 0
175 |
176 | if (0...15).contains(j) {
177 | F = (B & C) | ((~B) & D)
178 | g = j
179 | } else if (16...31).contains(j) {
180 | F = (D & B) | (~D & C)
181 | g = (5 * j + 1) % 16
182 | } else if (32...47).contains(j) {
183 | F = B ^ C ^ D
184 | g = (3 * j + 5) % 16
185 | } else if (48...63).contains(j) {
186 | F = C ^ (B | (~D))
187 | g = (7 * j) % 16
188 | }
189 |
190 | dTemp = D
191 | D = C
192 | C = B
193 | B = B &+ rotateLeft(A &+ F &+ sines[j] &+ M[g], bits: shifts[j])
194 | A = dTemp
195 | }
196 |
197 | hh[0] = hh[0] &+ A
198 | hh[1] = hh[1] &+ B
199 | hh[2] = hh[2] &+ C
200 | hh[3] = hh[3] &+ D
201 | }
202 |
203 | var result = [UInt8]()
204 | result.reserveCapacity(hh.count / 4)
205 |
206 | hh.forEach {
207 | let itemLE = $0.littleEndian
208 | result += [UInt8(itemLE & 0xFF), UInt8((itemLE >> 8) & 0xFF), UInt8((itemLE >> 16) & 0xFF), UInt8((itemLE >> 24) & 0xFF)]
209 | }
210 | return result
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/String/String+Modify.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Modify.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/15.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension String {
12 |
13 | /// characteristics of vowels
14 | static var vowels: [String] {
15 | ["a", "e", "i", "o", "u"]
16 | }
17 |
18 | /// characteristics of consonants
19 | static var consonants: [String] {
20 | ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "z"]
21 | }
22 |
23 | /// exceptions plural format
24 | private var pluralExceptions: [String: String] {
25 | return [
26 | "roof": "roofs",
27 | "belief": "beliefs",
28 | "chef": "chefs",
29 | "chief": "chiefs",
30 | "photo": "photos",
31 | "piano": "pianos",
32 | "halo": "halos",
33 | ]
34 | }
35 |
36 | /// return normal form or plural form of a noun
37 | /// - Parameter count: count of object
38 | func pluralize(_ count: Int, countable: Bool = true) -> String {
39 | if countable == false { return self }
40 | if count == 1 || count == 0 { return self }
41 | if self.count == 1 || self.count == 0 { return self }
42 |
43 | if pluralExceptions.keys.contains(where: { $0 == self.lowercased() }) {
44 | if self == self.uppercased() {
45 | return pluralExceptions[self.lowercased()]!.uppercased()
46 | }
47 | return pluralExceptions[self.lowercased()]!
48 | }
49 |
50 | var prefix = "", suffix = ""
51 |
52 | // noun ends with f/F
53 | let fRegex = #"^[a-z|A-Z|0-9]+[^Ss](f{1}|F{1})$"#
54 | // noun ends with fe/FE
55 | let feRegex = #"^[a-z|A-Z|0-9]+(fe{1}|FE{1})$"#
56 | // noun ends with s/S
57 | let sRegex = #"^[a-z|A-Z|0-9]+[^Ss](s{1}|S{1})$"#
58 | // noun ends with o/O
59 | let oRegex = #"^[a-z|A-Z|0-9]+(o{1}|O{1})$"#
60 | // noun ends with y/Y
61 | let yRegex = #"^[a-z|A-Z|0-9]+[aeiouAEIOU](y{1}|Y{1})$"#
62 | // noun ends with consonant and y/Y
63 | let consonantAndYRegex = #"^[a-z|A-Z|0-9]+[bddfghjklmnpqrstvxzBCDFGHJKLMNPQRSTVXZ](y{1}|Y{1})$"#
64 | // noun ends with sh/SH
65 | let shRegex = #"^[a-z|A-Z|0-9]+(sh{1}|SH{1})$"#
66 | // noun ends with ss/SS
67 | let ssRegex = #"^[a-z|A-Z|0-9]+(ss{1}|SS{1})$"#
68 | // noun ends with vowels and s/S
69 | let vowelsAndSRegex = #"^[a-z|A-Z|0-9]+[aeiouAEIOU](s{1}|S{1})$"#
70 | // noun ends with vowels and z/Z
71 | let vowelsAndZRegex = #"^[a-z|A-Z|0-9]+[aeiouAEIOU](z{1}|Z{1})$"#
72 | // noun ends with ch/CH
73 | let chRegex = #"^[a-z|A-Z|0-9]+(ch{1}|CH{1})$"#
74 | // noun ends with x/X
75 | let xRegex = #"^[a-z|A-Z|0-9]+[aeiouAEIOU](x{1}|X{1})$"#
76 | // noun ends with tz/TZ
77 | let tzRegex = #"^[a-z|A-Z|0-9]+[^Oo](tz{1}|TZ{1})$"#
78 |
79 | let lastCharacter = String(last!)
80 |
81 | if matches(pattern: vowelsAndZRegex) {
82 | prefix = self[0 ..< self.count]
83 | suffix = "zes"
84 | } else if matches(pattern: vowelsAndSRegex) {
85 | prefix = self[0 ..< self.count]
86 | suffix = "ses"
87 | } else if matches(pattern: sRegex) || matches(pattern: oRegex) || matches(pattern: xRegex) {
88 | prefix = self[0 ..< self.count]
89 | suffix = "es"
90 | } else if matches(pattern: shRegex) ||
91 | matches(pattern: ssRegex) ||
92 | matches(pattern: chRegex) ||
93 | matches(pattern: tzRegex) {
94 | prefix = self[0 ..< self.count]
95 | suffix = "es"
96 | } else if matches(pattern: yRegex) {
97 | prefix = self[0 ..< self.count]
98 | suffix = "s"
99 | } else if matches(pattern: consonantAndYRegex) {
100 | prefix = self[0 ..< self.count - 1]
101 | suffix = "ies"
102 | } else if matches(pattern: fRegex) {
103 | prefix = self[0 ..< self.count - 1]
104 | suffix = "ves"
105 | } else if matches(pattern: feRegex) {
106 | prefix = self[0 ..< self.count - 2]
107 | suffix = "ves"
108 | } else {
109 | prefix = self[0 ..< self.count]
110 | suffix = "s"
111 | }
112 |
113 | return prefix + (lastCharacter != lastCharacter.uppercased() ? suffix : suffix.uppercased())
114 | }
115 |
116 | /// plural of the string
117 | var plural: String {
118 | pluralize(2)
119 | }
120 |
121 | ///
122 | /// Returns a new string made by replacing values in dictionary
123 | ///
124 | /// e.g.
125 | ///
126 | /// let str = "12345"
127 | ///
128 | /// let dict = ["1": "一", "3": "三", "5": "五", "8": "八"]
129 | ///
130 | /// str.replacingCharacters(in: dict) // 一2三4五
131 | ///
132 | func replacingCharacters(in dictionary: [String: String]) -> String {
133 | map { (character: Character) -> String in
134 | dictionary[String(character)] ?? String(character)
135 | }.joined()
136 | }
137 |
138 | func trimmingTrailingSpaces() -> String {
139 | var t = self
140 | while t.hasSuffix(" ") {
141 | t = "" + t.dropLast()
142 | }
143 | return t
144 | }
145 |
146 | }
147 |
148 | public extension String {
149 | func capitalizingFirstLetter() -> String {
150 | prefix(1).uppercased() + dropFirst()
151 | }
152 |
153 | mutating func capitalizeFirstLetter() {
154 | self = self.capitalizingFirstLetter()
155 | }
156 | }
157 |
158 | public extension String {
159 | var trimmed: String {
160 | self.trimmingCharacters(in: .whitespacesAndNewlines)
161 | }
162 |
163 | mutating func trim() {
164 | self = self.trimmed
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/Sources/Extensions/Foundation/String/String+Subscript.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Subscript.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/3.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // slicing
12 | public extension String {
13 | /// Substring from closed range
14 | ///
15 | /// For example:
16 | ///
17 | /// let str = "12345678"
18 | /// print(str[2...5])
19 | /// // Prints "3456"
20 | ///
21 | /// - Parameter bounds: e.g. ( x...x )
22 | subscript(bounds: ClosedRange) -> String {
23 | if bounds.lowerBound < 0 ||
24 | bounds.upperBound > count - 1 {
25 | return ""
26 | }
27 | return String(self[self.index(startIndex, offsetBy: bounds.lowerBound) ... self.index(self.startIndex, offsetBy: bounds.upperBound)])
28 | }
29 |
30 | /// Substring from half-open range
31 | ///
32 | /// For example:
33 | ///
34 | /// let str = "12345678"
35 | /// print(str[2..<5])
36 | /// // Prints "345"
37 | ///
38 | /// - Parameter bounds: ( x..) -> String {
40 | guard let lowerIndex = index(startIndex,
41 | offsetBy: max(0, range.lowerBound),
42 | limitedBy: endIndex),
43 | let upperIndex = index(lowerIndex,
44 | offsetBy: range.upperBound - max(0, range.lowerBound),
45 | limitedBy: endIndex)
46 | else { return "" }
47 | return String(self[lowerIndex ..< upperIndex])
48 |
49 | }
50 |
51 | /// Substring from a partial interval extending upward from a lower bound range
52 | ///
53 | /// For example:
54 | ///
55 | /// let str = "12345678"
56 | /// print(str[2...])
57 | /// // Prints "345678"
58 | ///
59 | /// - Parameter bounds: ( x... )
60 | subscript(bounds: PartialRangeFrom) -> String {
61 | if bounds.lowerBound < 0 ||
62 | bounds.lowerBound > count - 1 {
63 | return ""
64 | }
65 | return String(self[self.index(startIndex, offsetBy: bounds.lowerBound)...])
66 | }
67 |
68 | /// Substring from a partial interval up to range
69 | ///
70 | /// For example:
71 | ///
72 | /// let str = "12345678"
73 | /// print(str[...5])
74 | /// // Prints "123456"
75 | ///
76 | /// - Parameter bounds: ( ...x )
77 | subscript(bounds: PartialRangeThrough) -> String {
78 | if bounds.upperBound < 0 ||
79 | bounds.upperBound > count - 1 {
80 | return ""
81 | }
82 | return String(self[...self.index(startIndex, offsetBy: bounds.upperBound)])
83 | }
84 |
85 | /// Substring from partial half-open interval up to, but not including, an upper bound range
86 | ///
87 | /// For example:
88 | ///
89 | /// let str = "12345678"
90 | /// print(str[..<5])
91 | /// // Prints "12345"
92 | ///
93 | /// - Parameter bounds: ( ..) -> String {
95 | if bounds.upperBound < 0 ||
96 | bounds.upperBound > count {
97 | return ""
98 | }
99 | return String(self[.. Bool {
32 | let detector = try! NSDataDetector(types: types.rawValue)
33 | let matches = detector.matches(in: self, options: .reportCompletion, range: NSMakeRange(0, count))
34 | return matches.count > 0
35 | }
36 |
37 | /// 严格意义上包含电话号码
38 | var strictContainsPhoneNumber: Bool {
39 | return westernArabicNumeralsOnly.containsPhoneNumber
40 | }
41 |
42 | /// A Boolean value indicating whether a string is valid email address.
43 | var isValidEmail: Bool {
44 | let emailRegEx = #"^(?:[\p{L}0-9!#$%\&'*+/=?\^_`{|}~-]+(?:\.[\p{L}0-9!#$%\&'*+/=?\^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[\p{L}0-9](?:[a-z0-9-]*[\p{L}0-9])?\.)+[\p{L}0-9](?:[\p{L}0-9-]*[\p{L}0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[\p{L}0-9-]*[\p{L}0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$"#
45 |
46 | return matches(pattern: emailRegEx)
47 | }
48 |
49 | /// A Boolean value indicating whether a string is valid phone number.
50 | var isValidPhoneNumber: Bool {
51 | let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.phoneNumber.rawValue)
52 | let matches = detector.matches(in: self, options: [], range: NSMakeRange(0, count))
53 | if let res = matches.first {
54 | return res.resultType == .phoneNumber && res.range.location == 0 && res.range.length == count
55 | }
56 | return false
57 | }
58 |
59 | /// A Boolean value indicating whether a string is valid JSON String.
60 | var isValidJSON: Bool {
61 | if let jsonDataToVerify = self.data(using: String.Encoding.utf8),
62 | let _ = try? JSONSerialization.jsonObject(with: jsonDataToVerify) {
63 | return true
64 | }
65 | return false
66 | }
67 |
68 | /// A Boolean value indicating whether a string is a UUID String.
69 | var isUUID: Bool {
70 | UUID(uuidString: self) != nil
71 | }
72 |
73 | /// A Boolean value indicating whether a string is a Int String.
74 | var isInt: Bool {
75 | Int(self) != nil
76 | }
77 |
78 | /// A Boolean value indicating whether a string is a Double String.
79 | var isDouble: Bool {
80 | Double(self) != nil
81 | }
82 |
83 | /// A Boolean value indicating whether a string is a Float String.
84 | var isFloat: Bool {
85 | Float(self) != nil
86 | }
87 |
88 | /// A Boolean value indicating whether a string is valid Chinese ID card number String.
89 | var isValidChineseIDCardNo: Bool {
90 | // 判断是否为空
91 | if count <= 0 { return false }
92 |
93 | // 判断是否是18位,末尾是否是x
94 | let regex = "^(\\d{14}|\\d{17})(\\d|[xX])$"
95 | if !matches(pattern: regex) { return false }
96 |
97 | // 判断生日是否合法
98 | let from = index(startIndex, offsetBy: 6)
99 | let to = index(from, offsetBy: 7)
100 | let dateStr = String(self[from ... to])
101 | let formatter = DateFormatter()
102 | formatter.dateFormat = "yyyyMMdd"
103 | if formatter.date(from: dateStr) == nil { return false }
104 |
105 | // 判断校验位
106 | if count == 18 {
107 | // 将前17位加权因子保存在数组里
108 | let idCardWi = ["7", "9", "10", "5", "8", "4", "2", "1", "6", "3", "7", "9", "10", "5", "8", "4", "2"]
109 | // 这是除以11后,可能产生的11位余数、验证码,也保存成数组
110 | let idCardY = ["1", "0", "10", "9", "8", "7", "6", "5", "4", "3", "2"]
111 | var idCardWiSum: Int = 0 // 用来保存前17位各自乘以加权因子后的总和
112 | for i in 0 ..< 17 {
113 | let startIndex = index(self.startIndex, offsetBy: i)
114 | let toIndex = index(startIndex, offsetBy: 0)
115 | idCardWiSum += Int(String(self[startIndex ... toIndex]))! * Int(idCardWi[i])!
116 | }
117 | // 计算出校验码所在数组的位置
118 | let idCardMod: Int = idCardWiSum % 11
119 | // 得到最后一位身份证号码
120 | let idCardLast = String(suffix(1))
121 | // 如果等于2,则说明校验码是10,身份证号码最后一位应该是X
122 | if idCardMod == 2 {
123 | return idCardLast == "X" || idCardLast == "x"
124 | }
125 | // 用计算出的验证码与最后一位身份证号码匹配,如果一致,说明通过,否则是无效的身份证号码
126 | return Int(idCardLast)! == Int(idCardY[idCardMod])!
127 | }
128 | return false
129 | }
130 |
131 | /// A Boolean value indicating whether a string is valid Bank Card Number String.
132 | var isValidBankCardNumber: Bool {
133 | self.luhnCheck()
134 | }
135 |
136 | /// Luhn Algorithm in Swift.
137 | func luhnCheck() -> Bool {
138 | var sum = 0
139 | let num = self.replacingOccurrences(of: " ", with: "")
140 | if num.count == 0 { return false }
141 | let digitStrings = num.reversed().map { String($0) }
142 |
143 | for tuple in digitStrings.enumerated() {
144 | if let digit = Int(tuple.element) {
145 | let odd = tuple.offset % 2 == 1
146 |
147 | switch (odd, digit) {
148 | case (true, 9):
149 | sum += 9
150 | case (true, 0...8):
151 | sum += (digit * 2) % 9
152 | default:
153 | sum += digit
154 | }
155 | } else {
156 | return false
157 | }
158 | }
159 | return sum % 10 == 0
160 | }
161 |
162 | /// check whether a string maches pattern
163 | ///
164 | /// - Parameter pattern: 正则表达式字符串
165 | /// - Returns: 是否满足正则表达式
166 | func matches(pattern: String) -> Bool {
167 | let predicate = NSPredicate(format: "SELF MATCHES %@", pattern)
168 | return predicate.evaluate(with: self)
169 | }
170 |
171 | var westernArabicNumeralsOnly: String {
172 | let pattern = UnicodeScalar("0") ... "9"
173 | return String(unicodeScalars
174 | .compactMap { pattern ~= $0 ? Character($0) : nil })
175 | }
176 |
177 | var containsOnlyDigits: Bool {
178 | let notDigits = NSCharacterSet.decimalDigits.inverted
179 | return rangeOfCharacter(from: notDigits, options: String.CompareOptions.literal, range: nil) == nil
180 | }
181 |
182 | var containsOnlyLetters: Bool {
183 | let notLetters = NSCharacterSet.letters.inverted
184 | return rangeOfCharacter(from: notLetters, options: String.CompareOptions.literal, range: nil) == nil
185 | }
186 |
187 | var isAlphanumeric: Bool {
188 | !isEmpty && range(of: "[^a-zA-Z0-9]", options: .regularExpression) == nil
189 | }
190 |
191 | }
192 |
193 | extension String {
194 | var condensedWhitespace: String {
195 | let components = self.components(separatedBy: .whitespacesAndNewlines)
196 | return components.filter { !$0.isEmpty }.joined(separator: " ")
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/Sources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Sources/SwiftWings.h:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftWings.h
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/1/3.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for SwiftWings.
12 | FOUNDATION_EXPORT double SwiftWingsVersionNumber;
13 |
14 | //! Project version string for SwiftWings.
15 | FOUNDATION_EXPORT const unsigned char SwiftWingsVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/SwiftWings.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'SwiftWings'
3 | s.version = '1.9.0'
4 | s.summary = 'A collection of Swift extensions for all platforms'
5 | s.homepage = 'https://github.com/leacode/SwiftWings'
6 | s.license = { :type => 'MIT', :file => 'LICENSE' }
7 | s.author = { 'leacode' => 'lichunyu@vip.qq.com' }
8 | s.source = { :git => 'https://github.com/leacode/SwiftWings.git', :tag => s.version.to_s }
9 |
10 | s.ios.deployment_target = '12.0'
11 | s.osx.deployment_target = '10.11'
12 | s.tvos.deployment_target = '9.0'
13 | s.watchos.deployment_target = '2.0'
14 |
15 | s.swift_versions = ['5.0', '5.1', '5.2', '5.3']
16 |
17 | s.source_files = 'Sources/**/*.swift'
18 |
19 | s.frameworks = 'Foundation'
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/SwiftWings.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftWings.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftWings.xcodeproj/xcshareddata/xcschemes/SwiftWings macOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
57 |
58 |
59 |
60 |
62 |
63 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/SwiftWings.xcodeproj/xcshareddata/xcschemes/SwiftWings tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
57 |
58 |
59 |
60 |
62 |
63 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/SwiftWings.xcodeproj/xcshareddata/xcschemes/SwiftWings watchOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
57 |
58 |
59 |
60 |
62 |
63 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/SwiftWings.xcodeproj/xcshareddata/xcschemes/SwiftWings.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
35 |
41 |
42 |
43 |
44 |
45 |
55 |
56 |
62 |
63 |
69 |
70 |
71 |
72 |
74 |
75 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/Tests/Data/amrFile.amr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/amrFile.amr
--------------------------------------------------------------------------------
/Tests/Data/bmpFile.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/bmpFile.bmp
--------------------------------------------------------------------------------
/Tests/Data/bz2File.bz2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/bz2File.bz2
--------------------------------------------------------------------------------
/Tests/Data/flacFile.flac:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/flacFile.flac
--------------------------------------------------------------------------------
/Tests/Data/gifFile.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/gifFile.gif
--------------------------------------------------------------------------------
/Tests/Data/gzFile.gz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/gzFile.gz
--------------------------------------------------------------------------------
/Tests/Data/icoFile.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/icoFile.ico
--------------------------------------------------------------------------------
/Tests/Data/jpegFile.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/jpegFile.jpeg
--------------------------------------------------------------------------------
/Tests/Data/jpgFile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/jpgFile.jpg
--------------------------------------------------------------------------------
/Tests/Data/m4aFile.m4a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/m4aFile.m4a
--------------------------------------------------------------------------------
/Tests/Data/midFile.mid:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/midFile.mid
--------------------------------------------------------------------------------
/Tests/Data/mp3File.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/mp3File.mp3
--------------------------------------------------------------------------------
/Tests/Data/oggFile.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/oggFile.ogg
--------------------------------------------------------------------------------
/Tests/Data/opusFile.opus:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/opusFile.opus
--------------------------------------------------------------------------------
/Tests/Data/pngFile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/pngFile.png
--------------------------------------------------------------------------------
/Tests/Data/psdFile.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/psdFile.psd
--------------------------------------------------------------------------------
/Tests/Data/rarFile.rar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/rarFile.rar
--------------------------------------------------------------------------------
/Tests/Data/sqliteFile.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/sqliteFile.sqlite
--------------------------------------------------------------------------------
/Tests/Data/tarFile.tar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/tarFile.tar
--------------------------------------------------------------------------------
/Tests/Data/tiffFile.tiff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/tiffFile.tiff
--------------------------------------------------------------------------------
/Tests/Data/wavFile.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/wavFile.wav
--------------------------------------------------------------------------------
/Tests/Data/webpFile.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/leacode/SwiftWings/d14ffcd08fed17e79e3296def4eb4773b5a7d457/Tests/Data/webpFile.webp
--------------------------------------------------------------------------------
/Tests/Extensions/Array/Array+DataTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+DataTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/20.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class Array_DataTests: XCTestCase {
13 |
14 | func test_Array_data_returnsExpected() {
15 | let uint8Array: [UInt8] = [UInt8(5), UInt8(7), UInt8(2), UInt8(4)]
16 | XCTAssertEqual(uint8Array.data.bytes.count, 4)
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/Tests/Extensions/Array/Array+SubscriptTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+SubscriptTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/20.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class Array_SubscriptTests: XCTestCase {
13 |
14 | func test_Array_guardsubscript_returnsExpected() {
15 | let array = [1, 2, 3, 4, 5]
16 | XCTAssertEqual(array[guard: 6] ?? 0, 0)
17 | XCTAssertEqual(array[guard: 1] ?? 0, 2)
18 | }
19 |
20 | func test_Array_reduct_returnsExpected() {
21 | XCTAssertEqual([1, 2, 3, 4].reduct(+), 10)
22 | XCTAssertEqual([9, 3, 2, 1].reduct(-), 3)
23 | XCTAssertEqual([1, 2, 3, 4].reduct(*), 24)
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/Tests/Extensions/Bundle/Bundle+ExtensionsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Bundle+ExtensionsTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/9/25.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class Bundle_ExtensionsTests: XCTestCase {
13 |
14 | private let bundle = Bundle(for: SomeClass.self)
15 |
16 | func test_appVersions_returnsExpected() {
17 | XCTAssertNotNil(bundle.appVersion)
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/Tests/Extensions/Codable/Codable+JSONTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Codable+JSONTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/20.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class Codable_JSONTests: XCTestCase {
12 |
13 | var model: CodableModel!
14 | var invalidModel: CodableModel!
15 |
16 | override func setUp() {
17 | super.setUp()
18 |
19 | model = CodableModel(name: "name", number: 1, date: Date(), price: 5.20)
20 | invalidModel = CodableModel(name: "name", number: 1, date: Date(), price: Double.infinity)
21 | }
22 |
23 | override func tearDown() {
24 | model = nil
25 | invalidModel = nil
26 | super.tearDown()
27 | }
28 |
29 | func test_Codable_jsonString_returnsExpected() {
30 | let jsonStr = model.jsonString
31 | XCTAssertNotNil(jsonStr)
32 |
33 | XCTAssertNil(invalidModel.jsonString)
34 | }
35 |
36 | func test_Codable_dictionary_returnsExpected() throws {
37 | let dict = try? model.dictionary()
38 | XCTAssertNotNil(dict)
39 | XCTAssertTrue(dict?.count ?? 0 > 0)
40 |
41 | let invalidResult = try? invalidModel.dictionary()
42 | XCTAssertNil(invalidResult)
43 | XCTAssertThrowsError(try invalidModel.dictionary())
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/Tests/Extensions/Data/AudioMimeTypeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AudioMimeTypeTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/8.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class AudioMimeTypeTests: XCTestCase {
13 |
14 | func test_AudioMimeType_ExpressibleByStringLiteral_init() {
15 | let amrAudioMimeType = AudioMimeType(stringLiteral: "audio/amr")
16 | XCTAssertEqual(amrAudioMimeType, AudioMimeType.amr)
17 |
18 | let unknownType = AudioMimeType(stringLiteral: "xxx")
19 | XCTAssertEqual(unknownType, AudioMimeType.unknown)
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/Extensions/Data/Data+MimeTypeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data+MimeTypeTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/8.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class Data_MimeTypeTests: XCTestCase {
13 |
14 | // given
15 |
16 | func givenFileData(extensionName: String) throws -> Data {
17 | return try Data.fromFile(fileName: "\(extensionName)File", extensionName: extensionName)
18 | }
19 |
20 | // tests
21 |
22 | // MARK: - Test Audio Types
23 |
24 | func test_Data_isAmr_returnsExpected() throws {
25 | let fileData = try givenFileData(extensionName: "amr")
26 | XCTAssertTrue(fileData.isAmr)
27 |
28 | let wavFileData = try givenFileData(extensionName: "wav")
29 | XCTAssertFalse(wavFileData.isAmr)
30 | }
31 |
32 | func test_Data_isMp3_returnsExpected() throws {
33 | let fileData = try givenFileData(extensionName: "mp3")
34 | XCTAssertTrue(fileData.isMp3)
35 |
36 | let wavFileData = try givenFileData(extensionName: "wav")
37 | XCTAssertFalse(wavFileData.isMp3)
38 | }
39 |
40 | func test_Data_isFlac_returnsExpected() throws {
41 | let fileData = try givenFileData(extensionName: "flac")
42 | XCTAssertTrue(fileData.isFlac)
43 |
44 | let wavFileData = try givenFileData(extensionName: "wav")
45 | XCTAssertFalse(wavFileData.isFlac)
46 | }
47 |
48 | func test_Data_isWav_returnsExpected() throws {
49 | let fileData = try givenFileData(extensionName: "wav")
50 | XCTAssertTrue(fileData.isWav)
51 |
52 | let mp3Data = try givenFileData(extensionName: "mp3")
53 | XCTAssertFalse(mp3Data.isWav)
54 | }
55 |
56 | func test_Data_isOgg_returnsExpected() throws {
57 | let fileData = try givenFileData(extensionName: "ogg")
58 | XCTAssertTrue(fileData.isOgg)
59 |
60 | let mp3Data = try givenFileData(extensionName: "mp3")
61 | XCTAssertFalse(mp3Data.isOgg)
62 | }
63 |
64 | func test_Data_isOpus_returnsExpected() throws {
65 | let fileData = try givenFileData(extensionName: "opus")
66 | XCTAssertTrue(fileData.isOpus)
67 |
68 | let mp3Data = try givenFileData(extensionName: "mp3")
69 | XCTAssertFalse(mp3Data.isOpus)
70 | }
71 |
72 | func test_Data_isM4a_returnsExpected() throws {
73 | let fileData = try givenFileData(extensionName: "m4a")
74 | XCTAssertTrue(fileData.isM4a)
75 |
76 | let mp3Data = try givenFileData(extensionName: "mp3")
77 | XCTAssertFalse(mp3Data.isM4a)
78 | }
79 |
80 | func test_Data_isMid_returnsExpected() throws {
81 | let fileData = try givenFileData(extensionName: "mid")
82 | XCTAssertTrue(fileData.isMid)
83 |
84 | let mp3Data = try givenFileData(extensionName: "mp3")
85 | XCTAssertFalse(mp3Data.isMid)
86 | }
87 |
88 | // MARK: - Test Image Types
89 |
90 | func test_Data_isPng_returnsExpected() throws {
91 | let fileData = try givenFileData(extensionName: "png")
92 | XCTAssertTrue(fileData.isPng)
93 |
94 | let wavFileData = try givenFileData(extensionName: "wav")
95 | XCTAssertFalse(wavFileData.isPng)
96 | }
97 |
98 | func test_Data_isJPG_returnsExpected() throws {
99 | let fileData = try givenFileData(extensionName: "jpg")
100 | XCTAssertTrue(fileData.isJpg)
101 |
102 | let wavFileData = try givenFileData(extensionName: "png")
103 | XCTAssertFalse(wavFileData.isJpg)
104 | }
105 |
106 | func test_Data_isJPEG_returnsExpected() throws {
107 | let fileData = try givenFileData(extensionName: "jpeg")
108 | XCTAssertTrue(fileData.isJPEG)
109 |
110 | let wavFileData = try givenFileData(extensionName: "png")
111 | XCTAssertFalse(wavFileData.isJPEG)
112 | }
113 |
114 | func test_Data_isGif_returnsExpected() throws {
115 | let fileData = try givenFileData(extensionName: "gif")
116 | XCTAssertTrue(fileData.isGif)
117 |
118 | let wavFileData = try givenFileData(extensionName: "png")
119 | XCTAssertFalse(wavFileData.isGif)
120 | }
121 |
122 | func test_Data_isTiff_returnsExpected() throws {
123 | let fileData = try givenFileData(extensionName: "tiff")
124 | XCTAssertTrue(fileData.isTiff)
125 |
126 | let wavFileData = try givenFileData(extensionName: "png")
127 | XCTAssertFalse(wavFileData.isTiff)
128 | }
129 |
130 | func test_Data_isBmp_returnsExpected() throws {
131 | let fileData = try givenFileData(extensionName: "bmp")
132 | XCTAssertTrue(fileData.isBmp)
133 |
134 | let wavFileData = try givenFileData(extensionName: "png")
135 | XCTAssertFalse(wavFileData.isBmp)
136 | }
137 |
138 | func test_Data_isWebp_returnsExpected() throws {
139 | let fileData = try givenFileData(extensionName: "webp")
140 | XCTAssertTrue(fileData.isWebp)
141 |
142 | let wavFileData = try givenFileData(extensionName: "png")
143 | XCTAssertFalse(wavFileData.isWebp)
144 | }
145 |
146 | func test_Data_isPsd_returnsExpected() throws {
147 | let fileData = try givenFileData(extensionName: "psd")
148 | XCTAssertTrue(fileData.isPsd)
149 |
150 | let wavFileData = try givenFileData(extensionName: "png")
151 | XCTAssertFalse(wavFileData.isPsd)
152 | }
153 |
154 | func test_Data_isIco_returnsExpected() throws {
155 | let fileData = try givenFileData(extensionName: "ico")
156 | XCTAssertTrue(fileData.isIco)
157 |
158 | let wavFileData = try givenFileData(extensionName: "png")
159 | XCTAssertFalse(wavFileData.isIco)
160 | }
161 |
162 | // MARK: - Test Application Types
163 |
164 | func test_Data_isSqlite_returnsExpected() throws {
165 | let fileData = try givenFileData(extensionName: "sqlite")
166 | XCTAssertTrue(fileData.isSqlite)
167 |
168 | let wavFileData = try givenFileData(extensionName: "wav")
169 | XCTAssertFalse(wavFileData.isSqlite)
170 | }
171 |
172 | func test_Data_isRar_returnsExpected() throws {
173 | let fileData = try givenFileData(extensionName: "rar")
174 | XCTAssertTrue(fileData.isRar)
175 |
176 | let wavFileData = try givenFileData(extensionName: "wav")
177 | XCTAssertFalse(wavFileData.isRar)
178 | }
179 |
180 | func test_Data_isTar_returnsExpected() throws {
181 | let fileData = try givenFileData(extensionName: "tar")
182 | XCTAssertTrue(fileData.isTar)
183 |
184 | let wavFileData = try givenFileData(extensionName: "wav")
185 | XCTAssertFalse(wavFileData.isTar)
186 | }
187 |
188 | func test_Data_isGzip_returnsExpected() throws {
189 | let fileData = try givenFileData(extensionName: "gz")
190 | XCTAssertTrue(fileData.isGzip)
191 |
192 | let wavFileData = try givenFileData(extensionName: "wav")
193 | XCTAssertFalse(wavFileData.isGzip)
194 | }
195 |
196 | func test_Data_isBz2_returnsExpected() throws {
197 | let fileData = try givenFileData(extensionName: "bz2")
198 | XCTAssertTrue(fileData.isBz2)
199 |
200 | let wavFileData = try givenFileData(extensionName: "wav")
201 | XCTAssertFalse(wavFileData.isBz2)
202 | }
203 |
204 | }
205 |
--------------------------------------------------------------------------------
/Tests/Extensions/Data/Data+PrettyJSONTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data+PrettyJSONTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/20.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class Data_PrettyJSONTests: XCTestCase {
12 |
13 | var model: CodableModel!
14 | var invalidModel: CodableModel!
15 |
16 | override func setUp() {
17 | super.setUp()
18 | model = CodableModel(name: "name", number: 1, date: Date(), price: 5.20)
19 | invalidModel = CodableModel(name: "name", number: 1, date: Date(), price: Double.infinity)
20 | }
21 |
22 | override func tearDown() {
23 | model = nil
24 | invalidModel = nil
25 | super.tearDown()
26 | }
27 |
28 | func test_Data_prettyJSONString_returnsExpected() throws {
29 | let data = try JSONEncoder().encode(model)
30 | XCTAssertNotNil(data.prettyJSONString)
31 |
32 | let otherData = try Data.fromFile(fileName: "tiffFile", extensionName: "tiff")
33 | XCTAssertNil(otherData.prettyJSONString)
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/Tests/Extensions/Data/MimeTypeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MimeTypeTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/8.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class MimeTypeTests: XCTestCase {
13 |
14 | func test_MimeType_init() {
15 | let path = "/leacode/index.html"
16 | XCTAssertEqual(FileType(pathOrFileName: path).rawValue, "text/html")
17 |
18 | let fileName = "index.html"
19 | XCTAssertEqual(FileType(pathOrFileName: fileName).rawValue, "text/html")
20 |
21 | let noExtensionFilePath = "leacode/index"
22 | XCTAssertEqual(FileType(pathOrFileName: noExtensionFilePath).rawValue, "unknown")
23 |
24 | let noExtensionFileName = "index"
25 | XCTAssertEqual(FileType(pathOrFileName: noExtensionFileName).rawValue, "unknown")
26 | }
27 |
28 | func test_MimeType_ExpressibleByStringLiteral_init() {
29 |
30 | let htmlMimeType = FileType(stringLiteral: "text/html")
31 | XCTAssertEqual(htmlMimeType, FileType.html)
32 |
33 | let unknownType = FileType(stringLiteral: "xxx")
34 | XCTAssertEqual(unknownType, FileType.unknown)
35 |
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/Tests/Extensions/Date/Date+ComponentsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date+ComponentsTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by Chunyu Li on 2021/6/18.
6 | // Copyright © 2021 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class Date_ComponentsTests: XCTestCase {
13 |
14 | var formatter: DateFormatter!
15 |
16 | override func setUp() {
17 | super.setUp()
18 |
19 | formatter = DateFormatter()
20 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
21 | formatter.timeZone = TimeZone(identifier: "UTC +8")
22 | }
23 |
24 | override func tearDown() {
25 | formatter = nil
26 | super.tearDown()
27 | }
28 |
29 | // MARK: - Given
30 |
31 | func givenADate() -> Date {
32 | return formatter.date(from: "2021-06-18 11:12:13")!
33 | }
34 |
35 | func test_Data_era_returnsExpected() {
36 | let date = givenADate()
37 | let expectedResult = 1
38 |
39 | XCTAssertEqual(date.era, expectedResult)
40 | }
41 |
42 | func test_Data_year_returnsExpected() {
43 | let date = givenADate()
44 | let expectedResult = 2021
45 |
46 | XCTAssertEqual(date.year, expectedResult)
47 | }
48 |
49 | func test_Data_month_returnsExpected() {
50 | let date = givenADate()
51 | let expectedResult = 6
52 |
53 | XCTAssertEqual(date.month, expectedResult)
54 | }
55 |
56 | func test_Data_day_returnsExpected() {
57 | let date = givenADate()
58 | let expectedResult = 18
59 |
60 | XCTAssertEqual(date.day, expectedResult)
61 | }
62 |
63 | func test_Data_hour_returnsExpected() {
64 | let date = givenADate()
65 | let expectedResult = 11
66 |
67 | XCTAssertEqual(date.hour, expectedResult)
68 | }
69 |
70 | func test_Data_minute_returnsExpected() {
71 | let date = givenADate()
72 | let expectedResult = 12
73 |
74 | XCTAssertEqual(date.minute, expectedResult)
75 | }
76 |
77 | func test_Data_second_returnsExpected() {
78 | let date = givenADate()
79 | let expectedResult = 13
80 |
81 | XCTAssertEqual(date.second, expectedResult)
82 | }
83 |
84 | func test_Data_weekday_returnsExpected() {
85 | let date = givenADate() // It's a friday
86 | let expectedResult = 6
87 |
88 | XCTAssertEqual(date.weekday, expectedResult)
89 | }
90 |
91 | func test_Data_weekdayOrdinal_returnsExpected() {
92 | let date = givenADate()
93 | let expectedResult = 3 // Third friday of this May
94 |
95 | XCTAssertEqual(date.weekdayOrdinal, expectedResult)
96 | }
97 |
98 | func test_Data_weekOfMonth_returnsExpected() {
99 | let date = givenADate()
100 | let expectedResult = 3
101 |
102 | XCTAssertEqual(date.weekOfMonth, expectedResult)
103 | }
104 |
105 | func test_Data_weekOfYear_returnsExpected() {
106 | let date = givenADate()
107 | let expectedResult = 25
108 |
109 | XCTAssertEqual(date.weekOfYear, expectedResult)
110 | }
111 |
112 | func test_Data_yearForWeekOfYear_returnsExpected() {
113 | let date = givenADate()
114 | let expectedResult = 2021
115 |
116 | XCTAssertEqual(date.yearForWeekOfYear, expectedResult)
117 | }
118 |
119 | func test_Data_nanosecond_returnsExpected() {
120 | let date = givenADate()
121 | let expectedResult = 0
122 |
123 | XCTAssertEqual(date.nanosecond, expectedResult)
124 | }
125 |
126 | func test_Data_calender_returnsExpected() {
127 | let date = givenADate()
128 | let expectedResult = 9223372036854775807
129 |
130 | XCTAssertEqual(date.calendar, expectedResult)
131 | }
132 |
133 | func test_Data_timeZone_returnsExpected() {
134 | let date = givenADate()
135 | let expectedResult = 9223372036854775807
136 |
137 | XCTAssertEqual(date.timeZone, expectedResult)
138 | }
139 |
140 | }
141 |
--------------------------------------------------------------------------------
/Tests/Extensions/Date/Date+ExtensionsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Date+ExtensionsTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/14.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class Date_ExtensionsTests: XCTestCase {
13 |
14 | var now: Date!
15 | var dayFormatter: DateFormatter!
16 | var formatter: DateFormatter!
17 |
18 | override func setUp() {
19 | super.setUp()
20 |
21 | now = Date()
22 | formatter = DateFormatter()
23 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
24 |
25 | dayFormatter = DateFormatter()
26 | dayFormatter.dateFormat = "yyyy-MM-dd"
27 | }
28 |
29 | override func tearDown() {
30 | now = nil
31 | formatter = nil
32 | dayFormatter = nil
33 | super.tearDown()
34 | }
35 |
36 | // MARK: - given
37 | func givenAMonday() -> Date {
38 | return dayFormatter.date(from: "2020-01-13")!
39 | }
40 |
41 | // MARK: - Tests
42 |
43 | func test_Date_currentTimeMillis_returnsExpected() {
44 | let timeMillis = now.timeIntervalSince1970 * 1000
45 | XCTAssertGreaterThanOrEqual(Date.currentTimeMillis, Int64(timeMillis))
46 | }
47 |
48 | func test_Date_timeMillis_returnsExpected() {
49 | let timeMillis = now.timeIntervalSince1970 * 1000
50 | XCTAssertGreaterThanOrEqual(Date().timeMillis, Int64(timeMillis))
51 | }
52 |
53 | func test_Date_isToday_returnsExpected() {
54 | XCTAssertTrue(now.isToday)
55 | }
56 |
57 | func test_Date_isTomorrow_returnsExpected() {
58 | let tomorrow = now.addingTimeInterval(60 * 60 * 24)
59 |
60 | XCTAssertTrue(tomorrow.isTomorrow)
61 | XCTAssertFalse(now.isTomorrow)
62 | }
63 |
64 | func test_Date_isInThisWeek_returnsExpected() {
65 |
66 | XCTAssertTrue(now.isInThisWeek)
67 | }
68 |
69 | func test_Date_isInNextWeek_returnsExpected() {
70 | let date = now.addingTimeInterval(60 * 60 * 24 * 7)
71 |
72 | XCTAssertTrue(date.isInNextWeek)
73 | }
74 |
75 | func test_Date_lastMonday_returnsExpected() {
76 | let aMonday = givenAMonday()
77 | guard let expectedDate = dayFormatter.date(from: "2020-01-06") else { return }
78 | let aLastMonday = aMonday.lastMonday
79 |
80 | XCTAssertEqual(aLastMonday, expectedDate)
81 | }
82 |
83 | func test_Date_lastTuesday_returnsExpected() {
84 | let aMonday = givenAMonday()
85 | guard let expectedDate = dayFormatter.date(from: "2020-01-07") else { return }
86 | let aLastTuesday = aMonday.lastTuesday
87 |
88 | XCTAssertEqual(aLastTuesday, expectedDate)
89 | }
90 |
91 | func test_Date_lastWednesday_returnsExpected() {
92 | let aMonday = givenAMonday()
93 | guard let expectedDate = dayFormatter.date(from: "2020-01-08") else { return }
94 | let aLastWednesday = aMonday.lastWednesday
95 |
96 | XCTAssertEqual(aLastWednesday, expectedDate)
97 | }
98 |
99 | func test_Date_lastThursday_returnsExpected() {
100 | let aMonday = givenAMonday()
101 | guard let expectedDate = dayFormatter.date(from: "2020-01-09") else { return }
102 | let aLastThursday = aMonday.lastThursday
103 |
104 | XCTAssertEqual(aLastThursday, expectedDate)
105 | }
106 |
107 | func test_Date_lastFriday_returnsExpected() {
108 | let aMonday = givenAMonday()
109 | guard let expectedDate = dayFormatter.date(from: "2020-01-10") else { return }
110 | let aLastFriday = aMonday.lastFriday
111 |
112 | XCTAssertEqual(aLastFriday, expectedDate)
113 | }
114 |
115 | func test_Date_lastSatuaday_returnsExpected() {
116 | let aMonday = givenAMonday()
117 | guard let expectedDate = dayFormatter.date(from: "2020-01-11") else { return }
118 | let aLastSatuaday = aMonday.lastSatuaday
119 |
120 | XCTAssertEqual(aLastSatuaday, expectedDate)
121 | }
122 |
123 | func test_Date_lastSunday_returnsExpected() {
124 | let aMonday = givenAMonday()
125 | guard let expectedDate = dayFormatter.date(from: "2020-01-12") else { return }
126 | let aLastSunday = aMonday.lastSunday
127 |
128 | XCTAssertEqual(aLastSunday, expectedDate)
129 | }
130 |
131 | func test_Date_lastDay_returnsExpected() {
132 | let aMonday = givenAMonday()
133 | guard let expectedDate = dayFormatter.date(from: "2020-01-12") else { return }
134 | XCTAssertEqual(aMonday.lastDay!, expectedDate)
135 | }
136 |
137 | func test_Date_nextDay_returnsExpected() {
138 | let aMonday = givenAMonday()
139 | guard let expectedDate = dayFormatter.date(from: "2020-01-14") else { return }
140 | XCTAssertEqual(aMonday.nextDay!, expectedDate)
141 | }
142 |
143 | func test_Date_lastMonth_returnsExpected() {
144 | let date = dayFormatter.date(from: "2020-5-31")!
145 | guard let expectedDate = dayFormatter.date(from: "2020-04-30") else { return }
146 | XCTAssertEqual(date.lastMonth!, expectedDate)
147 | }
148 |
149 | func test_Date_nextMonth_returnsExpected() {
150 | let date = dayFormatter.date(from: "2020-5-31")!
151 | guard let expectedDate = dayFormatter.date(from: "2020-06-30") else { return }
152 | XCTAssertEqual(date.nextMonth!, expectedDate)
153 | }
154 |
155 | func test_Date_lastYear_returnsExpected() {
156 | let date = dayFormatter.date(from: "2020-5-31")!
157 | guard let expectedDate = dayFormatter.date(from: "2019-065-31") else { return }
158 | let lastYear = date.lastYear!
159 |
160 | XCTAssertEqual(lastYear, expectedDate)
161 | }
162 |
163 | func test_Date_nextYear_returnsExpected() {
164 | let date = dayFormatter.date(from: "2020-5-31")!
165 | guard let expectedDate = dayFormatter.date(from: "2021-065-31") else { return }
166 | let nextYear = date.nextYear!
167 |
168 | XCTAssertEqual(nextYear, expectedDate)
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/Tests/Extensions/Double/Double+ExtensionsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Double+ExtensionsTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/9/25.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class Double_ExtensionsTests: XCTestCase {
12 |
13 | func test_toString_returnsExpected() {
14 | let double: Double = 121.123231
15 | XCTAssertEqual(double.toString(), "121.12")
16 | }
17 |
18 | func test_toInt_returnsExpected() {
19 | let double: Double = 321321
20 | XCTAssertEqual(double.toInt(), 321321)
21 | XCTAssertEqual(Double(32131.333).toInt(), 32131)
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/Extensions/Double/Double_ChineseMoneyTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Double_ChineseMoneyTests.swift
3 | // SwiftWings
4 | //
5 | // Created by leacode on 2020/3/30.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class Double_ChineseMoneyTests: XCTestCase {
13 |
14 | func test_Double_chineseMoneySpell_returnsExpected() {
15 | XCTAssertEqual(3213213.21.chineseMoneySpell, "叁佰贰拾壹万叁仟贰佰壹拾叁圆贰角壹分")
16 | XCTAssertEqual(0.56.chineseMoneySpell, "伍角陆分")
17 | XCTAssertEqual(0.5678.chineseMoneySpell, "伍角柒分")
18 | XCTAssertEqual(123456.chineseMoneySpell, "壹拾贰万叁仟肆佰伍拾陆圆整")
19 | XCTAssertEqual(123456789.chineseMoneySpell, "壹亿贰仟叁佰肆拾伍万陆仟柒佰捌拾玖圆整")
20 | XCTAssertEqual(123456.5.chineseMoneySpell, "壹拾贰万叁仟肆佰伍拾陆圆伍角")
21 | XCTAssertEqual(123456.55.chineseMoneySpell, "壹拾贰万叁仟肆佰伍拾陆圆伍角伍分")
22 | XCTAssertEqual(123456.05.chineseMoneySpell, "壹拾贰万叁仟肆佰伍拾陆圆零伍分")
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/Tests/Extensions/Int/Int+ChineseCurrencyTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Int+ChineseCurrencyTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/3/30.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class Int_ChineseCurrencyTests: XCTestCase {
12 |
13 | func test_Int_chineseMoneySpell_returnsExpected() {
14 | XCTAssertEqual(Int(123456).chineseMoneySpell, "壹拾贰万叁仟肆佰伍拾陆圆整")
15 | XCTAssertEqual(Int(123456789).chineseMoneySpell, "壹亿贰仟叁佰肆拾伍万陆仟柒佰捌拾玖圆整")
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/Tests/Extensions/Int/Int+ExtensionsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Int+ExtensionsTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/8.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class Int_ExtensionsTests: XCTestCase {
13 |
14 | func test_simplifiedChinese_rerurnsExpected() {
15 |
16 | XCTAssertEqual(1.simplifiedChinese, "一")
17 | XCTAssertEqual(12.simplifiedChinese, "十二")
18 | XCTAssertEqual(123.simplifiedChinese, "一百二十三")
19 | XCTAssertEqual(1234.simplifiedChinese, "一千二百三十四")
20 | XCTAssertEqual(123456.simplifiedChinese, "十二万三千四百五十六")
21 | XCTAssertEqual(1234567.simplifiedChinese, "一百二十三万四千五百六十七")
22 | XCTAssertEqual(12345678.simplifiedChinese, "一千二百三十四万五千六百七十八")
23 | XCTAssertEqual(123456789.simplifiedChinese, "一亿二千三百四十五万六千七百八十九")
24 | XCTAssertEqual(1234567890.simplifiedChinese, "十二亿三千四百五十六万七千八百九十")
25 | XCTAssertEqual(Int64(12345678900).simplifiedChinese, "一百二十三亿四千五百六十七万八千九百")
26 | XCTAssertEqual(Int64(123456789000).simplifiedChinese, "一千二百三十四亿五千六百七十八万九千")
27 | XCTAssertEqual(Int64(1234567890000).simplifiedChinese, "一兆二千三百四十五亿六千七百八十九万")
28 |
29 | }
30 | func test_traditionalChinese_rerurnsExpected() {
31 |
32 | XCTAssertEqual(1.traditionalChinese, "一")
33 | XCTAssertEqual(12.traditionalChinese, "十二")
34 | XCTAssertEqual(123.traditionalChinese, "一百二十三")
35 | XCTAssertEqual(1234.traditionalChinese, "一千二百三十四")
36 | XCTAssertEqual(123456.traditionalChinese, "十二萬三千四百五十六")
37 | XCTAssertEqual(1234567.traditionalChinese, "一百二十三萬四千五百六十七")
38 | XCTAssertEqual(12345678.traditionalChinese, "一千二百三十四萬五千六百七十八")
39 | XCTAssertEqual(123456789.traditionalChinese, "一億二千三百四十五萬六千七百八十九")
40 | XCTAssertEqual(1234567890.traditionalChinese, "十二億三千四百五十六萬七千八百九十")
41 | XCTAssertEqual(Int64(12345678900).traditionalChinese, "一百二十三億四千五百六十七萬八千九百")
42 | XCTAssertEqual(Int64(123456789000).traditionalChinese, "一千二百三十四億五千六百七十八萬九千")
43 | XCTAssertEqual(Int64(1234567890000).traditionalChinese, "一兆二千三百四十五億六千七百八十九萬")
44 |
45 | }
46 |
47 | func test_half_returnsExpected() {
48 | XCTAssertEqual(4.half, 2)
49 | XCTAssertEqual(5.half, 2)
50 | }
51 |
52 | func test_isEven_returnsExpected() {
53 | XCTAssertTrue(2.isEven)
54 | XCTAssertTrue(4.isEven)
55 |
56 | XCTAssertFalse(5.isEven)
57 | }
58 |
59 | func test_toDouble_returnsExpected() {
60 | XCTAssertEqual(1.toDouble(), Double(1))
61 | XCTAssertEqual(133232.toDouble(), Double(133232))
62 | XCTAssertEqual(-21211.toDouble(), Double(-21211))
63 | XCTAssertEqual(0.toDouble(), Double(0))
64 | }
65 |
66 | func test_toString_returnsExpected() {
67 | XCTAssertEqual(123.toString(), "123")
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/Tests/Extensions/Mirror/Mirror+ReflectionTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Mirror+ReflectionTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/14.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | @testable import SwiftWings
10 | import XCTest
11 |
12 | protocol TestProtocol {
13 | func invokeFunction() -> Bool
14 | }
15 |
16 | class TestClass1: TestProtocol {
17 | func invokeFunction() -> Bool {
18 | return true
19 | }
20 | }
21 |
22 | class TestClass2: TestProtocol {
23 | func invokeFunction() -> Bool {
24 | return true
25 | }
26 | }
27 |
28 | class TestClass3: TestProtocol {
29 | func invokeFunction() -> Bool {
30 | return true
31 | }
32 | }
33 |
34 | class TestMirrorClass {
35 | var class1 = TestClass1()
36 | var class2 = TestClass2()
37 | var class3 = TestClass3()
38 |
39 | func invokeDirectly() -> Bool {
40 | return class1.invokeFunction() &&
41 | class2.invokeFunction() &&
42 | class3.invokeFunction()
43 | }
44 |
45 | func invoke() -> Bool {
46 | var reflected: Bool = false
47 | Mirror.reflectProperties(of: self) { (_: TestProtocol) in
48 | reflected = true
49 | }
50 |
51 | return reflected
52 | }
53 |
54 | func invokeRecursively() -> Bool {
55 | var reflected: Bool = false
56 | Mirror
57 | .reflectProperties(of: self,
58 | recursively: true) { (_: TestProtocol) in
59 | reflected = true
60 | }
61 |
62 | return reflected
63 | }
64 | }
65 |
66 | class Mirror_ReflectionTests: XCTestCase {
67 | var testMirrorClass: TestMirrorClass!
68 |
69 | override func setUp() {
70 | super.setUp()
71 |
72 | testMirrorClass = TestMirrorClass()
73 | }
74 |
75 | override func tearDown() {
76 | testMirrorClass = nil
77 |
78 | super.tearDown()
79 | }
80 |
81 | func test_Mirror_reflectProperties() throws {
82 | for _ in 0 ... 1000 {
83 | XCTAssertTrue(testMirrorClass.invoke())
84 | XCTAssertTrue(testMirrorClass.invokeRecursively())
85 | }
86 | }
87 |
88 | func test_InvokeDirectly() {
89 | XCTAssertEqual(testMirrorClass.invokeDirectly(), true)
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Tests/Extensions/NSObject/NSObject+NameTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSObject+NameTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/3.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | @testable import SwiftWings
10 | import XCTest
11 |
12 | class NSObject_NameTests: XCTestCase {
13 |
14 | class NSObjectClass: NSObject {}
15 |
16 | func test_className_returnsExpected() {
17 | // given
18 | let expected = "NSObjectClass"
19 |
20 | // when
21 | let aClass = NSObjectClass()
22 | let className = aClass.className
23 |
24 | // then
25 | XCTAssertEqual(className, expected)
26 | }
27 |
28 | func test_class_className_returnExpected() {
29 | // given
30 | let expected = "NSObjectClass"
31 |
32 | // when
33 | let className = NSObjectClass.className
34 |
35 | // then
36 | XCTAssertEqual(className, expected)
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/Tests/Extensions/Optional/Optional+TransformTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Optional+TransformTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/20.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class Optional_TransformTests: XCTestCase {
13 |
14 | func test_String_NilCoalescingPrecedence_returnsExpected() {
15 |
16 | let aString: String? = nil
17 | let aData: Data? = nil
18 | let aNotNullString: String? = "abc"
19 |
20 | XCTAssertEqual(aString ??? "str", "str")
21 | XCTAssertEqual(aData ??? "str", "str")
22 | XCTAssertEqual(aNotNullString ??? "str", "abc")
23 |
24 | }
25 |
26 | // func test_failureText_works() {
27 | // let s = "foo"
28 | //
29 | //
30 | //
31 | // let i = Int(s) !! "Expecting integer, got \"\(s)\""
32 | //// XCTAssertEqual(i, "Expecting integer, got \"foo\"")
33 | // }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/Tests/Extensions/Sequence/Sequence+ExtensionsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Sequence+ExtensionsTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/2/29.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class Sequence_ExtensionsTests: XCTestCase {
13 |
14 | func test_Sequence_unique_returnsExpected() {
15 | let result = [1, 2, 2, 3, 3, 4, 5, 6, 7, 7].unique()
16 | let expectedResult = [1, 2, 3, 4, 5, 6, 7]
17 | XCTAssertEqual(result, expectedResult)
18 |
19 | let result1 = ["a", "b", "c", "c", "c", "e", "f", "f", "ab", "ab", "ef"].unique()
20 | let expectedResult1 = ["a", "b", "c", "e", "f", "ab", "ef"]
21 | XCTAssertEqual(result1, expectedResult1)
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/Extensions/String/String+EmojiTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+EmojiTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/28.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class String_EmojiTests: XCTestCase {
13 |
14 | override func 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 | }
21 |
22 | func test_String_isSingleEmoji_returnsExpected() {
23 | XCTAssertTrue("😎".isSingleEmoji)
24 | XCTAssertTrue("⛓".isSingleEmoji)
25 | XCTAssertFalse("😎😍".isSingleEmoji)
26 | XCTAssertFalse("abc".isSingleEmoji)
27 | }
28 |
29 | func test_String_containsOnlyEmoji_returnsExpected() {
30 | XCTAssertTrue("😎".containsOnlyEmoji)
31 | XCTAssertTrue("😎😳".containsOnlyEmoji)
32 | XCTAssertTrue("😎👨🏾🦳".containsOnlyEmoji)
33 |
34 | XCTAssertFalse("⛿".containsOnlyEmoji)
35 | XCTAssertFalse("😎↪".containsOnlyEmoji)
36 | XCTAssertFalse("😎↪⤵#".containsOnlyEmoji)
37 | XCTAssertFalse("😎↪⤵#😎↪".containsOnlyEmoji)
38 | XCTAssertFalse("#".containsOnlyEmoji)
39 | XCTAssertFalse("0xFE0F".containsOnlyEmoji)
40 | XCTAssertFalse("😎😍aca".containsOnlyEmoji)
41 | XCTAssertFalse("abc😎😍aca".containsOnlyEmoji)
42 | XCTAssertFalse("abc😎adsfa😍aca".containsOnlyEmoji)
43 | XCTAssertFalse("abc".containsOnlyEmoji)
44 | XCTAssertFalse("".containsOnlyEmoji)
45 |
46 | print("abc")
47 |
48 | print(String(UnicodeScalar(0x200D)!) + "abc")
49 |
50 | // XCTAssertNotEqual(String(UnicodeScalar(0xFE0F)!), String(UnicodeScalar(0xFE0F)!))
51 |
52 | }
53 | func test_String_containsEmoji_returnsExpected() {
54 | XCTAssertTrue("11sss👽431fas".containsEmoji)
55 | XCTAssertTrue("1👽☝🏽💪".containsEmoji)
56 | XCTAssertTrue("1adf⛓afdasf".containsEmoji)
57 | XCTAssertFalse("12345678AFJDAISOFOADS".containsEmoji)
58 | }
59 |
60 | func test_String_trimmingEmojis_returnsExpected() {
61 | XCTAssertEqual("👧🏼abc👨🏾🦳fadsf🕵🏻♀️adsf👳🏾♀️".trimmingEmojis, "abcfadsfadsf")
62 | XCTAssertEqual("abc👨🏾🦳fadsf🕵🏻♀️adsf".trimmingEmojis, "abcfadsfadsf")
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/Tests/Extensions/String/String+ExtensionsTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+ExtensionsTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/9/18.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class String_ExtensionsTests: XCTestCase {
13 |
14 | func test_String_asURL_returnsExpected() throws {
15 | let url1 = "https://www.baidu.com"
16 | let url2 = "fasdjfapsoifjadsof"
17 | XCTAssertEqual(url1.asURL, URL(string: url1))
18 | XCTAssertNotNil(url2.asURL)
19 | XCTAssertEqual(url2.asURL, URL(string: url2))
20 | }
21 |
22 | func test_String_asDict_returnsExpected() throws {
23 | let str = """
24 | {"name": "Leacode", "age": 32, "gendar": "male", "married": true}
25 | """
26 | XCTAssertNotNil(str.asDict)
27 |
28 | let str1 = "fadjfiaosf"
29 | XCTAssertNil(str1.asDict)
30 | }
31 |
32 | func test_String_asArray_returnsExpected() throws {
33 | let str = """
34 | [{"name": "Tom"}, {"name": "Jerry"}]
35 | """
36 | XCTAssertNotNil(str.asArray)
37 |
38 | let str1 = "fadjfiaosf"
39 | XCTAssertNil(str1.asArray)
40 | }
41 |
42 | func test_String_localized_returnsExpected() throws {
43 | XCTAssertEqual("Hello".localized, "Hello")
44 | }
45 |
46 | func test_String_init_returnsExpected() {
47 |
48 | let jsonObj: [String: Any] = [
49 | "name": "Leacode",
50 | "age": 32,
51 | "gendar": "male",
52 | "married": true
53 | ]
54 |
55 | XCTAssertNotNil(String(json: jsonObj))
56 | }
57 |
58 | func test_String_jsonToDictionary_returnsExpected() {
59 | let json = """
60 | {"name": "Leacode", "age": 32, "gendar": "male", "married": true}
61 | """
62 | XCTAssertNotNil(json.jsonToDictionary())
63 | }
64 |
65 | func test_String_jsonToArray_returnsExpected() {
66 | let json = """
67 | [{"name": "Tom"}, {"name": "Jerry"}]
68 | """
69 | XCTAssertNotNil(json.jsonToArray())
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/Tests/Extensions/String/String+MD5Tests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+MD5Tests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/8.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class String_MD5Tests: XCTestCase {
13 |
14 | func test_md5_returnsExpected() {
15 | let expected = "e10adc3949ba59abbe56e057f20f883e"
16 |
17 | let result = "123456".md5
18 |
19 | XCTAssertEqual(result, expected)
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/Extensions/String/String+ModifyTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+ModifyTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/15.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | @testable import SwiftWings
10 | import XCTest
11 |
12 | class String_ModifyTests: XCTestCase {
13 | func test_String_vowels_returnsExpected() {
14 | XCTAssertEqual(String.vowels, ["a", "e", "i", "o", "u"])
15 | }
16 |
17 | func test_String_consonants_returnsExpected() {
18 | let consonants = ["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "z"]
19 | XCTAssertEqual(String.consonants, consonants)
20 | }
21 |
22 | func test_String_pluralize_returnsExpected() {
23 | XCTAssertEqual("".pluralize(0), "")
24 | XCTAssertEqual("".pluralize(1), "")
25 | XCTAssertEqual("".pluralize(2), "")
26 |
27 | XCTAssertEqual("a".pluralize(0), "a")
28 | XCTAssertEqual("a".pluralize(1), "a")
29 | XCTAssertEqual("a".pluralize(2), "a")
30 |
31 | XCTAssertEqual("Reply".pluralize(0), "Reply")
32 | XCTAssertEqual("Reply".pluralize(1), "Reply")
33 | XCTAssertEqual("Reply".pluralize(2), "Replies")
34 | XCTAssertEqual("REPLY".pluralize(3), "REPLIES")
35 | XCTAssertEqual("Horse".pluralize(2), "Horses")
36 | XCTAssertEqual("Boy".pluralize(2), "Boys")
37 | XCTAssertEqual("Cut".pluralize(2), "Cuts")
38 | XCTAssertEqual("Boss".pluralize(2), "Bosses")
39 | XCTAssertEqual("Domino".pluralize(2), "Dominoes")
40 |
41 | XCTAssertEqual("product".pluralize(0), "product")
42 | XCTAssertEqual("product".pluralize(1), "product")
43 | XCTAssertEqual("product".pluralize(2), "products")
44 | XCTAssertEqual("product".pluralize(3), "products")
45 |
46 | XCTAssertEqual("furniture".pluralize(3, countable: false), "furniture")
47 | }
48 |
49 | func testMatch() {
50 | let sRegex = #"^[a-z|A-Z|0-9]+[^S]\s?(s{1}|S{1})$"#
51 |
52 | XCTAssertTrue("apples".matches(pattern: sRegex))
53 | XCTAssertTrue("34fqsdfs".matches(pattern: sRegex))
54 | XCTAssertTrue("appleS".matches(pattern: sRegex))
55 | XCTAssertFalse("appleSS".matches(pattern: sRegex))
56 | XCTAssertFalse("apple".matches(pattern: sRegex))
57 |
58 | let feRegex = #"^[a-z|A-Z|0-9]+[^S]\s?(f{1}|fe{1})$"#
59 | XCTAssertTrue("wife".matches(pattern: feRegex))
60 | XCTAssertTrue("wolf".matches(pattern: feRegex))
61 |
62 | let consonantAndYRegex = #"^[a-z|A-Z|0-9]+[bddfghjklmnpqrstvxzBCDFGHJKLMNPQRSTVXZ]\s?(y{1}|Y{1})$"#
63 |
64 | XCTAssertTrue("city".matches(pattern: consonantAndYRegex))
65 | XCTAssertTrue("puppy".matches(pattern: consonantAndYRegex))
66 | }
67 |
68 | // Test case is from https://www.grammarly.com/blog/plural-nouns/
69 | func test_String_plural_returnsExpected() {
70 |
71 | XCTAssertEqual("a".plural, "a")
72 | XCTAssertEqual("A".plural, "A")
73 | XCTAssertEqual("".plural, "")
74 |
75 |
76 | XCTAssertEqual("cat".plural, "cats")
77 | XCTAssertEqual("house".plural, "houses")
78 |
79 | XCTAssertEqual("truss".plural, "trusses")
80 | XCTAssertEqual("bus".plural, "busses")
81 | XCTAssertEqual("marsh".plural, "marshes")
82 | XCTAssertEqual("lunch".plural, "lunches")
83 | XCTAssertEqual("tax".plural, "taxes")
84 | XCTAssertEqual("blitz".plural, "blitzes")
85 |
86 | XCTAssertEqual("fez".plural, "fezzes")
87 | XCTAssertEqual("gas".plural, "gasses")
88 |
89 | XCTAssertEqual("wife".plural, "wives")
90 | XCTAssertEqual("wolf".plural, "wolves")
91 |
92 | XCTAssertEqual("roof".plural, "roofs")
93 | XCTAssertEqual("belief".plural, "beliefs")
94 | XCTAssertEqual("chef".plural, "chefs")
95 | XCTAssertEqual("chief".plural, "chiefs")
96 |
97 | XCTAssertEqual("city".plural, "cities")
98 | XCTAssertEqual("puppy".plural, "puppies")
99 |
100 | XCTAssertEqual("ray".plural, "rays")
101 | XCTAssertEqual("boy".plural, "boys")
102 |
103 | XCTAssertEqual("potato".plural, "potatoes")
104 | XCTAssertEqual("tomato".plural, "tomatoes")
105 | XCTAssertEqual("photo".plural, "photos")
106 | XCTAssertEqual("piano".plural, "pianos")
107 | XCTAssertEqual("halo".plural, "halos")
108 |
109 | // Upper Cases
110 |
111 | XCTAssertEqual("CAT".plural, "CATS")
112 | XCTAssertEqual("HOUSE".plural, "HOUSES")
113 |
114 | XCTAssertEqual("TRUSS".plural, "TRUSSES")
115 | XCTAssertEqual("BUS".plural, "BUSSES")
116 | XCTAssertEqual("MARSH".plural, "MARSHES")
117 | XCTAssertEqual("LUNCH".plural, "LUNCHES")
118 | XCTAssertEqual("TAX".plural, "TAXES")
119 | XCTAssertEqual("BLITZ".plural, "BLITZES")
120 |
121 | XCTAssertEqual("FEZ".plural, "FEZZES")
122 | XCTAssertEqual("GAS".plural, "GASSES")
123 |
124 | XCTAssertEqual("WIFE".plural, "WIVES")
125 | XCTAssertEqual("WOLF".plural, "WOLVES")
126 |
127 | XCTAssertEqual("ROOF".plural, "ROOFS")
128 | XCTAssertEqual("BELIEF".plural, "BELIEFS")
129 | XCTAssertEqual("CHEF".plural, "CHEFS")
130 | XCTAssertEqual("CHIEF".plural, "CHIEFS")
131 |
132 | XCTAssertEqual("CITY".plural, "CITIES")
133 | XCTAssertEqual("PUPPY".plural, "PUPPIES")
134 |
135 | XCTAssertEqual("RAY".plural, "RAYS")
136 | XCTAssertEqual("BOY".plural, "BOYS")
137 |
138 | XCTAssertEqual("POTATO".plural, "POTATOES")
139 | XCTAssertEqual("TOMATO".plural, "TOMATOES")
140 | XCTAssertEqual("PHOTO".plural, "PHOTOS")
141 | XCTAssertEqual("PIANO".plural, "PIANOS")
142 | XCTAssertEqual("HALO".plural, "HALOS")
143 | }
144 |
145 | func test_String_replacingCharacters_returnsExpected() {
146 | let str = "12345"
147 | let dict = ["1": "一", "3": "三", "5": "五", "8": "八"]
148 | XCTAssertEqual(str.replacingCharacters(in: dict), "一2三4五")
149 | }
150 |
151 | func test_String_trimmingTrailingSpaces_returnsExpected() {
152 |
153 | XCTAssertEqual("abc ".trimmingTrailingSpaces(), "abc")
154 | XCTAssertEqual(" abc ".trimmingTrailingSpaces(), " abc")
155 | XCTAssertEqual(" abc ".trimmingTrailingSpaces(), " abc")
156 | XCTAssertEqual("abc ".trimmingTrailingSpaces(), "abc")
157 |
158 | }
159 |
160 | func test_String_capitalizingFirstLetter_returnsExpected() {
161 | XCTAssertEqual("hello world".capitalizingFirstLetter(), "Hello world")
162 | }
163 |
164 | func test_String_capitalizeFirstLetter_returnsExpected() {
165 | var str = "hello world"
166 | str.capitalizeFirstLetter()
167 | XCTAssertEqual(str, "Hello world")
168 | }
169 |
170 | func test_String_trimmed_returnsExpected() {
171 | XCTAssertEqual(" Hello world! ".trimmed, "Hello world!")
172 | }
173 |
174 | func test_String_trim_returnsExpected() {
175 | var str = " Hello world! "
176 | str.trim()
177 | XCTAssertEqual(str, "Hello world!")
178 | }
179 |
180 | }
181 |
--------------------------------------------------------------------------------
/Tests/Extensions/String/String+SubscriptTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+SubscriptTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/3.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class String_SubscriptTests: XCTestCase {
13 |
14 | func test_subscript_ClosedRange_returnsExpected() {
15 | // given
16 | let expected = "3456"
17 |
18 | // when
19 | let str = "12345678"
20 | let result = str[2...5]
21 |
22 | // then
23 | XCTAssertEqual(result, expected)
24 | }
25 |
26 | func test_subscript_ClosedRange_wrongRange_returnsExpected() {
27 | // given
28 | let expected = ""
29 |
30 | // when
31 | let str = "12345678"
32 | let result1 = str[-3...5]
33 | let result2 = str[0...20]
34 | let result3 = str[4...20]
35 |
36 | // then
37 | XCTAssertEqual(result1, expected)
38 | XCTAssertEqual(result2, expected)
39 | XCTAssertEqual(result3, expected)
40 | }
41 |
42 | func test_subscript_halfOpenRange_returnsExpected() {
43 | // given
44 | let expected = "345"
45 |
46 | // when
47 | let str = "12345678"
48 | let result = str[2..<5]
49 |
50 | // then
51 | XCTAssertEqual(result, expected)
52 | }
53 |
54 | func test_subscript_halfOpenRange_wrongRange_returnsExpected() {
55 | // given
56 | let expected = ""
57 |
58 | // when
59 | let str = "12345678"
60 | let result1 = str[-3..<5]
61 | let result2 = str[-5..<7]
62 | let result3 = str[0..<20]
63 | let result4 = str[4..<20]
64 |
65 | // then
66 | XCTAssertEqual(result1, "12345")
67 | XCTAssertEqual(result2, "1234567")
68 | XCTAssertEqual(result3, expected)
69 | XCTAssertEqual(result4, expected)
70 | }
71 |
72 | func test_subscript_PartialRangeFrom_returnsExpected() {
73 | // given
74 | let expected = "345678"
75 |
76 | // when
77 | let str = "12345678"
78 | let result = str[2...]
79 |
80 | // then
81 | XCTAssertEqual(result, expected)
82 | }
83 |
84 | func test_subscript_PartialRangeFrom_wrongRange_returnsExpected() {
85 | // given
86 | let expected = ""
87 |
88 | // when
89 | let str = "12345678"
90 | let result = str[9...]
91 | let result1 = str[(-5)...]
92 |
93 | // then
94 | XCTAssertEqual(result, expected)
95 | XCTAssertEqual(result1, expected)
96 | }
97 |
98 | func test_subscript_PartialRangeThrough_returnsExpected() {
99 | // given
100 | let expected = "123456"
101 |
102 | // when
103 | let str = "12345678"
104 | let result = str[...5]
105 |
106 | // then
107 | XCTAssertEqual(result, expected)
108 | }
109 |
110 | func test_subscript_PartialRangeThrough_wrongRange_returnsExpected() {
111 | // given
112 | let expected = ""
113 |
114 | // when
115 | let str = "12345678"
116 | let result = str[...(-5)]
117 | let result1 = str[...(20)]
118 |
119 |
120 | // then
121 | XCTAssertEqual(result, expected)
122 | XCTAssertEqual(result1, expected)
123 | }
124 |
125 | func test_subscript_PartialRangeUpTo_returnsExpected() {
126 | // given
127 | let expected = "12345"
128 |
129 | // when
130 | let str = "12345678"
131 | let result = str[..<5]
132 |
133 | // then
134 | XCTAssertEqual(result, expected)
135 | }
136 |
137 | func test_subscript_PartialRangeUpTo_wrongRange_returnsExpected() {
138 | // given
139 | let expected = ""
140 |
141 | // when
142 | let str = "12345678"
143 | let result = str[..<(-5)]
144 | let result1 = str[..<20]
145 |
146 | // then
147 | XCTAssertEqual(result, expected)
148 | XCTAssertEqual(result1, expected)
149 |
150 | }
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/Tests/Extensions/String/String+ValidationTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+ValidationTests.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/6.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import SwiftWings
11 |
12 | class String_ValidationTests: XCTestCase {
13 |
14 | func test_containsPhoneNumber_returnsExpected() {
15 | XCTAssertTrue("13477292020".containsPhoneNumber)
16 | XCTAssertTrue("134-7729-2020".containsPhoneNumber)
17 | XCTAssertTrue("我的号码是13477292020".containsPhoneNumber)
18 | XCTAssertTrue("电话13477292020这个".containsPhoneNumber)
19 | XCTAssertTrue("我的号码是134-7729-2020".containsPhoneNumber)
20 | XCTAssertTrue("电话134-7729-2020这个".containsPhoneNumber)
21 |
22 | XCTAssertTrue("+8613477292020".containsPhoneNumber)
23 | XCTAssertTrue("+86134-7729-2020".containsPhoneNumber)
24 | XCTAssertTrue("我的号码是+86134-7729-2020".containsPhoneNumber)
25 | XCTAssertTrue("电话+86134-7729-2020这个".containsPhoneNumber)
26 | XCTAssertTrue("我的号码是+86134-7729-2020".containsPhoneNumber)
27 | XCTAssertTrue("电话+86134-7729-2020这个".containsPhoneNumber)
28 |
29 | XCTAssertTrue("+86 13477292020".containsPhoneNumber)
30 | XCTAssertTrue("+86 134-7729-2020".containsPhoneNumber)
31 | XCTAssertTrue("我的号码是+86 134-7729-2020".containsPhoneNumber)
32 | XCTAssertTrue("电话+86 134-7729-2020这个".containsPhoneNumber)
33 | XCTAssertTrue("我的号码是+86 134-7729-2020".containsPhoneNumber)
34 | XCTAssertTrue("电话+86 134-7729-2020这个".containsPhoneNumber)
35 |
36 | XCTAssertFalse("134772".containsPhoneNumber)
37 | XCTAssertFalse("134-772".containsPhoneNumber)
38 | XCTAssertFalse("电话+86 134-7这个".containsPhoneNumber)
39 | XCTAssertFalse("asdffds".containsPhoneNumber)
40 | XCTAssertFalse("".containsPhoneNumber)
41 | }
42 |
43 | func test_containsLink_returnsExpected() {
44 | XCTAssertTrue("http://www.google.com".containsLink)
45 | XCTAssertTrue("https://www.google.com".containsLink)
46 | XCTAssertFalse("wwwgooglecom".containsLink)
47 | }
48 |
49 | func test_containsAddress_returnsExpected() {
50 | XCTAssertFalse("fdafdsaf".containsAddress)
51 | }
52 |
53 | func test_strictContainsPhoneNumber_returnsExpected() {
54 | XCTAssertTrue("13477292020".strictContainsPhoneNumber)
55 | XCTAssertTrue("134-7729-2020".strictContainsPhoneNumber)
56 | XCTAssertTrue("我的号码是13477292020".strictContainsPhoneNumber)
57 | XCTAssertTrue("电话13477292020这个".strictContainsPhoneNumber)
58 | XCTAssertTrue("我的号码是134-7729-2020".strictContainsPhoneNumber)
59 | XCTAssertTrue("电话134-7729-2020这个".strictContainsPhoneNumber)
60 |
61 | XCTAssertTrue("+8613477292020".strictContainsPhoneNumber)
62 | XCTAssertTrue("+86134-7729-2020".strictContainsPhoneNumber)
63 | XCTAssertTrue("我的号码是+86134-7729-2020".strictContainsPhoneNumber)
64 | XCTAssertTrue("电话+86134-7729-2020这个".strictContainsPhoneNumber)
65 | XCTAssertTrue("我的号码是+86134-7729-2020".strictContainsPhoneNumber)
66 | XCTAssertTrue("电话+86134-7729-2020这个".strictContainsPhoneNumber)
67 |
68 | XCTAssertTrue("+86 13477292020".strictContainsPhoneNumber)
69 | XCTAssertTrue("+86 134-7729-2020".strictContainsPhoneNumber)
70 | XCTAssertTrue("我的号码是+86 134-7729-2020".strictContainsPhoneNumber)
71 | XCTAssertTrue("电话+86 134-7729-2020这个".strictContainsPhoneNumber)
72 | XCTAssertTrue("我的号码是+86 134-7729-2020".strictContainsPhoneNumber)
73 | XCTAssertTrue("电话+86 134-7729-2020这个".strictContainsPhoneNumber)
74 |
75 | XCTAssertTrue("电话1 3 4 7 7 2 9 2 0 1 2这个".strictContainsPhoneNumber)
76 | XCTAssertTrue("电话1-3-4-7-7-2-9-2-0-1-2这个".strictContainsPhoneNumber)
77 | XCTAssertTrue("电话13477~2~92020这个".strictContainsPhoneNumber)
78 |
79 | XCTAssertFalse("".strictContainsPhoneNumber)
80 | XCTAssertFalse("abc".strictContainsPhoneNumber)
81 | }
82 |
83 | func test_contains_returnsExpected() {
84 | XCTAssertFalse("123".contains(types: .date))
85 | }
86 |
87 | func test_isValidEmail_returnsExpected() {
88 | XCTAssertTrue("abc@gmail.com".isValidEmail)
89 | XCTAssertTrue("abc_faf989@gmail.com".isValidEmail)
90 | XCTAssertTrue("abc_faf989@gmail.com.abc.def".isValidEmail)
91 |
92 | XCTAssertFalse("abc_faf989@gmail".isValidEmail)
93 | XCTAssertFalse("@gmail.com.abc.def".isValidEmail)
94 | XCTAssertFalse("abc@".isValidEmail)
95 | XCTAssertFalse("abc@abc".isValidEmail)
96 | XCTAssertFalse("abc@abc.def ".isValidEmail)
97 | XCTAssertFalse(" abc@abc.def".isValidEmail)
98 | }
99 |
100 | func test_isValidPhoneNumber_returnsExpected() {
101 | XCTAssertTrue("13563738943".isValidPhoneNumber)
102 | XCTAssertTrue("11211111111".isValidPhoneNumber)
103 | XCTAssertTrue("2345676".isValidPhoneNumber)
104 | XCTAssertTrue("1234543".isValidPhoneNumber)
105 | XCTAssertTrue("1199999".isValidPhoneNumber)
106 |
107 | XCTAssertFalse("119999".isValidPhoneNumber)
108 | XCTAssertFalse("11999".isValidPhoneNumber)
109 | XCTAssertFalse("1199".isValidPhoneNumber)
110 | XCTAssertFalse("119".isValidPhoneNumber)
111 | XCTAssertFalse("11".isValidPhoneNumber)
112 | XCTAssertFalse("1".isValidPhoneNumber)
113 |
114 | XCTAssertFalse("123".isValidPhoneNumber)
115 | XCTAssertFalse("123".isValidPhoneNumber)
116 | XCTAssertFalse("fasdfa".isValidPhoneNumber)
117 | XCTAssertFalse("|fa324".isValidPhoneNumber)
118 | XCTAssertFalse("1-2-3-4-5".isValidPhoneNumber)
119 | XCTAssertFalse(" ".isValidPhoneNumber)
120 | XCTAssertFalse("".isValidPhoneNumber)
121 | }
122 |
123 | func test_isInt_returnsExpected() {
124 | XCTAssertTrue("12345".isInt)
125 | XCTAssertTrue("-12345".isInt)
126 |
127 | XCTAssertFalse("".isInt)
128 | XCTAssertFalse("1 23 45".isInt)
129 | XCTAssertFalse("99999999999999999999999999".isInt)
130 | XCTAssertFalse("12345.43".isInt)
131 | XCTAssertFalse("212dsadsa".isInt)
132 | }
133 |
134 | func test_isDouble_returnsExpected() {
135 |
136 | XCTAssertTrue("12345.4567".isDouble)
137 | XCTAssertTrue("-12345.4567".isDouble)
138 | XCTAssertTrue("12345".isDouble)
139 |
140 | XCTAssertFalse("212dsadsa".isDouble)
141 | }
142 |
143 | func test_isFloat_returnsExpected() {
144 | XCTAssertTrue("12345.4567".isFloat)
145 | XCTAssertTrue("-12345.4567".isFloat)
146 | XCTAssertTrue("12345".isFloat)
147 |
148 | XCTAssertFalse("212dsadsa".isFloat)
149 | }
150 |
151 | func test_isUUID_returnsExpected() {
152 | XCTAssertTrue(UUID().uuidString.isUUID)
153 | XCTAssertFalse("faisdfjaosfa".isUUID)
154 | }
155 |
156 | func test_isValidJSON_returnsExpected() {
157 | XCTAssertTrue(#"{"name":"John"}"#.isValidJSON)
158 |
159 | XCTAssertFalse(#"{name:"John"}"#.isValidJSON)
160 | }
161 |
162 | func test_isValidChineseIDCardNo_returnsExpected() {
163 |
164 | XCTAssertTrue("511823198401103576".isValidChineseIDCardNo)
165 | XCTAssertTrue("450311197509084501".isValidChineseIDCardNo)
166 | XCTAssertTrue("511823198401106574".isValidChineseIDCardNo)
167 | XCTAssertTrue("450311197509082960".isValidChineseIDCardNo)
168 | XCTAssertTrue("450311197509082020".isValidChineseIDCardNo)
169 | XCTAssertTrue("450311197509084966".isValidChineseIDCardNo)
170 | XCTAssertTrue("450311197509088609".isValidChineseIDCardNo)
171 | XCTAssertTrue("511823198401101618".isValidChineseIDCardNo)
172 | XCTAssertTrue("45031119750908814X".isValidChineseIDCardNo)
173 |
174 | XCTAssertFalse("".isValidChineseIDCardNo)
175 | XCTAssertFalse("45031119759988814X".isValidChineseIDCardNo)
176 | XCTAssertFalse("511823198401101".isValidChineseIDCardNo)
177 | XCTAssertFalse("450311197509088149".isValidChineseIDCardNo)
178 | XCTAssertFalse("-12345.4567".isValidChineseIDCardNo)
179 | XCTAssertFalse("12345".isValidChineseIDCardNo)
180 | XCTAssertFalse("212dsadsa".isValidChineseIDCardNo)
181 | XCTAssertFalse("459311197509088609".isValidChineseIDCardNo)
182 | }
183 |
184 | func test_isValidBankCardNumber_returnsExpected() {
185 | XCTAssertTrue("6222 6002 6000 1072 444".isValidBankCardNumber)
186 |
187 | XCTAssertTrue("6217000630001006673".isValidBankCardNumber)
188 | XCTAssertTrue("6214832708150615".isValidBankCardNumber)
189 | XCTAssertTrue("4367424312010636088".isValidBankCardNumber)
190 | XCTAssertTrue("6214850276357743".isValidBankCardNumber)
191 | XCTAssertTrue("4367424312010636088".isValidBankCardNumber)
192 | XCTAssertTrue("6228482099360101273".isValidBankCardNumber)
193 | XCTAssertTrue("4563516507000581268".isValidBankCardNumber)
194 | XCTAssertTrue("6225885414675652".isValidBankCardNumber)
195 | XCTAssertTrue("6226220507881760".isValidBankCardNumber)
196 | XCTAssertTrue("4340622870856897".isValidBankCardNumber)
197 | XCTAssertTrue("6227002342155755777".isValidBankCardNumber)
198 | XCTAssertTrue("6212260200007845130".isValidBankCardNumber)
199 | XCTAssertTrue("9558821202005183097".isValidBankCardNumber)
200 | XCTAssertTrue("9558803400104364285".isValidBankCardNumber)
201 |
202 | XCTAssertFalse("".isValidBankCardNumber)
203 | XCTAssertFalse("asdf".isValidBankCardNumber)
204 | XCTAssertFalse("62226002600010724".isValidBankCardNumber)
205 |
206 | //testing
207 | //VISA
208 | XCTAssertTrue("4929939187355598".isValidBankCardNumber)
209 | XCTAssertTrue("4485383550284604".isValidBankCardNumber)
210 | XCTAssertTrue("4532307841419094".isValidBankCardNumber)
211 | XCTAssertTrue("4716014929481859".isValidBankCardNumber)
212 | XCTAssertTrue("4539677496449015".isValidBankCardNumber)
213 | XCTAssertFalse("4129939187355598".isValidBankCardNumber)
214 | XCTAssertFalse("4485383550184604".isValidBankCardNumber)
215 | XCTAssertFalse("4532307741419094".isValidBankCardNumber)
216 | XCTAssertFalse("4716014929401859".isValidBankCardNumber)
217 | XCTAssertFalse("4539672496449015".isValidBankCardNumber)
218 | //Master
219 | XCTAssertTrue("5454422955385717".isValidBankCardNumber)
220 | XCTAssertTrue("5582087594680466".isValidBankCardNumber)
221 | XCTAssertTrue("5485727655082288".isValidBankCardNumber)
222 | XCTAssertTrue("5523335560550243".isValidBankCardNumber)
223 | XCTAssertTrue("5128888281063960".isValidBankCardNumber)
224 | XCTAssertFalse("5454452295585717".isValidBankCardNumber)
225 | XCTAssertFalse("5582087594683466".isValidBankCardNumber)
226 | XCTAssertFalse("5487727655082288".isValidBankCardNumber)
227 | XCTAssertFalse("5523335500550243".isValidBankCardNumber)
228 | XCTAssertFalse("5128888221063960".isValidBankCardNumber)
229 | //Discover
230 | XCTAssertTrue("6011574229193527".isValidBankCardNumber)
231 | XCTAssertTrue("6011908281701522".isValidBankCardNumber)
232 | XCTAssertTrue("6011638416335074".isValidBankCardNumber)
233 | XCTAssertTrue("6011454315529985".isValidBankCardNumber)
234 | XCTAssertTrue("6011123583544386".isValidBankCardNumber)
235 | XCTAssertFalse("6011574229193127".isValidBankCardNumber)
236 | XCTAssertFalse("6031908281701522".isValidBankCardNumber)
237 | XCTAssertFalse("6011638416335054".isValidBankCardNumber)
238 | XCTAssertFalse("6011454316529985".isValidBankCardNumber)
239 | XCTAssertFalse("6011123581544386".isValidBankCardNumber)
240 | //American Express
241 | XCTAssertTrue("348570250878868".isValidBankCardNumber)
242 | XCTAssertTrue("341869994762900".isValidBankCardNumber)
243 | XCTAssertTrue("371040610543651".isValidBankCardNumber)
244 | XCTAssertTrue("341507151650399".isValidBankCardNumber)
245 | XCTAssertTrue("371673921387168".isValidBankCardNumber)
246 | XCTAssertFalse("348570250872868".isValidBankCardNumber)
247 | XCTAssertFalse("341669994762900".isValidBankCardNumber)
248 | XCTAssertFalse("371040610573651".isValidBankCardNumber)
249 | XCTAssertFalse("341557151650399".isValidBankCardNumber)
250 | XCTAssertFalse("371673901387168".isValidBankCardNumber)
251 | }
252 |
253 | func test_westernArabicNumeralsOnly_returnsExpected() {
254 | XCTAssertEqual("12jjsd32m2mk".westernArabicNumeralsOnly, "12322")
255 | XCTAssertEqual("asdfafaf".westernArabicNumeralsOnly, "")
256 | }
257 |
258 | func test_containsOnlyDigits_returnsExpected() {
259 | XCTAssertTrue("12312312".containsOnlyDigits)
260 | XCTAssertFalse("31231fds12".containsOnlyDigits)
261 | XCTAssertFalse("^&213213".containsOnlyDigits)
262 | }
263 |
264 | func test_containsOnlyLetters_returnsExpected() {
265 | XCTAssertTrue("afadsfafadf".containsOnlyLetters)
266 | XCTAssertTrue("afsAADFAvsffafaf".containsOnlyLetters)
267 | XCTAssertFalse("12kl23lkl32lkl".containsOnlyLetters)
268 | XCTAssertFalse("&*(iojoasf".containsOnlyLetters)
269 | }
270 |
271 | func test_isAlphanumeric_returnsExpected() {
272 | XCTAssertTrue("12312312".isAlphanumeric)
273 | XCTAssertTrue("31231fds12".isAlphanumeric)
274 | XCTAssertTrue("31231ADFADSFAfds12".isAlphanumeric)
275 | XCTAssertFalse("^&213213".isAlphanumeric)
276 | }
277 |
278 | func test_condensedWhitespace_returnsExpected() {
279 | XCTAssertEqual("Hello World.\nHello!".condensedWhitespace, "Hello World. Hello!")
280 | XCTAssertEqual(" 3432 fadf afdf ".condensedWhitespace, "3432 fadf afdf")
281 | }
282 |
283 | }
284 |
285 |
--------------------------------------------------------------------------------
/Tests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Tests/TestTypes/Extensions/Data+File.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Data+File.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/8.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 |
12 | extension Data {
13 |
14 | public static func fromFile(fileName: String,
15 | extensionName: String? = nil,
16 | file: StaticString = #file,
17 | line: UInt = #line) throws -> Data {
18 |
19 | let bundle = Bundle(for: TestBundleClass.self)
20 | let url = try XCTUnwrap(bundle.url(forResource: fileName, withExtension: extensionName),
21 | "Unable to find \(fileName).\(extensionName ?? ""). Did you add it to the tests?",
22 | file: file, line: line)
23 | return try Data(contentsOf: url)
24 | }
25 | }
26 |
27 | private class TestBundleClass { }
28 |
--------------------------------------------------------------------------------
/Tests/TestTypes/Models/CodableModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CodableModel.swift
3 | // SwiftWingsTests
4 | //
5 | // Created by leacode on 2020/1/20.
6 | // Copyright © 2020 Leacode. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct CodableModel: Codable {
12 | let name: String
13 | let number: Int
14 | let date: Date
15 | let price: Double
16 | }
17 |
18 | class SomeClass { }
19 |
--------------------------------------------------------------------------------