├── .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 | ![Swift5.0](https://img.shields.io/badge/swift-5.0-blue.svg) 4 | [![Version](https://img.shields.io/cocoapods/v/SwiftWings.svg?style=flat)](https://cocoapods.org/pods/SwiftWings) 5 | [![License](https://img.shields.io/cocoapods/l/SwiftWings.svg?style=flat)](https://github.com/leacode/SwiftWings/blob/master/LICENSE) 6 | [![Platform](https://img.shields.io/cocoapods/p/SwiftWings.svg?style=flat)](https://cocoapods.org/pods/SwiftWings) 7 | [![Codecov](https://codecov.io/gh/leacode/SwiftWings/branch/master/graph/badge.svg)](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 | --------------------------------------------------------------------------------