#if canImport(Foundation)
import Foundation
#endif
#if canImport(UIKit)
import UIKit
#endif
#if canImport(AppKit)
import AppKit
#endif
#if canImport(CoreGraphics)
import CoreGraphics
#endif
public extension String{
func as_bytes() -> Array<UInt8>{
Array(self.utf8)
}
func len() -> Int{
self.count
}
func is_empty() -> Bool{
self.count == 0
}
func chars() -> Array<Element>{
Array(self)
}
func split(by:String) -> [String]{
self.components(separatedBy: by)
}
mutating func push(_ s:String){
self.append(s)
}
mutating func clear(){
self = ""
}
mutating func remove(at index:Int)->Element{
let removeIndex = self.index(self.startIndex, offsetBy: index)
let removed = self.remove(at: removeIndex)
return removed
}
mutating func replace(from:String,to:String)->String{
self.replacingOccurrences(of:from, with: to)
}
func trim()->String{
self.trimmingCharacters(in: .whitespacesAndNewlines)
}
func to_uppercase()->String{
self.uppercased()
}
func to_lowercase()->String{
self.lowercased()
}
func repeat_string(count:Int)->String{
String(repeating: self, count: count)
}
func rfind(_ s:Character)->Optional<Int>{
if let index = self.firstIndex(of: s) {
let result = self.distance(from: self.startIndex, to: index)
return result
}else{
return nil
}
}
func lfind(_ s:Character)->Optional<Int>{
if let index = self.lastIndex(of: s) {
let result = self.distance(from: self.startIndex, to: index)
return result
}else{
return nil
}
}
func base64_encode()->String?{
let plainData = self.data(using: .utf8)
return plainData?.base64EncodedString()
}
func base64_decode()->String?{
if let data = Data(base64Encoded: self,
options: .ignoreUnknownCharacters) {
return String(data: data, encoding: .utf8)
}
let remainder = count % 4
var padding = ""
if remainder > 0 {
padding = String(repeating: "=", count: 4 - remainder)
}
guard let data = Data(base64Encoded: self + padding,
options: .ignoreUnknownCharacters) else { return nil }
return String(data: data, encoding: .utf8)
}
func to_url()-> URL? {
return URL(string: self)
}
func urlDecoded()-> String {
return removingPercentEncoding ?? self
}
func urlEncoded()-> String {
return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
}
func lines() -> [String] {
var result = [String]()
enumerateLines { line, _ in
result.append(line)
}
return result
}
func contains(_ string: String, caseSensitive: Bool = true) -> Bool {
if !caseSensitive {
return range(of: string, options: .caseInsensitive) != nil
}
return range(of: string) != nil
}
func words() -> [String] {
let characterSet = CharacterSet.whitespacesAndNewlines.union(.punctuationCharacters)
let comps = components(separatedBy: characterSet)
return comps.filter { !$0.isEmpty }
}
/// SwifterSwift: Safely subscript string with index.
///
/// "Hello World!"[safe: 3] -> "l"
/// "Hello World!"[safe: 20] -> nil
///
/// - Parameter index: index.
subscript(safe index: Int) -> Character? {
guard index >= 0, index < count else { return nil }
return self[self.index(startIndex, offsetBy: index)]
}
#if os(iOS) || os(macOS)
/// SwifterSwift: Copy string to global pasteboard.
///
/// "SomeText".copyToPasteboard() // copies "SomeText" to pasteboard
///
func copyToPasteboard() {
#if os(iOS)
UIPasteboard.general.string = self
#elseif os(macOS)
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(self, forType: .string)
#endif
}
#endif
#if os(iOS) || os(tvOS)
/// SwifterSwift: Check if the given string spelled correctly.
var isSpelledCorrectly: Bool {
let checker = UITextChecker()
let range = NSRange(startIndex..<endIndex, in: self)
let misspelledRange = checker.rangeOfMisspelledWord(
in: self,
range: range,
startingAt: 0,
wrap: false,
language: Locale.preferredLanguages.first ?? "en")
return misspelledRange.location == NSNotFound
}
#endif
}
public extension String {
/// SwifterSwift: Array of characters of a string.
var charactersArray: [Character] {
return Array(self)
}
#if canImport(Foundation)
/// SwifterSwift: CamelCase of string.
///
/// "sOme vAriable naMe".camelCased -> "someVariableName"
///
var camelCased: String {
let source = lowercased()
let first = source[..<source.index(after: source.startIndex)]
if source.contains(" ") {
let connected = source.capitalized.replacingOccurrences(of: " ", with: "")
let camel = connected.replacingOccurrences(of: "\n", with: "")
let rest = String(camel.dropFirst())
return first + rest
}
let rest = String(source.dropFirst())
return first + rest
}
#endif
/// SwifterSwift: Check if string contains one or more emojis.
///
/// "Hello 😀".containEmoji -> true
///
var containEmoji: Bool {
// http://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji
for scalar in unicodeScalars {
switch scalar.value {
case 0x1F600...0x1F64F, // Emoticons
0x1F300...0x1F5FF, // Misc Symbols and Pictographs
0x1F680...0x1F6FF, // Transport and Map
0x1F1E6...0x1F1FF, // Regional country flags
0x2600...0x26FF, // Misc symbols
0x2700...0x27BF, // Dingbats
0xE0020...0xE007F, // Tags
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
127_000...127_600, // Various asian characters
65024...65039, // Variation selector
9100...9300, // Misc items
8400...8447: // Combining Diacritical Marks for Symbols
return true
default:
continue
}
}
return false
}
/// SwifterSwift: First character of string (if applicable).
///
/// "Hello".firstCharacterAsString -> Optional("H")
/// "".firstCharacterAsString -> nil
///
var firstCharacterAsString: String? {
guard let first = first else { return nil }
return String(first)
}
/// SwifterSwift: Check if string contains one or more letters.
///
/// "123abc".hasLetters -> true
/// "123".hasLetters -> false
///
var hasLetters: Bool {
return rangeOfCharacter(from: .letters, options: .numeric, range: nil) != nil
}
/// SwifterSwift: Check if string contains one or more numbers.
///
/// "abcd".hasNumbers -> false
/// "123abc".hasNumbers -> true
///
var hasNumbers: Bool {
return rangeOfCharacter(from: .decimalDigits, options: .literal, range: nil) != nil
}
/// SwifterSwift: Check if string contains only letters.
///
/// "abc".isAlphabetic -> true
/// "123abc".isAlphabetic -> false
///
var isAlphabetic: Bool {
let hasLetters = rangeOfCharacter(from: .letters, options: .numeric, range: nil) != nil
let hasNumbers = rangeOfCharacter(from: .decimalDigits, options: .literal, range: nil) != nil
return hasLetters && !hasNumbers
}
/// SwifterSwift: Check if string contains at least one letter and one number.
///
/// // useful for passwords
/// "123abc".isAlphaNumeric -> true
/// "abc".isAlphaNumeric -> false
///
var isAlphaNumeric: Bool {
let hasLetters = rangeOfCharacter(from: .letters, options: .numeric, range: nil) != nil
let hasNumbers = rangeOfCharacter(from: .decimalDigits, options: .literal, range: nil) != nil
let comps = components(separatedBy: .alphanumerics)
return comps.joined(separator: "").count == 0 && hasLetters && hasNumbers
}
/// SwifterSwift: Check if string is palindrome.
///
/// "abcdcba".isPalindrome -> true
/// "Mom".isPalindrome -> true
/// "A man a plan a canal, Panama!".isPalindrome -> true
/// "Mama".isPalindrome -> false
///
var isPalindrome: Bool {
let letters = filter { $0.isLetter }
guard !letters.isEmpty else { return false }
let midIndex = letters.index(letters.startIndex, offsetBy: letters.count / 2)
let firstHalf = letters[letters.startIndex..<midIndex]
let secondHalf = letters[midIndex..<letters.endIndex].reversed()
return !zip(firstHalf, secondHalf).contains(where: { $0.lowercased() != $1.lowercased() })
}
#if canImport(Foundation)
/// SwifterSwift: Check if string is valid email format.
///
/// - Note: Note that this property does not validate the email address against an email server. It merely attempts to determine whether its format is suitable for an email address.
///
/// "john@doe.com".isValidEmail -> true
///
var isValidEmail: Bool {
// http://emailregex.com/
let regex =
"^(?:[\\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])+)\\])$"
return range(of: regex, options: .regularExpression, range: nil, locale: nil) != nil
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Check if string is a valid URL.
///
/// "https://google.com".isValidUrl -> true
///
var isValidUrl: Bool {
return URL(string: self) != nil
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Check if string is a valid schemed URL.
///
/// "https://google.com".isValidSchemedUrl -> true
/// "google.com".isValidSchemedUrl -> false
///
var isValidSchemedUrl: Bool {
guard let url = URL(string: self) else { return false }
return url.scheme != nil
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Check if string is a valid https URL.
///
/// "https://google.com".isValidHttpsUrl -> true
///
var isValidHttpsUrl: Bool {
guard let url = URL(string: self) else { return false }
return url.scheme == "https"
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Check if string is a valid http URL.
///
/// "http://google.com".isValidHttpUrl -> true
///
var isValidHttpUrl: Bool {
guard let url = URL(string: self) else { return false }
return url.scheme == "http"
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Check if string is a valid file URL.
///
/// "file://Documents/file.txt".isValidFileUrl -> true
///
var isValidFileUrl: Bool {
return URL(string: self)?.isFileURL ?? false
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Check if string is a valid Swift number. Note: In North America, "." is the decimal separator, while in many parts of Europe "," is used.
///
/// "123".isNumeric -> true
/// "1.3".isNumeric -> true (en_US)
/// "1,3".isNumeric -> true (fr_FR)
/// "abc".isNumeric -> false
///
var isNumeric: Bool {
let scanner = Scanner(string: self)
scanner.locale = NSLocale.current
#if os(Linux) || targetEnvironment(macCatalyst)
return scanner.scanDecimal() != nil && scanner.isAtEnd
#else
return scanner.scanDecimal(nil) && scanner.isAtEnd
#endif
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Check if string only contains digits.
///
/// "123".isDigits -> true
/// "1.3".isDigits -> false
/// "abc".isDigits -> false
///
var isDigits: Bool {
return CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: self))
}
#endif
/// SwifterSwift: Last character of string (if applicable).
///
/// "Hello".lastCharacterAsString -> Optional("o")
/// "".lastCharacterAsString -> nil
///
var lastCharacterAsString: String? {
guard let last = last else { return nil }
return String(last)
}
#if canImport(Foundation)
/// SwifterSwift: Latinized string.
///
/// "Hèllö Wórld!".latinized -> "Hello World!"
///
var latinized: String {
return folding(options: .diacriticInsensitive, locale: Locale.current)
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Bool value from string (if applicable).
///
/// "1".bool -> true
/// "False".bool -> false
/// "Hello".bool = nil
///
var bool: Bool? {
let selfLowercased = trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
switch selfLowercased {
case "true", "yes", "1":
return true
case "false", "no", "0":
return false
default:
return nil
}
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Date object from "yyyy-MM-dd" formatted string.
///
/// "2007-06-29".date -> Optional(Date)
///
var date: Date? {
let selfLowercased = trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
let formatter = DateFormatter()
formatter.timeZone = TimeZone.current
formatter.dateFormat = "yyyy-MM-dd"
return formatter.date(from: selfLowercased)
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Date object from "yyyy-MM-dd HH:mm:ss" formatted string.
///
/// "2007-06-29 14:23:09".dateTime -> Optional(Date)
///
var dateTime: Date? {
let selfLowercased = trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
let formatter = DateFormatter()
formatter.timeZone = TimeZone.current
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return formatter.date(from: selfLowercased)
}
#endif
/// SwifterSwift: Integer value from string (if applicable).
///
/// "101".int -> 101
///
var int: Int? {
return Int(self)
}
/// SwifterSwift: Lorem ipsum string of given length.
///
/// - Parameter length: number of characters to limit lorem ipsum to (default is 445 - full lorem ipsum).
/// - Returns: Lorem ipsum dolor sit amet... string.
static func loremIpsum(ofLength length: Int = 445) -> String {
guard length > 0 else { return "" }
// https://www.lipsum.com/
let loremIpsum = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
"""
if loremIpsum.count > length {
return String(loremIpsum[loremIpsum.startIndex..<loremIpsum.index(loremIpsum.startIndex, offsetBy: length)])
}
return loremIpsum
}
#if canImport(Foundation)
/// SwifterSwift: URL from string (if applicable).
///
/// "https://google.com".url -> URL(string: "https://google.com")
/// "not url".url -> nil
///
var url: URL? {
return URL(string: self)
}
#endif
#if canImport(Foundation)
/// SwifterSwift: String with no spaces or new lines in beginning and end.
///
/// " hello \n".trimmed -> "hello"
///
var trimmed: String {
return trimmingCharacters(in: .whitespacesAndNewlines)
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Readable string from a URL string.
///
/// "it's%20easy%20to%20decode%20strings".urlDecoded -> "it's easy to decode strings"
///
var urlDecoded: String {
return removingPercentEncoding ?? self
}
#endif
#if canImport(Foundation)
/// SwifterSwift: URL escaped string.
///
/// "it's easy to encode strings".urlEncoded -> "it's%20easy%20to%20encode%20strings"
///
var urlEncoded: String {
return addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Escaped string for inclusion in a regular expression pattern.
///
/// "hello ^$ there" -> "hello \\^\\$ there"
///
var regexEscaped: String {
return NSRegularExpression.escapedPattern(for: self)
}
#endif
#if canImport(Foundation)
/// SwifterSwift: String without spaces and new lines.
///
/// " \n Swifter \n Swift ".withoutSpacesAndNewLines -> "SwifterSwift"
///
var withoutSpacesAndNewLines: String {
return replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "\n", with: "")
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Check if the given string contains only white spaces.
var isWhitespace: Bool {
return trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
#endif
#if os(iOS) || os(tvOS)
/// SwifterSwift: Check if the given string spelled correctly.
var isSpelledCorrectly: Bool {
let checker = UITextChecker()
let range = NSRange(startIndex..<endIndex, in: self)
let misspelledRange = checker.rangeOfMisspelledWord(
in: self,
range: range,
startingAt: 0,
wrap: false,
language: Locale.preferredLanguages.first ?? "en")
return misspelledRange.location == NSNotFound
}
#endif
}
// MARK: - Methods
public extension String {
#if canImport(Foundation)
/// SwifterSwift: Float value from string (if applicable).
///
/// - Parameter locale: Locale (default is Locale.current)
/// - Returns: Optional Float value from given string.
func float(locale: Locale = .current) -> Float? {
let formatter = NumberFormatter()
formatter.locale = locale
formatter.allowsFloats = true
return formatter.number(from: self)?.floatValue
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Double value from string (if applicable).
///
/// - Parameter locale: Locale (default is Locale.current)
/// - Returns: Optional Double value from given string.
func double(locale: Locale = .current) -> Double? {
let formatter = NumberFormatter()
formatter.locale = locale
formatter.allowsFloats = true
return formatter.number(from: self)?.doubleValue
}
#endif
#if canImport(CoreGraphics) && canImport(Foundation)
/// SwifterSwift: CGFloat value from string (if applicable).
///
/// - Parameter locale: Locale (default is Locale.current)
/// - Returns: Optional CGFloat value from given string.
func cgFloat(locale: Locale = .current) -> CGFloat? {
let formatter = NumberFormatter()
formatter.locale = locale
formatter.allowsFloats = true
return formatter.number(from: self) as? CGFloat
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Array of strings separated by new lines.
///
/// "Hello\ntest".lines() -> ["Hello", "test"]
///
/// - Returns: Strings separated by new lines.
func lines() -> [String] {
var result = [String]()
enumerateLines { line, _ in
result.append(line)
}
return result
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Returns a localized string, with an optional comment for translators.
///
/// "Hello world".localized() -> Hallo Welt
///
/// - Parameter comment: Optional comment for translators.
/// - Returns: Localized string.
func localized(comment: String = "") -> String {
return NSLocalizedString(self, comment: comment)
}
#endif
/// SwifterSwift: The most common character in string.
///
/// "This is a test, since e is appearing everywhere e should be the common character".mostCommonCharacter() -> "e"
///
/// - Returns: The most common character.
func mostCommonCharacter() -> Character? {
let mostCommon = withoutSpacesAndNewLines.reduce(into: [Character: Int]()) {
let count = $0[$1] ?? 0
$0[$1] = count + 1
}.max { $0.1 < $1.1 }?.key
return mostCommon
}
/// SwifterSwift: Array with unicodes for all characters in a string.
///
/// "SwifterSwift".unicodeArray() -> [83, 119, 105, 102, 116, 101, 114, 83, 119, 105, 102, 116]
///
/// - Returns: The unicodes for all characters in a string.
func unicodeArray() -> [Int] {
return unicodeScalars.map { Int($0.value) }
}
#if canImport(Foundation)
/// SwifterSwift: an array of all words in a string.
///
/// "Swift is amazing".words() -> ["Swift", "is", "amazing"]
///
/// - Returns: The words contained in a string.
func words() -> [String] {
// https://stackoverflow.com/questions/42822838
let characterSet = CharacterSet.whitespacesAndNewlines.union(.punctuationCharacters)
let comps = components(separatedBy: characterSet)
return comps.filter { !$0.isEmpty }
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Count of words in a string.
///
/// "Swift is amazing".wordsCount() -> 3
///
/// - Returns: The count of words contained in a string.
func wordCount() -> Int {
// https://stackoverflow.com/questions/42822838
let characterSet = CharacterSet.whitespacesAndNewlines.union(.punctuationCharacters)
let comps = components(separatedBy: characterSet)
let words = comps.filter { !$0.isEmpty }
return words.count
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Transforms the string into a slug string.
///
/// "Swift is amazing".toSlug() -> "swift-is-amazing"
///
/// - Returns: The string in slug format.
func toSlug() -> String {
let lowercased = self.lowercased()
let latinized = lowercased.folding(options: .diacriticInsensitive, locale: Locale.current)
let withDashes = latinized.replacingOccurrences(of: " ", with: "-")
let alphanumerics = NSCharacterSet.alphanumerics
var filtered = withDashes.filter {
guard String($0) != "-" else { return true }
guard String($0) != "&" else { return true }
return String($0).rangeOfCharacter(from: alphanumerics) != nil
}
while filtered.lastCharacterAsString == "-" {
filtered = String(filtered.dropLast())
}
while filtered.firstCharacterAsString == "-" {
filtered = String(filtered.dropFirst())
}
return filtered.replacingOccurrences(of: "--", with: "-")
}
#endif
/// SwifterSwift: Safely subscript string with index.
///
/// "Hello World!"[safe: 3] -> "l"
/// "Hello World!"[safe: 20] -> nil
///
/// - Parameter index: index.
subscript(safe index: Int) -> Character? {
guard index >= 0, index < count else { return nil }
return self[self.index(startIndex, offsetBy: index)]
}
/// SwifterSwift: Safely subscript string within a given range.
///
/// "Hello World!"[safe: 6..<11] -> "World"
/// "Hello World!"[safe: 21..<110] -> nil
///
/// "Hello World!"[safe: 6...11] -> "World!"
/// "Hello World!"[safe: 21...110] -> nil
///
/// - Parameter range: Range expression.
subscript<R>(safe range: R) -> String? where R: RangeExpression, R.Bound == Int {
let range = range.relative(to: Int.min..<Int.max)
guard range.lowerBound >= 0,
let lowerIndex = index(startIndex, offsetBy: range.lowerBound, limitedBy: endIndex),
let upperIndex = index(startIndex, offsetBy: range.upperBound, limitedBy: endIndex) else {
return nil
}
return String(self[lowerIndex..<upperIndex])
}
#if os(iOS) || os(macOS)
/// SwifterSwift: Copy string to global pasteboard.
///
/// "SomeText".copyToPasteboard() // copies "SomeText" to pasteboard
///
func copyToPasteboard() {
#if os(iOS)
UIPasteboard.general.string = self
#elseif os(macOS)
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(self, forType: .string)
#endif
}
#endif
/// SwifterSwift: Converts string format to CamelCase.
///
/// var str = "sOme vaRiabLe Name"
/// str.camelize()
/// print(str) // prints "someVariableName"
///
@discardableResult
mutating func camelize() -> String {
let source = lowercased()
let first = source[..<source.index(after: source.startIndex)]
if source.contains(" ") {
let connected = source.capitalized.replacingOccurrences(of: " ", with: "")
let camel = connected.replacingOccurrences(of: "\n", with: "")
let rest = String(camel.dropFirst())
self = first + rest
return self
}
let rest = String(source.dropFirst())
self = first + rest
return self
}
/// SwifterSwift: First character of string uppercased(if applicable) while keeping the original string.
///
/// "hello world".firstCharacterUppercased() -> "Hello world"
/// "".firstCharacterUppercased() -> ""
///
mutating func firstCharacterUppercased() {
guard let first = first else { return }
self = String(first).uppercased() + dropFirst()
}
/// SwifterSwift: Check if string contains only unique characters.
///
func hasUniqueCharacters() -> Bool {
guard count > 0 else { return false }
var uniqueChars = Set<String>()
for char in self {
if uniqueChars.contains(String(char)) { return false }
uniqueChars.insert(String(char))
}
return true
}
#if canImport(Foundation)
/// SwifterSwift: Check if string contains one or more instance of substring.
///
/// "Hello World!".contain("O") -> false
/// "Hello World!".contain("o", caseSensitive: false) -> true
///
/// - Parameters:
/// - string: substring to search for.
/// - caseSensitive: set true for case sensitive search (default is true).
/// - Returns: true if string contains one or more instance of substring.
func contains(_ string: String, caseSensitive: Bool = true) -> Bool {
if !caseSensitive {
return range(of: string, options: .caseInsensitive) != nil
}
return range(of: string) != nil
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Count of substring in string.
///
/// "Hello World!".count(of: "o") -> 2
/// "Hello World!".count(of: "L", caseSensitive: false) -> 3
///
/// - Parameters:
/// - string: substring to search for.
/// - caseSensitive: set true for case sensitive search (default is true).
/// - Returns: count of appearance of substring in string.
func count(of string: String, caseSensitive: Bool = true) -> Int {
if !caseSensitive {
return lowercased().components(separatedBy: string.lowercased()).count - 1
}
return components(separatedBy: string).count - 1
}
#endif
/// SwifterSwift: Check if string ends with substring.
///
/// "Hello World!".ends(with: "!") -> true
/// "Hello World!".ends(with: "WoRld!", caseSensitive: false) -> true
///
/// - Parameters:
/// - suffix: substring to search if string ends with.
/// - caseSensitive: set true for case sensitive search (default is true).
/// - Returns: true if string ends with substring.
func ends(with suffix: String, caseSensitive: Bool = true) -> Bool {
if !caseSensitive {
return lowercased().hasSuffix(suffix.lowercased())
}
return hasSuffix(suffix)
}
#if canImport(Foundation)
/// SwifterSwift: Latinize string.
///
/// var str = "Hèllö Wórld!"
/// str.latinize()
/// print(str) // prints "Hello World!"
///
@discardableResult
mutating func latinize() -> String {
self = folding(options: .diacriticInsensitive, locale: Locale.current)
return self
}
#endif
/// SwifterSwift: Random string of given length.
///
/// String.random(ofLength: 18) -> "u7MMZYvGo9obcOcPj8"
///
/// - Parameter length: number of characters in string.
/// - Returns: random string of given length.
static func random(ofLength length: Int) -> String {
guard length > 0 else { return "" }
let base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var randomString = ""
for _ in 1...length {
randomString.append(base.randomElement()!)
}
return randomString
}
/// SwifterSwift: Reverse string.
@discardableResult
mutating func reverse() -> String {
let chars: [Character] = reversed()
self = String(chars)
return self
}
/// SwifterSwift: Sliced string from a start index with length.
///
/// "Hello World".slicing(from: 6, length: 5) -> "World"
///
/// - Parameters:
/// - index: string index the slicing should start from.
/// - length: amount of characters to be sliced after given index.
/// - Returns: sliced substring of length number of characters (if applicable) (example: "Hello World".slicing(from: 6, length: 5) -> "World").
func slicing(from index: Int, length: Int) -> String? {
guard length >= 0, index >= 0, index < count else { return nil }
guard index.advanced(by: length) <= count else {
return self[safe: index..<count]
}
guard length > 0 else { return "" }
return self[safe: index..<index.advanced(by: length)]
}
/// SwifterSwift: Slice given string from a start index with length (if applicable).
///
/// var str = "Hello World"
/// str.slice(from: 6, length: 5)
/// print(str) // prints "World"
///
/// - Parameters:
/// - index: string index the slicing should start from.
/// - length: amount of characters to be sliced after given index.
@discardableResult
mutating func slice(from index: Int, length: Int) -> String {
if let str = slicing(from: index, length: length) {
self = String(str)
}
return self
}
/// SwifterSwift: Slice given string from a start index to an end index (if applicable).
///
/// var str = "Hello World"
/// str.slice(from: 6, to: 11)
/// print(str) // prints "World"
///
/// - Parameters:
/// - start: string index the slicing should start from.
/// - end: string index the slicing should end at.
@discardableResult
mutating func slice(from start: Int, to end: Int) -> String {
guard end >= start else { return self }
if let str = self[safe: start..<end] {
self = str
}
return self
}
/// SwifterSwift: Slice given string from a start index (if applicable).
///
/// var str = "Hello World"
/// str.slice(at: 6)
/// print(str) // prints "World"
///
/// - Parameter index: string index the slicing should start from.
@discardableResult
mutating func slice(at index: Int) -> String {
guard index < count else { return self }
if let str = self[safe: index..<count] {
self = str
}
return self
}
/// SwifterSwift: Check if string starts with substring.
///
/// "hello World".starts(with: "h") -> true
/// "hello World".starts(with: "H", caseSensitive: false) -> true
///
/// - Parameters:
/// - suffix: substring to search if string starts with.
/// - caseSensitive: set true for case sensitive search (default is true).
/// - Returns: true if string starts with substring.
func starts(with prefix: String, caseSensitive: Bool = true) -> Bool {
if !caseSensitive {
return lowercased().hasPrefix(prefix.lowercased())
}
return hasPrefix(prefix)
}
#if canImport(Foundation)
/// SwifterSwift: Date object from string of date format.
///
/// "2017-01-15".date(withFormat: "yyyy-MM-dd") -> Date set to Jan 15, 2017
/// "not date string".date(withFormat: "yyyy-MM-dd") -> nil
///
/// - Parameter format: date format.
/// - Returns: Date object from string (if applicable).
func date(withFormat format: String) -> Date? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.date(from: self)
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Removes spaces and new lines in beginning and end of string.
///
/// var str = " \n Hello World \n\n\n"
/// str.trim()
/// print(str) // prints "Hello World"
///
@discardableResult
mutating func trim() -> String {
self = trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
return self
}
#endif
/// SwifterSwift: Truncate string (cut it to a given number of characters).
///
/// var str = "This is a very long sentence"
/// str.truncate(toLength: 14)
/// print(str) // prints "This is a very..."
///
/// - Parameters:
/// - toLength: maximum number of characters before cutting.
/// - trailing: string to add at the end of truncated string (default is "...").
@discardableResult
mutating func truncate(toLength length: Int, trailing: String? = "...") -> String {
guard length > 0 else { return self }
if count > length {
self = self[startIndex..<index(startIndex, offsetBy: length)] + (trailing ?? "")
}
return self
}
/// SwifterSwift: Truncated string (limited to a given number of characters).
///
/// "This is a very long sentence".truncated(toLength: 14) -> "This is a very..."
/// "Short sentence".truncated(toLength: 14) -> "Short sentence"
///
/// - Parameters:
/// - toLength: maximum number of characters before cutting.
/// - trailing: string to add at the end of truncated string.
/// - Returns: truncated string (this is an extr...).
func truncated(toLength length: Int, trailing: String? = "...") -> String {
guard 0..<count ~= length else { return self }
return self[startIndex..<index(startIndex, offsetBy: length)] + (trailing ?? "")
}
#if canImport(Foundation)
/// SwifterSwift: Convert URL string to readable string.
///
/// var str = "it's%20easy%20to%20decode%20strings"
/// str.urlDecode()
/// print(str) // prints "it's easy to decode strings"
///
@discardableResult
mutating func urlDecode() -> String {
if let decoded = removingPercentEncoding {
self = decoded
}
return self
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Escape string.
///
/// var str = "it's easy to encode strings"
/// str.urlEncode()
/// print(str) // prints "it's%20easy%20to%20encode%20strings"
///
@discardableResult
mutating func urlEncode() -> String {
if let encoded = addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) {
self = encoded
}
return self
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Verify if string matches the regex pattern.
///
/// - Parameter pattern: Pattern to verify.
/// - Returns: `true` if string matches the pattern.
func matches(pattern: String) -> Bool {
return range(of: pattern, options: .regularExpression, range: nil, locale: nil) != nil
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Verify if string matches the regex.
///
/// - Parameters:
/// - regex: Regex to verify.
/// - options: The matching options to use.
/// - Returns: `true` if string matches the regex.
func matches(regex: NSRegularExpression, options: NSRegularExpression.MatchingOptions = []) -> Bool {
let range = NSRange(startIndex..<endIndex, in: self)
return regex.firstMatch(in: self, options: options, range: range) != nil
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Overload Swift's 'contains' operator for matching regex pattern.
///
/// - Parameters:
/// - lhs: String to check on regex pattern.
/// - rhs: Regex pattern to match against.
/// - Returns: true if string matches the pattern.
static func ~= (lhs: String, rhs: String) -> Bool {
return lhs.range(of: rhs, options: .regularExpression) != nil
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Overload Swift's 'contains' operator for matching regex.
///
/// - Parameters:
/// - lhs: String to check on regex.
/// - rhs: Regex to match against.
/// - Returns: `true` if there is at least one match for the regex in the string.
static func ~= (lhs: String, rhs: NSRegularExpression) -> Bool {
let range = NSRange(lhs.startIndex..<lhs.endIndex, in: lhs)
return rhs.firstMatch(in: lhs, range: range) != nil
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Returns a new string in which all occurrences of a regex in a specified range of the receiver are replaced by the template.
/// - Parameters:
/// - regex Regex to replace.
/// - template: The template to replace the regex.
/// - options: The matching options to use
/// - searchRange: The range in the receiver in which to search.
/// - Returns: A new string in which all occurrences of regex in searchRange of the receiver are replaced by template.
func replacingOccurrences(
of regex: NSRegularExpression,
with template: String,
options: NSRegularExpression.MatchingOptions = [],
range searchRange: Range<String.Index>? = nil) -> String {
let range = NSRange(searchRange ?? startIndex..<endIndex, in: self)
return regex.stringByReplacingMatches(in: self, options: options, range: range, withTemplate: template)
}
#endif
/// SwifterSwift: Pad string to fit the length parameter size with another string in the start.
///
/// "hue".padStart(10) -> " hue"
/// "hue".padStart(10, with: "br") -> "brbrbrbhue"
///
/// - Parameters:
/// - length: The target length to pad.
/// - string: Pad string. Default is " ".
@discardableResult
mutating func padStart(_ length: Int, with string: String = " ") -> String {
self = paddingStart(length, with: string)
return self
}
/// SwifterSwift: Returns a string by padding to fit the length parameter size with another string in the start.
///
/// "hue".paddingStart(10) -> " hue"
/// "hue".paddingStart(10, with: "br") -> "brbrbrbhue"
///
/// - Parameters:
/// - length: The target length to pad.
/// - string: Pad string. Default is " ".
/// - Returns: The string with the padding on the start.
func paddingStart(_ length: Int, with string: String = " ") -> String {
guard count < length else { return self }
let padLength = length - count
if padLength < string.count {
return string[string.startIndex..<string.index(string.startIndex, offsetBy: padLength)] + self
} else {
var padding = string
while padding.count < padLength {
padding.append(string)
}
return padding[padding.startIndex..<padding.index(padding.startIndex, offsetBy: padLength)] + self
}
}
/// SwifterSwift: Pad string to fit the length parameter size with another string in the start.
///
/// "hue".padEnd(10) -> "hue "
/// "hue".padEnd(10, with: "br") -> "huebrbrbrb"
///
/// - Parameters:
/// - length: The target length to pad.
/// - string: Pad string. Default is " ".
@discardableResult
mutating func padEnd(_ length: Int, with string: String = " ") -> String {
self = paddingEnd(length, with: string)
return self
}
/// SwifterSwift: Returns a string by padding to fit the length parameter size with another string in the end.
///
/// "hue".paddingEnd(10) -> "hue "
/// "hue".paddingEnd(10, with: "br") -> "huebrbrbrb"
///
/// - Parameters:
/// - length: The target length to pad.
/// - string: Pad string. Default is " ".
/// - Returns: The string with the padding on the end.
func paddingEnd(_ length: Int, with string: String = " ") -> String {
guard count < length else { return self }
let padLength = length - count
if padLength < string.count {
return self + string[string.startIndex..<string.index(string.startIndex, offsetBy: padLength)]
} else {
var padding = string
while padding.count < padLength {
padding.append(string)
}
return self + padding[padding.startIndex..<padding.index(padding.startIndex, offsetBy: padLength)]
}
}
/// SwifterSwift: Removes given prefix from the string.
///
/// "Hello, World!".removingPrefix("Hello, ") -> "World!"
///
/// - Parameter prefix: Prefix to remove from the string.
/// - Returns: The string after prefix removing.
func removingPrefix(_ prefix: String) -> String {
guard hasPrefix(prefix) else { return self }
return String(dropFirst(prefix.count))
}
/// SwifterSwift: Removes given suffix from the string.
///
/// "Hello, World!".removingSuffix(", World!") -> "Hello"
///
/// - Parameter suffix: Suffix to remove from the string.
/// - Returns: The string after suffix removing.
func removingSuffix(_ suffix: String) -> String {
guard hasSuffix(suffix) else { return self }
return String(dropLast(suffix.count))
}
/// SwifterSwift: Adds prefix to the string.
///
/// "www.apple.com".withPrefix("https://") -> "https://www.apple.com"
///
/// - Parameter prefix: Prefix to add to the string.
/// - Returns: The string with the prefix prepended.
func withPrefix(_ prefix: String) -> String {
// https://www.hackingwithswift.com/articles/141/8-useful-swift-extensions
guard !hasPrefix(prefix) else { return self }
return prefix + self
}
}
// MARK: - Initializers
public extension String {
#if canImport(Foundation)
/// SwifterSwift: Create a new string from a base64 string (if applicable).
///
/// String(base64: "SGVsbG8gV29ybGQh") = "Hello World!"
/// String(base64: "hello") = nil
///
/// - Parameter base64: base64 string.
init?(base64: String) {
guard let decodedData = Data(base64Encoded: base64) else { return nil }
guard let str = String(data: decodedData, encoding: .utf8) else { return nil }
self.init(str)
}
#endif
/// SwifterSwift: Create a new random string of given length.
///
/// String(randomOfLength: 10) -> "gY8r3MHvlQ"
///
/// - Parameter length: number of characters in string.
init(randomOfLength length: Int) {
guard length > 0 else {
self.init()
return
}
let base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var randomString = ""
for _ in 1...length {
randomString.append(base.randomElement()!)
}
self = randomString
}
}
#if !os(Linux)
// MARK: - NSAttributedString
public extension String {
#if os(iOS) || os(macOS)
/// SwifterSwift: Bold string.
var bold: NSAttributedString {
return NSMutableAttributedString(
string: self,
attributes: [.font: SFFont.boldSystemFont(ofSize: SFFont.systemFontSize)])
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Underlined string
var underline: NSAttributedString {
return NSAttributedString(string: self, attributes: [.underlineStyle: NSUnderlineStyle.single.rawValue])
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Strikethrough string.
var strikethrough: NSAttributedString {
return NSAttributedString(
string: self,
attributes: [.strikethroughStyle: NSNumber(value: NSUnderlineStyle.single.rawValue as Int)])
}
#endif
#if os(iOS)
/// SwifterSwift: Italic string.
var italic: NSAttributedString {
return NSMutableAttributedString(
string: self,
attributes: [.font: UIFont.italicSystemFont(ofSize: UIFont.systemFontSize)])
}
#endif
#if canImport(AppKit) || canImport(UIKit)
/// SwifterSwift: Add color to string.
///
/// - Parameter color: text color.
/// - Returns: a NSAttributedString versions of string colored with given color.
func colored(with color: SFColor) -> NSAttributedString {
return NSMutableAttributedString(string: self, attributes: [.foregroundColor: color])
}
#endif
}
#endif
// MARK: - Operators
public extension String {
/// SwifterSwift: Repeat string multiple times.
///
/// 'bar' * 3 -> "barbarbar"
///
/// - Parameters:
/// - lhs: string to repeat.
/// - rhs: number of times to repeat character.
/// - Returns: new string with given string repeated n times.
static func * (lhs: String, rhs: Int) -> String {
guard rhs > 0 else { return "" }
return String(repeating: lhs, count: rhs)
}
/// SwifterSwift: Repeat string multiple times.
///
/// 3 * 'bar' -> "barbarbar"
///
/// - Parameters:
/// - lhs: number of times to repeat character.
/// - rhs: string to repeat.
/// - Returns: new string with given string repeated n times.
static func * (lhs: Int, rhs: String) -> String {
guard lhs > 0 else { return "" }
return String(repeating: rhs, count: lhs)
}
}
#if canImport(Foundation)
// MARK: - NSString extensions
public extension String {
/// SwifterSwift: NSString from a string.
var nsString: NSString {
return NSString(string: self)
}
/// SwifterSwift: The full `NSRange` of the string.
var fullNSRange: NSRange { NSRange(startIndex..<endIndex, in: self) }
/// SwifterSwift: NSString lastPathComponent.
var lastPathComponent: String {
return (self as NSString).lastPathComponent
}
/// SwifterSwift: NSString pathExtension.
var pathExtension: String {
return (self as NSString).pathExtension
}
/// SwifterSwift: NSString deletingLastPathComponent.
var deletingLastPathComponent: String {
return (self as NSString).deletingLastPathComponent
}
/// SwifterSwift: NSString deletingPathExtension.
var deletingPathExtension: String {
return (self as NSString).deletingPathExtension
}
/// SwifterSwift: NSString pathComponents.
var pathComponents: [String] {
return (self as NSString).pathComponents
}
/// SwifterSwift: Convert an `NSRange` into `Range<String.Index>`.
/// - Parameter nsRange: The `NSRange` within the receiver.
/// - Returns: The equivalent `Range<String.Index>` of `nsRange` found within the receiving string.
func range(from nsRange: NSRange) -> Range<Index> {
guard let range = Range(nsRange, in: self) else { fatalError("Failed to find range \(nsRange) in \(self)") }
return range
}
/// SwifterSwift: Convert a `Range<String.Index>` into `NSRange`.
/// - Parameter range: The `Range<String.Index>` within the receiver.
/// - Returns: The equivalent `NSRange` of `range` found within the receiving string.
func nsRange(from range: Range<Index>) -> NSRange {
return NSRange(range, in: self)
}
/// SwifterSwift: NSString appendingPathComponent(str: String).
///
/// - Note: This method only works with file paths (not, for example, string representations of URLs.
/// See NSString [appendingPathComponent(_:)](https://developer.apple.com/documentation/foundation/nsstring/1417069-appendingpathcomponent)
/// - Parameter str: the path component to append to the receiver.
/// - Returns: a new string made by appending aString to the receiver, preceded if necessary by a path separator.
func appendingPathComponent(_ str: String) -> String {
return (self as NSString).appendingPathComponent(str)
}
/// SwifterSwift: NSString appendingPathExtension(str: String).
///
/// - Parameter str: The extension to append to the receiver.
/// - Returns: a new string made by appending to the receiver an extension separator followed by ext (if applicable).
func appendingPathExtension(_ str: String) -> String? {
return (self as NSString).appendingPathExtension(str)
}
/// SwifterSwift: Accesses a contiguous subrange of the collection’s elements.
/// - Parameter nsRange: A range of the collection’s indices. The bounds of the range must be valid indices of the collection.
/// - Returns: A slice of the receiving string.
subscript(bounds: NSRange) -> Substring {
guard let range = Range(bounds, in: self) else { fatalError("Failed to find range \(bounds) in \(self)") }
return self[range]
}
}
#endif
// MARK: - String extension
/// This extesion adds some useful functions to String.
public extension String {
// MARK: - Variables
/// Gets the individual characters and puts them in an array as Strings.
var array: [String] {
description.map { String($0) }
}
/// Returns the Float value
var floatValue: Float {
NSString(string: self).floatValue
}
/// Returns the Int value
var intValue: Int {
Int(NSString(string: self).intValue)
}
/// Convert self to a Data.
var dataValue: Data? {
data(using: .utf8)
}
/// Encoded string to Base64.
var base64encoded: String {
guard let data: Data = data(using: .utf8) else {
return ""
}
return data.base64EncodedString()
}
/// Decoded Base64 to string.
var base64decoded: String {
guard let data = Data(base64Encoded: String(self), options: .ignoreUnknownCharacters), let dataString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else {
return ""
}
return String(describing: dataString)
}
/// Encode self to an encoded url string.
var urlEncoded: String? {
addingPercentEncoding(withAllowedCharacters: CharacterSet.urlHostAllowed)
}
/// Returns the localized string from self.
var localized: String {
NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
}
/// Convert the String to a NSNumber.
var numberValue: NSNumber? {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter.number(from: self)
}
// MARK: - Functions
/// Get the character at a given index.
///
/// - Parameter index: The index.
/// - Returns: Returns the character at a given index, starts from 0.
func character(at index: Int) -> Character {
self[self.index(startIndex, offsetBy: index)]
}
/// Returns a new string containing the characters of the String from the one at a given index to the end.
///
/// - Parameter index: The index.
/// - Returns: Returns the substring from index.
func substring(from index: Int) -> String {
String(self[self.index(startIndex, offsetBy: index)...])
}
/// Creates a substring from the given character.
///
/// - Parameter character: The character.
/// - Returns: Returns the substring from character.
func substring(from character: Character) -> String {
let index = self.index(of: character)
guard index > -1 else {
return ""
}
return substring(from: index + 1)
}
/// Returns a new string containing the characters of the String up to, but not including, the one at a given index.
///
/// - Parameter index: The index.
/// - Returns: Returns the substring to index.
func substring(to index: Int) -> String {
guard index <= count else {
return ""
}
return String(self[..<self.index(startIndex, offsetBy: index)])
}
/// Creates a substring to the given character.
///
/// - Parameter character: The character.
/// - Returns: Returns the substring to character.
func substring(to character: Character) -> String {
let index: Int = self.index(of: character)
guard index > -1 else {
return ""
}
return substring(to: index)
}
/// Creates a substring with a given range.
///
/// - Parameter range: The range.
/// - Returns: Returns the string between the range.
func substring(with range: Range<Int>) -> String {
let start = index(startIndex, offsetBy: range.lowerBound)
let end = index(startIndex, offsetBy: range.upperBound)
return String(self[start..<end])
}
/// Creates a substring with a given range.
///
/// - Parameter range: The range.
/// - Returns: Returns the string between the range.
func substring(with range: CountableClosedRange<Int>) -> String {
substring(with: Range(uncheckedBounds: (lower: range.lowerBound, upper: range.upperBound + 1)))
}
/// Returns the index of the given character.
///
/// - Parameter character: The character to search.
/// - Returns: Returns the index of the given character, -1 if not found.
func index(of character: Character) -> Int {
guard let index: Index = firstIndex(of: character) else {
return -1
}
return distance(from: startIndex, to: index)
}
/// Check if self has the given substring in case-sensitiv or case-insensitive.
///
/// - Parameters:
/// - string: The substring to be searched.
/// - caseSensitive: If the search has to be case-sensitive or not.
/// - Returns: Returns true if founded, otherwise false.
func range(of string: String, caseSensitive: Bool = true) -> Bool {
caseSensitive ? (range(of: string) != nil) : (lowercased().range(of: string.lowercased()) != nil)
}
/// Check if self has the given substring in case-sensitiv or case-insensitive.
///
/// - Parameters:
/// - string: The substring to be searched.
/// - caseSensitive: If the search has to be case-sensitive or not.
/// - Returns: Returns true if founded, otherwise false.
func has(_ string: String, caseSensitive: Bool = true) -> Bool {
range(of: string, caseSensitive: caseSensitive)
}
/// Returns the number of occurrences of a String into
///
/// - Parameter string: String of occurrences.
/// - Returns: Returns the number of occurrences of a String into
func occurrences(of string: String, caseSensitive: Bool = true) -> Int {
var string = string
if !caseSensitive {
string = string.lowercased()
}
return lowercased().components(separatedBy: string).count - 1
}
/// Conver self to a capitalized string.
/// Example: "This is a Test" will return "This is a test" and "this is a test" will return "This is a test".
///
/// - Returns: Returns the capitalized sentence string.
func sentenceCapitalizedString() -> String {
guard !isEmpty else {
return ""
}
let uppercase: String = substring(to: 1).uppercased()
let lowercase: String = substring(from: 1).lowercased()
return uppercase + lowercase
}
/// Returns the query parameter string.
///
/// - Parameter parameter: Parameter to be searched.
/// - Returns: Returns the query parameter string.
func queryStringParameter(parameter: String) -> String? {
guard let url = URLComponents(string: self) else {
return nil
}
return url.queryItems?.first { $0.name == parameter }?.value
}
/// Converts the query to a dictionary of parameters.
///
/// - Returns: Returns the dictionary of parameters.
func queryDictionary() -> [String: String] {
var queryStrings: [String: String] = [:]
for pair in self.components(separatedBy: "&") {
let key = pair.components(separatedBy: "=")[0]
let value = pair.components(separatedBy: "=")[1].replacingOccurrences(of: "+", with: " ").removingPercentEncoding ?? ""
queryStrings[key] = value
}
return queryStrings
}
#if !os(Linux)
/// Check if the URL is a valid HTTP URL.
///
/// - Returns: Returns if the URL is a valid HTTP URL
func isURLValid() -> Bool {
let regEx = "((https|http)://)((\\w|-)+)(([.]|[/])((\\w|-)+))?.+"
let predicate = NSPredicate(format: "SELF MATCHES %@", argumentArray: [regEx])
return predicate.evaluate(with: self)
}
#endif
/// Convert a String to a NSAttributedString.
/// With that variable you can customize a String with a style.
///
/// Example:
///
/// string.attributedString.font(UIFont(fontName: .helveticaNeue, size: 20))
///
/// You can even concatenate two or more styles:
///
/// string.attributedString.font(UIFont(fontName: .helveticaNeue, size: 20)).backgroundColor(UIColor.red)
var attributedString: NSAttributedString {
NSAttributedString(string: self)
}
/// Returns the last path component.
var lastPathComponent: String {
NSString(string: self).lastPathComponent
}
/// Returns the path extension.
var pathExtension: String {
NSString(string: self).pathExtension
}
/// Delete the last path component.
var deletingLastPathComponent: String {
NSString(string: self).deletingLastPathComponent
}
/// Delete the path extension.
var deletingPathExtension: String {
NSString(string: self).deletingPathExtension
}
/// Returns an array of path components.
var pathComponents: [String] {
NSString(string: self).pathComponents
}
/// Appends a path component to the string.
///
/// - Parameter path: Path component to append.
/// - Returns: Returns all the string.
func appendingPathComponent(_ path: String) -> String {
let string = NSString(string: self)
return string.appendingPathComponent(path)
}
/// Appends a path extension to the string.
///
/// - Parameter ext: Extension to append.
/// - Returns: Returns all the string.
func appendingPathExtension(_ ext: String) -> String? {
let nsSt = NSString(string: self)
return nsSt.appendingPathExtension(ext)
}
/// Converts self to an UUID APNS valid (No "<>" or "-" or spaces).
///
/// - Returns: Converts self to an UUID APNS valid (No "<>" or "-" or spaces).
func readableUUID() -> String {
trimmingCharacters(in: CharacterSet(charactersIn: "<>")).replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "-", with: "")
}
/// Returns string with the first character uppercased.
///
/// - returns: Returns string with the first character uppercased.
func uppercasedFirst() -> String {
String(prefix(1)).uppercased() + String(dropFirst())
}
/// Returns string with the first character lowercased.
///
/// - returns: Returns string with the first character lowercased.
func lowercasedFirst() -> String {
String(prefix(1)).lowercased() + String(dropFirst())
}
/// Returns the reversed String.
///
/// - parameter preserveFormat: If set to true preserve the String format.
/// The default value is false.
/// **Example:**
/// "Let's try this function?" ->
/// "?noitcnuf siht yrt S'tel"
///
/// - returns: Returns the reversed String.
func reversed(preserveFormat: Bool) -> String {
guard !isEmpty else {
return ""
}
var reversed = String(removeExtraSpaces().reversed())
guard preserveFormat else {
return reversed
}
let words = reversed.components(separatedBy: " ").filter { !$0.isEmpty }
reversed.removeAll()
for word in words {
if let char = word.unicodeScalars.last {
if CharacterSet.uppercaseLetters.contains(char) {
reversed += word.lowercased().uppercasedFirst()
} else {
reversed += word.lowercased()
}
} else {
reversed += word.lowercased()
}
if word != words[words.count - 1] {
reversed += " "
}
}
return reversed
}
/// Returns true if the String has at least one uppercase chatacter, otherwise false.
///
/// - returns: Returns true if the String has at least one uppercase chatacter, otherwise false.
func hasUppercasedCharacters() -> Bool {
var found = false
for character in unicodeScalars where CharacterSet.uppercaseLetters.contains(character) {
found = true
}
return found
}
/// Returns true if the String has at least one lowercase chatacter, otherwise false.
///
/// - returns: Returns true if the String has at least one lowercase chatacter, otherwise false.
func hasLowercasedCharacters() -> Bool {
var found = false
for character in unicodeScalars where CharacterSet.lowercaseLetters.contains(character) {
found = true
}
return found
}
/// Remove double or more duplicated spaces.
///
/// - returns: Remove double or more duplicated spaces.
func removeExtraSpaces() -> String {
let squashed = replacingOccurrences(of: "[ ]+", with: " ", options: .regularExpression, range: nil)
return squashed.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
/// Returns a new string in which all occurrences of a target strings in a specified range of the String are replaced by another given string.
///
/// - Parameters:
/// - target: Target strings array.
/// - replacement: Replacement string.
/// - Returns: Returns a new string in which all occurrences of a target strings in a specified range of the String are replaced by another given string.
func replacingOccurrences(of target: [String], with replacement: String) -> String {
var string = self
for occurrence in target {
string = string.replacingOccurrences(of: occurrence, with: replacement)
}
return string
}
/// Count the number of lowercase characters.
///
/// - Returns: Number of lowercase characters.
func countLowercasedCharacters() -> Int {
var countLowercased = 0
for char in self where char.isLowercase {
countLowercased += 1
}
return countLowercased
}
/// Count the number of uppercase characters.
///
/// - Returns: Number of uppercase characters.
func countUppercasedCharacters() -> Int {
var countUppercased = 0
for char in self where char.isUppercase {
countUppercased += 1
}
return countUppercased
}
/// Count the number of numbers.
///
/// - Returns: Number of numbers.
func countNumbers() -> Int {
var countNumbers = 0
for char in self where char.isNumber {
countNumbers += 1
}
return countNumbers
}
/// Count the number of punctuations.
///
/// - Returns: Number of punctuations.
func countPunctuations() -> Int {
var countPuntuactions = 0
for char in self where char.isPunctuation {
countPuntuactions += 1
}
return countPuntuactions
}
/// Convert HEX string (separated by space) to "usual" characters string.
/// Example: "68 65 6c 6c 6f" -> "hello".
///
/// - Returns: Readable string.
func stringFromHEX() -> String {
var hex = self
var string: String = ""
hex = hex.replacingOccurrences(of: " ", with: "")
while !hex.isEmpty {
let character = String(hex[..<hex.index(hex.startIndex, offsetBy: 2)])
var characterInt: UInt32 = 0
hex = String(hex[hex.index(hex.startIndex, offsetBy: 2)...])
_ = Scanner(string: character).scanHexInt32(&characterInt)
string += String(format: "%c", characterInt)
}
return string
}
/// Return if self is anagram of another String.
///
/// - Parameter string: Other String.
/// - Returns: Return true if self is anagram of another String, otherwise false.
func isAnagram(of string: String) -> Bool {
let lowerSelf = lowercased().replacingOccurrences(of: " ", with: "")
let lowerOther = string.lowercased().replacingOccurrences(of: " ", with: "")
return lowerSelf.sorted() == lowerOther.sorted()
}
/// Returns if self is palindrome.
///
/// - Returns: Returns true if self is palindrome, otherwise false.
func isPalindrome() -> Bool {
let selfString = lowercased().replacingOccurrences(of: " ", with: "")
let otherString = String(selfString.reversed())
return selfString == otherString
}
/// Returns the character at the given index.
///
/// - Parameter index: Returns the character at the given index.
subscript(index: Int) -> Character {
self[self.index(startIndex, offsetBy: index)]
}
/// Returns the index of the given character, -1 if not found.
///
/// - Parameter character: Returns the index of the given character, -1 if not found.
subscript(character: Character) -> Int {
index(of: character)
}
/// Returns the character at the given index as String.
///
/// - Parameter index: Returns the character at the given index as String.
subscript(index: Int) -> String {
String(self[index])
}
/// Returns the string from a given range.
/// Example: print("BFKit"[1...3]) the result is "FKi".
///
/// - Parameter range: Returns the string from a given range.
subscript(range: Range<Int>) -> String {
substring(with: range)
}
/// Returns if self is a valid UUID or not.
///
/// - Returns: Returns if self is a valid UUID or not.
func isUUID() -> Bool {
do {
let regex: NSRegularExpression = try NSRegularExpression(pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", options: .caseInsensitive)
let matches: Int = regex.numberOfMatches(in: self, options: .reportCompletion, range: NSRange(location: 0, length: count))
return matches == 1
} catch {
return false
}
}
/// Returns if self is a valid UUID for APNS (Apple Push Notification System) or not.
///
/// - Returns: Returns if self is a valid UUID for APNS (Apple Push Notification System) or not.
func isUUIDForAPNS() -> Bool {
do {
let regex: NSRegularExpression = try NSRegularExpression(pattern: "^[0-9a-f]{32}$", options: .caseInsensitive)
let matches: Int = regex.numberOfMatches(in: self, options: .reportCompletion, range: NSRange(location: 0, length: count))
return matches == 1
} catch {
return false
}
}
/// Returns a new string containing matching regular expressions replaced with the template string.
///
/// - Parameters:
/// - regexString: The regex string.
/// - replacement: The replacement string.
/// - Returns: Returns a new string containing matching regular expressions replaced with the template string.
/// - Throws: Throws NSRegularExpression(pattern:, options:) errors.
func replacingMatches(regex regexString: String, with replacement: String) throws -> String {
let regex: NSRegularExpression = try NSRegularExpression(pattern: regexString, options: .caseInsensitive)
return regex.stringByReplacingMatches(in: self, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: count), withTemplate: "")
}
/// Localize current String using self as key.
///
/// - Returns: Returns localized String using self as key.
func localize() -> String {
NSLocalizedString(self, comment: "")
}
// MARK: - Functions not available on Linux
#if !os(Linux)
/// Check if self is an email.
///
/// - Returns: Returns true if it is an email, otherwise false.
func isEmail() -> Bool {
let emailRegEx: String = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
let regExPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegEx)
return regExPredicate.evaluate(with: lowercased())
}
/// Returns an array of String with all the links in
///
/// - Returns: Returns an array of String with all the links in
/// - Throws: Throws NSDataDetector errors.
func links() throws -> [String] {
let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let links = Array(detector.matches(in: self, options: NSRegularExpression.MatchingOptions.reportCompletion, range: NSRange(location: 0, length: count)))
return links.filter { $0.url != nil }.map { $0.url?.absoluteString ?? "" }
}
/// Returns an array of Date with all the dates in
///
/// - Returns: Returns an array of Date with all the date in
/// - Throws: Throws NSDataDetector errors.
func dates() throws -> [Date] {
let detector = try NSDataDetector(types: NSTextCheckingResult.CheckingType.date.rawValue)
let dates = Array(detector.matches(in: self, options: NSRegularExpression.MatchingOptions.withTransparentBounds, range: NSRange(location: 0, length: count)))
return dates.filter { $0.date != nil }.map { $0.date ?? Date() }
}
/// Returns an array of String with all the hashtags in
///
/// - Returns: Returns an array of String with all the hashtags in
/// - Throws: Throws NSRegularExpression errors.
func hashtags() throws -> [String] {
let detector = try NSRegularExpression(pattern: "#(\\w+)", options: NSRegularExpression.Options.caseInsensitive)
let hashtags = Array(detector.matches(in: self, options: NSRegularExpression.MatchingOptions.withoutAnchoringBounds, range: NSRange(location: 0, length: count)))
return hashtags.map { (self as NSString).substring(with: $0.range(at: 1)) }
}
/// Returns an array of String with all the mentions in
///
/// - Returns: Returns an array of String with all the mentions in
/// - Throws: Throws NSRegularExpression errors.
func mentions() throws -> [String] {
let detector = try NSRegularExpression(pattern: "@(\\w+)", options: NSRegularExpression.Options.caseInsensitive)
let mentions = Array(detector.matches(in: self, options: NSRegularExpression.MatchingOptions.withoutAnchoringBounds, range: NSRange(location: 0, length: count)))
return mentions.map { (self as NSString).substring(with: $0.range(at: 1)) }
}
#endif
}
/// Infix operator `???` with NilCoalescingPrecedence.
infix operator ???: NilCoalescingPrecedence
/// Returns defaultValue if optional is nil, otherwise returns optional.
///
/// - Parameters:
/// - optional: The optional variable.
/// - defaultValue: The default value.
/// - Returns: Returns defaultValue if optional is nil, otherwise returns optional.
public func ??? <T>(optional: T?, defaultValue: @autoclosure () -> String) -> String {
optional.map { String(describing: $0) } ?? defaultValue()
}
import Foundation
public extension StringProtocol {
/// SwifterSwift: The longest common suffix.
///
/// "Hello world!".commonSuffix(with: "It's cold!") = "ld!"
///
/// - Parameters:
/// - Parameter aString: The string with which to compare the receiver.
/// - Parameter options: Options for the comparison.
/// - Returns: The longest common suffix of the receiver and the given String.
func commonSuffix<T: StringProtocol>(with aString: T, options: String.CompareOptions = []) -> String {
return String(zip(reversed(), aString.reversed())
.lazy
.prefix(while: { (lhs: Character, rhs: Character) in
String(lhs).compare(String(rhs), options: options) == .orderedSame
})
.map { (lhs: Character, _: Character) in lhs }
.reversed())
}
#if canImport(Foundation)
/// SwifterSwift: Returns a new string in which all occurrences of a regex pattern in a specified range of the receiver are replaced by the template.
/// - Parameters:
/// - pattern: Regex pattern to replace.
/// - template: The regex template to replace the pattern.
/// - options: Options to use when matching the regex. Only .regularExpression, .anchored .and caseInsensitive are supported.
/// - searchRange: The range in the receiver in which to search.
/// - Returns: A new string in which all occurrences of regex pattern in searchRange of the receiver are replaced by template.
func replacingOccurrences<Target, Replacement>(
ofPattern pattern: Target,
withTemplate template: Replacement,
options: String.CompareOptions = [.regularExpression],
range searchRange: Range<Self.Index>? = nil) -> String where Target: StringProtocol,
Replacement: StringProtocol {
assert(
options.isStrictSubset(of: [.regularExpression, .anchored, .caseInsensitive]),
"Invalid options for regular expression replacement")
return replacingOccurrences(
of: pattern,
with: template,
options: options.union(.regularExpression),
range: searchRange)
}
#endif
}
import Foundation
public extension Array{
mutating func push(_ s:Element){
self.append(s)
}
func len()->Int{
self.count
}
mutating func pop()->Element?{
self.popLast()
}
mutating func clear(){
self = []
}
mutating func remove(_ index:Int)->Element{
let val = self.remove(at: index)
return val
}
}
extension Array where Element: Equatable {
mutating func dedup() {
var result = [Element]()
for element in self {
if !result.contains(element) {
result.append(element)
}
}
self = result
}
}
// MARK: - Global functions
/// Creates a flatten array.
///
/// Example:
/// [1, 2, [3, [4]]] -> [1, 2, 3, 4]
/// - Parameter array: Array bo te flattened.
/// - Returns: Returns a flatten array.
public func flatten<T>(_ array: [T]) -> [T] {
/// Returned flattened array.
var flattened: [T] = []
/// For every element inside the array.
for element in array {
/// If it's a nested array, safely cast.
if let subarray = element as? [T] {
/// For every subarray call the `flatten` function.
for subelement in flatten(subarray) {
/// We are now sure that this is an element
/// and we can add it to `flattened`.
flattened.append(subelement)
}
} else {
/// Otherwise is an element and add it to `flattened`.
flattened.append(element)
}
}
return flattened
}
extension Array {
mutating func shuffleArray() {
for i in 0 ..< (count - 1) {
let j = Int(arc4random_uniform(UInt32(count - i))) + i
swapAt(i, j)
}
}
}
/// This extension adds some useful functions to Array.
public extension Array {
// MARK: - Functions
/// A Bool value indicating whether the collection is not empty.
///
/// - Returns: Returns a Bool value indicating whether the collection is not empty.
var isNotEmpty: Bool {
!isEmpty
}
/// Simulates the array as a circle. When it is out of range, begins again.
///
/// - Parameter index: The index.
/// - Returns: Returns the object at a given index.
func circleObject(at index: Int) -> Element {
self[superCircle(at: index, size: count)]
}
/// Randomly selects an element from self and returns it.
///
/// - returns: An element that was randomly selected from the array.
func random() -> Element {
self[Int.random(in: 0...count - 1)]
}
/// Removes the element from self that is passed in.
///
/// - parameter object: The element that is removed from
mutating func remove(_ object: Element) {
var array: [String] = []
for index in self {
array.append("\(index)")
}
let index = array.firstIndex(of: "\(object)")
if let index = index {
remove(at: index)
}
}
/// Get the object at a given index in safe mode (nil if self is empty or out of range).
///
/// - Parameter index: The index.
/// - Returns: Returns the object at a given index in safe mode (nil if self is empty or out of range).
func safeObject(at index: Int) -> Element? {
guard !isEmpty, count > index else {
return nil
}
return self[index]
}
/// Get the index as a circle.
///
/// - Parameters:
/// - index: The index.
/// - maxSize: Max size of the array.
/// - Returns: Returns the right index.
private func superCircle(at index: Int, size maxSize: Int) -> Int {
var newIndex = index
if newIndex < 0 {
newIndex = newIndex % maxSize
newIndex += maxSize
}
if newIndex >= maxSize {
newIndex = newIndex % maxSize
}
return newIndex
}
/// Move object from an index to another.
///
/// - Parameters:
/// - fromIndex: The start index.
/// - toIndex: The end index.
mutating func swap(from fromIndex: Int, to toIndex: Int) {
if toIndex != fromIndex {
guard let object: Element = safeObject(at: fromIndex) else {
return
}
remove(at: fromIndex)
if toIndex >= count {
append(object)
} else {
insert(object, at: toIndex)
}
}
}
}
// MARK: - Initializers
public extension Array {
/// SwifterSwift: Creates an array with specified number of elements, for each element it calls specified closure.
/// - Parameters:
/// - count: The number of elements in the new array.
/// - element: A closure that initializes each element.
/// - Parameter *index*: An index of initialized element in the array.
/// - Returns: element of the array.
init(count: Int, element: (Int) throws -> Element) rethrows {
try self.init(unsafeUninitializedCapacity: count) { buffer, initializedCount in
for index in 0..<count {
try buffer.baseAddress?.advanced(by: index).initialize(to: element(index))
}
initializedCount = count
}
}
}
// MARK: - Methods
public extension Array {
/// SwifterSwift: Insert an element at the beginning of array.
///
/// [2, 3, 4, 5].prepend(1) -> [1, 2, 3, 4, 5]
/// ["e", "l", "l", "o"].prepend("h") -> ["h", "e", "l", "l", "o"]
///
/// - Parameter newElement: element to insert.
mutating func prepend(_ newElement: Element) {
insert(newElement, at: 0)
}
/// SwifterSwift: Safely swap values at given index positions.
///
/// [1, 2, 3, 4, 5].safeSwap(from: 3, to: 0) -> [4, 2, 3, 1, 5]
/// ["h", "e", "l", "l", "o"].safeSwap(from: 1, to: 0) -> ["e", "h", "l", "l", "o"]
///
/// - Parameters:
/// - index: index of first element.
/// - otherIndex: index of other element.
mutating func safeSwap(from index: Index, to otherIndex: Index) {
guard index != otherIndex else { return }
guard startIndex..<endIndex ~= index else { return }
guard startIndex..<endIndex ~= otherIndex else { return }
swapAt(index, otherIndex)
}
/// SwifterSwift: Sort an array like another array based on a key path. If the other array doesn't contain a certain value, it will be sorted last.
///
/// [MyStruct(x: 3), MyStruct(x: 1), MyStruct(x: 2)].sorted(like: [1, 2, 3], keyPath: \.x)
/// -> [MyStruct(x: 1), MyStruct(x: 2), MyStruct(x: 3)]
///
/// - Parameters:
/// - otherArray: array containing elements in the desired order.
/// - keyPath: keyPath indicating the property that the array should be sorted by
/// - Returns: sorted array.
func sorted<T: Hashable>(like otherArray: [T], keyPath: KeyPath<Element, T>) -> [Element] {
let dict = otherArray.enumerated().reduce(into: [:]) { $0[$1.element] = $1.offset }
return sorted {
guard let thisIndex = dict[$0[keyPath: keyPath]] else { return false }
guard let otherIndex = dict[$1[keyPath: keyPath]] else { return true }
return thisIndex < otherIndex
}
}
}
// MARK: - Methods (Equatable)
public extension Array where Element: Equatable {
/// SwifterSwift: Remove all instances of an item from array.
///
/// [1, 2, 2, 3, 4, 5].removeAll(2) -> [1, 3, 4, 5]
/// ["h", "e", "l", "l", "o"].removeAll("l") -> ["h", "e", "o"]
///
/// - Parameter item: item to remove.
/// - Returns: self after removing all instances of item.
@discardableResult
mutating func removeAll(_ item: Element) -> [Element] {
removeAll(where: { $0 == item })
return self
}
/// SwifterSwift: Remove all instances contained in items parameter from array.
///
/// [1, 2, 2, 3, 4, 5].removeAll([2,5]) -> [1, 3, 4]
/// ["h", "e", "l", "l", "o"].removeAll(["l", "h"]) -> ["e", "o"]
///
/// - Parameter items: items to remove.
/// - Returns: self after removing all instances of all items in given array.
@discardableResult
mutating func removeAll(_ items: [Element]) -> [Element] {
guard !items.isEmpty else { return self }
removeAll(where: { items.contains($0) })
return self
}
/// SwifterSwift: Remove all duplicate elements from Array.
///
/// [1, 2, 2, 3, 4, 5].removeDuplicates() -> [1, 2, 3, 4, 5]
/// ["h", "e", "l", "l", "o"]. removeDuplicates() -> ["h", "e", "l", "o"]
///
/// - Returns: Return array with all duplicate elements removed.
@discardableResult
mutating func removeDuplicates() -> [Element] {
// Thanks to https://github.com/sairamkotha for improving the method
self = reduce(into: [Element]()) {
if !$0.contains($1) {
$0.append($1)
}
}
return self
}
/// SwifterSwift: Return array with all duplicate elements removed.
///
/// [1, 1, 2, 2, 3, 3, 3, 4, 5].withoutDuplicates() -> [1, 2, 3, 4, 5])
/// ["h", "e", "l", "l", "o"].withoutDuplicates() -> ["h", "e", "l", "o"])
///
/// - Returns: an array of unique elements.
///
func withoutDuplicates() -> [Element] {
// Thanks to https://github.com/sairamkotha for improving the method
return reduce(into: [Element]()) {
if !$0.contains($1) {
$0.append($1)
}
}
}
/// SwifterSwift: Returns an array with all duplicate elements removed using KeyPath to compare.
///
/// - Parameter path: Key path to compare, the value must be Equatable.
/// - Returns: an array of unique elements.
func withoutDuplicates<E: Equatable>(keyPath path: KeyPath<Element, E>) -> [Element] {
return reduce(into: [Element]()) { result, element in
if !result.contains(where: { $0[keyPath: path] == element[keyPath: path] }) {
result.append(element)
}
}
}
/// SwifterSwift: Returns an array with all duplicate elements removed using KeyPath to compare.
///
/// - Parameter path: Key path to compare, the value must be Hashable.
/// - Returns: an array of unique elements.
func withoutDuplicates<E: Hashable>(keyPath path: KeyPath<Element, E>) -> [Element] {
var set = Set<E>()
return filter { set.insert($0[keyPath: path]).inserted }
}
}
import Foundation
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public extension Optional where Wrapped: Collection {
/// Returns whether the instance is nil or empty.
@inlinable var isNilOrEmpty: Bool {
return self?.isEmpty ?? true
}
}
public extension Optional {
/// SwifterSwift: Get self of default value (if self is nil).
///
/// let foo: String? = nil
/// print(foo.unwrapped(or: "bar")) -> "bar"
///
/// let bar: String? = "bar"
/// print(bar.unwrapped(or: "foo")) -> "bar"
///
/// - Parameter defaultValue: default value to return if self is nil.
/// - Returns: self if not nil or default value if nil.
func unwrapped(or defaultValue: Wrapped) -> Wrapped {
// http://www.russbishop.net/improving-optionals
return self ?? defaultValue
}
/// SwifterSwift: Gets the wrapped value of an optional. If the optional is `nil`, throw a custom error.
///
/// let foo: String? = nil
/// try print(foo.unwrapped(or: MyError.notFound)) -> error: MyError.notFound
///
/// let bar: String? = "bar"
/// try print(bar.unwrapped(or: MyError.notFound)) -> "bar"
///
/// - Parameter error: The error to throw if the optional is `nil`.
/// - Throws: The error passed in.
/// - Returns: The value wrapped by the optional.
func unwrapped(or error: Error) throws -> Wrapped {
guard let wrapped = self else { throw error }
return wrapped
}
/// SwifterSwift: Runs a block to Wrapped if not nil.
///
/// let foo: String? = nil
/// foo.run { unwrappedFoo in
/// // block will never run since foo is nil
/// print(unwrappedFoo)
/// }
///
/// let bar: String? = "bar"
/// bar.run { unwrappedBar in
/// // block will run since bar is not nil
/// print(unwrappedBar) -> "bar"
/// }
///
/// - Parameter block: a block to run if self is not nil.
func run(_ block: (Wrapped) -> Void) {
// http://www.russbishop.net/improving-optionals
_ = map(block)
}
/// SwifterSwift: Assign an optional value to a variable only if the value is not nil.
///
/// let someParameter: String? = nil
/// let parameters = [String: Any]() // Some parameters to be attached to a GET request
/// parameters[someKey] ??= someParameter // It won't be added to the parameters dict
///
/// - Parameters:
/// - lhs: Any?
/// - rhs: Any?
static func ??= (lhs: inout Optional, rhs: Optional) {
guard let rhs = rhs else { return }
lhs = rhs
}
/// SwifterSwift: Assign an optional value to a variable only if the variable is nil.
///
/// var someText: String? = nil
/// let newText = "Foo"
/// let defaultText = "Bar"
/// someText ?= newText // someText is now "Foo" because it was nil before
/// someText ?= defaultText // someText doesn't change its value because it's not nil
///
/// - Parameters:
/// - lhs: Any?
/// - rhs: Any?
static func ?= (lhs: inout Optional, rhs: @autoclosure () -> Optional) {
if lhs == nil {
lhs = rhs()
}
}
}
// MARK: - Methods (Collection)
public extension Optional where Wrapped: Collection {
/// SwifterSwift: Check if optional is nil or empty collection.
var isNilOrEmpty: Bool {
guard let collection = self else { return true }
return collection.isEmpty
}
/// SwifterSwift: Returns the collection only if it is not nil and not empty.
var nonEmpty: Wrapped? {
guard let collection = self else { return nil }
guard !collection.isEmpty else { return nil }
return collection
}
}
// MARK: - Methods (RawRepresentable, RawValue: Equatable)
public extension Optional where Wrapped: RawRepresentable, Wrapped.RawValue: Equatable {
// swiftlint:disable missing_swifterswift_prefix
/// Returns a Boolean value indicating whether two values are equal.
///
/// Equality is the inverse of inequality. For any values `a` and `b`,
/// `a == b` implies that `a != b` is `false`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
@inlinable static func == (lhs: Optional, rhs: Wrapped.RawValue?) -> Bool {
return lhs?.rawValue == rhs
}
/// Returns a Boolean value indicating whether two values are equal.
///
/// Equality is the inverse of inequality. For any values `a` and `b`,
/// `a == b` implies that `a != b` is `false`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
@inlinable static func == (lhs: Wrapped.RawValue?, rhs: Optional) -> Bool {
return lhs == rhs?.rawValue
}
/// Returns a Boolean value indicating whether two values are not equal.
///
/// Inequality is the inverse of equality. For any values `a` and `b`,
/// `a != b` implies that `a == b` is `false`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
@inlinable static func != (lhs: Optional, rhs: Wrapped.RawValue?) -> Bool {
return lhs?.rawValue != rhs
}
/// Returns a Boolean value indicating whether two values are not equal.
///
/// Inequality is the inverse of equality. For any values `a` and `b`,
/// `a != b` implies that `a == b` is `false`.
///
/// - Parameters:
/// - lhs: A value to compare.
/// - rhs: Another value to compare.
@inlinable static func != (lhs: Wrapped.RawValue?, rhs: Optional) -> Bool {
return lhs != rhs?.rawValue
}
// swiftlint:enable missing_swifterswift_prefix
}
// MARK: - Operators
infix operator ??=: AssignmentPrecedence
infix operator ?=: AssignmentPrecedence
import Foundation
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public extension Result {
/// Returns whether the instance is `.success`.
@inlinable var isSuccess: Bool {
guard case .success = self else { return false }
return true
}
/// Returns whether the instance is `.failure`.
@inlinable var isFailure: Bool {
!isSuccess
}
/// Returns the associated value if the result is a success, `nil` otherwise.
@inlinable var success: Success? {
guard case let .success(value) = self else { return nil }
return value
}
/// Returns the associated error value if the result is a failure, `nil` otherwise.
@inlinable var failure: Failure? {
guard case let .failure(error) = self else { return nil }
return error
}
/// Initializes a `Result` from value or error. Returns `.failure` if the error is non-nil, `.success` otherwise.
///
/// - Parameters:
/// - value: A value.
/// - error: An `Error`.
@inlinable init(value: Success, error: Failure?) {
if let error = error {
self = .failure(error)
} else {
self = .success(value)
}
}
/// Evaluates the specified closure when the `Result` is a success, passing the unwrapped value as a parameter.
///
/// Use the `tryMap` method with a closure that may throw an error. For example:
///
/// let possibleData: Result<Data, Error> = .success(Data(...))
/// let possibleObject = possibleData.tryMap {
/// try JSONSerialization.jsonObject(with: $0)
/// }
///
/// - parameter transform: A closure that takes the success value of the instance.
///
/// - returns: A `Result` containing the result of the given closure. If this instance is a failure, returns the
/// same failure.
@inlinable func tryMap<NewSuccess>(_ transform: (Success) throws -> NewSuccess) -> Result<NewSuccess, Error> {
switch self {
case let .success(value):
do {
return try .success(transform(value))
} catch {
return .failure(error)
}
case let .failure(error):
return .failure(error)
}
}
/// Evaluates the specified closure when the `Result` is a failure, passing the unwrapped error as a parameter.
///
/// Use the `tryMapError` function with a closure that may throw an error. For example:
///
/// let possibleData: Result<Data, Error> = .success(Data(...))
/// let possibleObject = possibleData.tryMapError {
/// try someFailableFunction(taking: $0)
/// }
///
/// - Parameter transform: A throwing closure that takes the error of the instance.
///
/// - Returns: A `Result` instance containing the result of the transform. If this instance is a success, returns
/// the same success.
@inlinable func tryMapError<NewFailure: Error>(
_ transform: (Failure) throws -> NewFailure
) -> Result<Success, Error> {
switch self {
case let .failure(error):
do {
return try .failure(transform(error))
} catch {
return .failure(error)
}
case let .success(value):
return .success(value)
}
}
}
#if canImport(Foundation)
import Foundation
#if canImport(UIKit) && canImport(AVFoundation)
import AVFoundation
import UIKit
#endif
// MARK: - Properties
public extension URL {
/// SwifterSwift: Dictionary of the URL's query parameters.
var queryParameters: [String: String]? {
guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems else { return nil }
var items: [String: String] = [:]
for queryItem in queryItems {
items[queryItem.name] = queryItem.value
}
return items
}
}
// MARK: - Initializers
public extension URL {
/// SwifterSwift: Initializes an `URL` object with a base URL and a relative string. If `string` was malformed, returns `nil`.
/// - Parameters:
/// - string: The URL string with which to initialize the `URL` object. Must conform to RFC 2396. `string` is interpreted relative to `url`.
/// - url: The base URL for the `URL` object.
init?(string: String?, relativeTo url: URL? = nil) {
guard let string = string else { return nil }
self.init(string: string, relativeTo: url)
}
/**
SwifterSwift: Initializes a forced unwrapped `URL` from string. Can potentially crash if string is invalid.
- Parameter unsafeString: The URL string used to initialize the `URL`object.
*/
init(unsafeString: String) {
self.init(string: unsafeString)!
}
}
// MARK: - Methods
public extension URL {
/// SwifterSwift: URL with appending query parameters.
///
/// let url = URL(string: "https://google.com")!
/// let param = ["q": "Swifter Swift"]
/// url.appendingQueryParameters(params) -> "https://google.com?q=Swifter%20Swift"
///
/// - Parameter parameters: parameters dictionary.
/// - Returns: URL with appending given query parameters.
func appendingQueryParameters(_ parameters: [String: String]) -> URL {
var urlComponents = URLComponents(url: self, resolvingAgainstBaseURL: true)!
urlComponents.queryItems = (urlComponents.queryItems ?? []) + parameters
.map { URLQueryItem(name: $0, value: $1) }
return urlComponents.url!
}
/// SwifterSwift: Append query parameters to URL.
///
/// var url = URL(string: "https://google.com")!
/// let param = ["q": "Swifter Swift"]
/// url.appendQueryParameters(params)
/// print(url) // prints "https://google.com?q=Swifter%20Swift"
///
/// - Parameter parameters: parameters dictionary.
mutating func appendQueryParameters(_ parameters: [String: String]) {
self = appendingQueryParameters(parameters)
}
/// SwifterSwift: Get value of a query key.
///
/// var url = URL(string: "https://google.com?code=12345")!
/// queryValue(for: "code") -> "12345"
///
/// - Parameter key: The key of a query value.
func queryValue(for key: String) -> String? {
return URLComponents(string: absoluteString)?
.queryItems?
.first(where: { $0.name == key })?
.value
}
/// SwifterSwift: Returns a new URL by removing all the path components.
///
/// let url = URL(string: "https://domain.com/path/other")!
/// print(url.deletingAllPathComponents()) // prints "https://domain.com/"
///
/// - Returns: URL with all path components removed.
func deletingAllPathComponents() -> URL {
guard !pathComponents.isEmpty else { return self }
var url: URL = self
for _ in 0..<pathComponents.count - 1 {
url.deleteLastPathComponent()
}
return url
}
/// SwifterSwift: Remove all the path components from the URL.
///
/// var url = URL(string: "https://domain.com/path/other")!
/// url.deleteAllPathComponents()
/// print(url) // prints "https://domain.com/"
mutating func deleteAllPathComponents() {
guard !pathComponents.isEmpty else { return }
for _ in 0..<pathComponents.count - 1 {
deleteLastPathComponent()
}
}
/// SwifterSwift: Generates new URL that does not have scheme.
///
/// let url = URL(string: "https://domain.com")!
/// print(url.droppedScheme()) // prints "domain.com"
func droppedScheme() -> URL? {
if let scheme = scheme {
let droppedScheme = String(absoluteString.dropFirst(scheme.count + 3))
return URL(string: droppedScheme)
}
guard host != nil else { return self }
let droppedScheme = String(absoluteString.dropFirst(2))
return URL(string: droppedScheme)
}
}
// MARK: - Methods
public extension URL {
#if os(iOS) || os(tvOS)
/// SwifterSwift: Generate a thumbnail image from given url. Returns nil if no thumbnail could be created. This function may take some time to complete. It's recommended to dispatch the call if the thumbnail is not generated from a local resource.
///
/// var url = URL(string: "https://video.golem.de/files/1/1/20637/wrkw0718-sd.mp4")!
/// var thumbnail = url.thumbnail()
/// thumbnail = url.thumbnail(fromTime: 5)
///
/// DispatchQueue.main.async {
/// someImageView.image = url.thumbnail()
/// }
///
/// - Parameter time: Seconds into the video where the image should be generated.
/// - Returns: The UIImage result of the AVAssetImageGenerator
func thumbnail(fromTime time: Float64 = 0) -> UIImage? {
let imageGenerator = AVAssetImageGenerator(asset: AVAsset(url: self))
let time = CMTimeMakeWithSeconds(time, preferredTimescale: 1)
var actualTime = CMTimeMake(value: 0, timescale: 0)
guard let cgImage = try? imageGenerator.copyCGImage(at: time, actualTime: &actualTime) else {
return nil
}
return UIImage(cgImage: cgImage)
}
#endif
}
#endif
#if canImport(CryptoKit)
import CryptoKit
@available(iOS 13.0, macOS 10.15, watchOS 6.0, tvOS 13.0, *)
public extension Digest {
// MARK: - Properties
/// SwifterSwift: Hexadecimal value string (read-only, Complexity: O(N), _N_ being the amount of bytes.)
var hexString: String {
var result = ""
for byte in self.makeIterator() {
result += String(format: "%02X", byte)
}
return result
}
}
#endif
public extension Sequence {
/// SwifterSwift: Check if all elements in collection match a condition.
///
/// [2, 2, 4].all(matching: {$0 % 2 == 0}) -> true
/// [1,2, 2, 4].all(matching: {$0 % 2 == 0}) -> false
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: true when all elements in the array match the specified condition.
func all(matching condition: (Element) throws -> Bool) rethrows -> Bool {
return try !contains { try !condition($0) }
}
/// SwifterSwift: Check if no elements in collection match a condition.
///
/// [2, 2, 4].none(matching: {$0 % 2 == 0}) -> false
/// [1, 3, 5, 7].none(matching: {$0 % 2 == 0}) -> true
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: true when no elements in the array match the specified condition.
func none(matching condition: (Element) throws -> Bool) rethrows -> Bool {
return try !contains { try condition($0) }
}
/// SwifterSwift: Check if any element in collection match a condition.
///
/// [2, 2, 4].any(matching: {$0 % 2 == 0}) -> false
/// [1, 3, 5, 7].any(matching: {$0 % 2 == 0}) -> true
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: true when no elements in the array match the specified condition.
func any(matching condition: (Element) throws -> Bool) rethrows -> Bool {
return try contains { try condition($0) }
}
/// SwifterSwift: Filter elements based on a rejection condition.
///
/// [2, 2, 4, 7].reject(where: {$0 % 2 == 0}) -> [7]
///
/// - Parameter condition: to evaluate the exclusion of an element from the array.
/// - Returns: the array with rejected values filtered from it.
func reject(where condition: (Element) throws -> Bool) rethrows -> [Element] {
return try filter { return try !condition($0) }
}
/// SwifterSwift: Get element count based on condition.
///
/// [2, 2, 4, 7].count(where: {$0 % 2 == 0}) -> 3
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: number of times the condition evaluated to true.
func count(where condition: (Element) throws -> Bool) rethrows -> Int {
var count = 0
for element in self where try condition(element) {
count += 1
}
return count
}
/// SwifterSwift: Iterate over a collection in reverse order. (right to left)
///
/// [0, 2, 4, 7].forEachReversed({ print($0)}) -> // Order of print: 7,4,2,0
///
/// - Parameter body: a closure that takes an element of the array as a parameter.
func forEachReversed(_ body: (Element) throws -> Void) rethrows {
try reversed().forEach(body)
}
/// SwifterSwift: Calls the given closure with each element where condition is true.
///
/// [0, 2, 4, 7].forEach(where: {$0 % 2 == 0}, body: { print($0)}) -> // print: 0, 2, 4
///
/// - Parameters:
/// - condition: condition to evaluate each element against.
/// - body: a closure that takes an element of the array as a parameter.
func forEach(where condition: (Element) throws -> Bool, body: (Element) throws -> Void) rethrows {
try lazy.filter(condition).forEach(body)
}
/// SwifterSwift: Reduces an array while returning each interim combination.
///
/// [1, 2, 3].accumulate(initial: 0, next: +) -> [1, 3, 6]
///
/// - Parameters:
/// - initial: initial value.
/// - next: closure that combines the accumulating value and next element of the array.
/// - Returns: an array of the final accumulated value and each interim combination.
func accumulate<U>(initial: U, next: (U, Element) throws -> U) rethrows -> [U] {
var runningTotal = initial
return try map { element in
runningTotal = try next(runningTotal, element)
return runningTotal
}
}
/// SwifterSwift: Filtered and map in a single operation.
///
/// [1,2,3,4,5].filtered({ $0 % 2 == 0 }, map: { $0.string }) -> ["2", "4"]
///
/// - Parameters:
/// - isIncluded: condition of inclusion to evaluate each element against.
/// - transform: transform element function to evaluate every element.
/// - Returns: Return an filtered and mapped array.
func filtered<T>(_ isIncluded: (Element) throws -> Bool, map transform: (Element) throws -> T) rethrows -> [T] {
return try lazy.filter(isIncluded).map(transform)
}
/// SwifterSwift: Get the only element based on a condition.
///
/// [].single(where: {_ in true}) -> nil
/// [4].single(where: {_ in true}) -> 4
/// [1, 4, 7].single(where: {$0 % 2 == 0}) -> 4
/// [2, 2, 4, 7].single(where: {$0 % 2 == 0}) -> nil
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: The only element in the array matching the specified condition. If there are more matching elements,
/// nil is returned. (optional)
func single(where condition: (Element) throws -> Bool) rethrows -> Element? {
var singleElement: Element?
for element in self where try condition(element) {
guard singleElement == nil else {
singleElement = nil
break
}
singleElement = element
}
return singleElement
}
/// SwifterSwift: Remove duplicate elements based on condition.
///
/// [1, 2, 1, 3, 2].withoutDuplicates { $0 } -> [1, 2, 3]
/// [(1, 4), (2, 2), (1, 3), (3, 2), (2, 1)].withoutDuplicates { $0.0 } -> [(1, 4), (2, 2), (3, 2)]
///
/// - Parameter transform: A closure that should return the value to be evaluated for repeating elements.
/// - Returns: Sequence without repeating elements
/// - Complexity: O(*n*), where *n* is the length of the sequence.
func withoutDuplicates<T: Hashable>(transform: (Element) throws -> T) rethrows -> [Element] {
var set = Set<T>()
return try filter { set.insert(try transform($0)).inserted }
}
/// SwifterSwift: Separates all items into 2 lists based on a given predicate. The first list contains all items
/// for which the specified condition evaluates to true. The second list contains those that don't.
///
/// let (even, odd) = [0, 1, 2, 3, 4, 5].divided { $0 % 2 == 0 }
/// let (minors, adults) = people.divided { $0.age < 18 }
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: A tuple of matched and non-matched items
func divided(by condition: (Element) throws -> Bool) rethrows -> (matching: [Element], nonMatching: [Element]) {
// Inspired by: http://ruby-doc.org/core-2.5.0/Enumerable.html#method-i-partition
var matching = [Element]()
var nonMatching = [Element]()
for element in self {
// swiftlint:disable:next void_function_in_ternary
try condition(element) ? matching.append(element) : nonMatching.append(element)
}
return (matching, nonMatching)
}
/// SwifterSwift: Return a sorted array based on a key path and a compare function.
///
/// - Parameter keyPath: Key path to sort by.
/// - Parameter compare: Comparison function that will determine the ordering.
/// - Returns: The sorted array.
func sorted<T>(by keyPath: KeyPath<Element, T>, with compare: (T, T) -> Bool) -> [Element] {
return sorted { compare($0[keyPath: keyPath], $1[keyPath: keyPath]) }
}
/// SwifterSwift: Return a sorted array based on a key path.
///
/// - Parameter keyPath: Key path to sort by. The key path type must be Comparable.
/// - Returns: The sorted array.
func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] {
return sorted { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
}
/// SwifterSwift: Returns a sorted sequence based on two key paths. The second one will be used in case the values
/// of the first one match.
///
/// - Parameters:
/// - keyPath1: Key path to sort by. Must be Comparable.
/// - keyPath2: Key path to sort by in case the values of `keyPath1` match. Must be Comparable.
func sorted<T: Comparable, U: Comparable>(by keyPath1: KeyPath<Element, T>,
and keyPath2: KeyPath<Element, U>) -> [Element] {
return sorted {
if $0[keyPath: keyPath1] != $1[keyPath: keyPath1] {
return $0[keyPath: keyPath1] < $1[keyPath: keyPath1]
}
return $0[keyPath: keyPath2] < $1[keyPath: keyPath2]
}
}
/// SwifterSwift: Returns a sorted sequence based on three key paths. Whenever the values of one key path match, the
/// next one will be used.
///
/// - Parameters:
/// - keyPath1: Key path to sort by. Must be Comparable.
/// - keyPath2: Key path to sort by in case the values of `keyPath1` match. Must be Comparable.
/// - keyPath3: Key path to sort by in case the values of `keyPath1` and `keyPath2` match. Must be Comparable.
func sorted<T: Comparable, U: Comparable, V: Comparable>(by keyPath1: KeyPath<Element, T>,
and keyPath2: KeyPath<Element, U>,
and keyPath3: KeyPath<Element, V>) -> [Element] {
return sorted {
if $0[keyPath: keyPath1] != $1[keyPath: keyPath1] {
return $0[keyPath: keyPath1] < $1[keyPath: keyPath1]
}
if $0[keyPath: keyPath2] != $1[keyPath: keyPath2] {
return $0[keyPath: keyPath2] < $1[keyPath: keyPath2]
}
return $0[keyPath: keyPath3] < $1[keyPath: keyPath3]
}
}
/// SwifterSwift: Sum of a `AdditiveArithmetic` property of each `Element` in a `Sequence`.
///
/// ["James", "Wade", "Bryant"].sum(for: \.count) -> 15
///
/// - Parameter keyPath: Key path of the `AdditiveArithmetic` property.
/// - Returns: The sum of the `AdditiveArithmetic` properties at `keyPath`.
func sum<T: AdditiveArithmetic>(for keyPath: KeyPath<Element, T>) -> T {
// Inspired by: https://swiftbysundell.com/articles/reducers-in-swift/
return reduce(.zero) { $0 + $1[keyPath: keyPath] }
}
/// SwifterSwift: Returns the first element of the sequence with having property by given key path equals to given
/// `value`.
///
/// - Parameters:
/// - keyPath: The `KeyPath` of property for `Element` to compare.
/// - value: The value to compare with `Element` property.
/// - Returns: The first element of the collection that has property by given key path equals to given `value` or
/// `nil` if there is no such element.
func first<T: Equatable>(where keyPath: KeyPath<Element, T>, equals value: T) -> Element? {
return first { $0[keyPath: keyPath] == value }
}
}
public extension Sequence where Element: Equatable {
/// SwifterSwift: Check if array contains an array of elements.
///
/// [1, 2, 3, 4, 5].contains([1, 2]) -> true
/// [1.2, 2.3, 4.5, 3.4, 4.5].contains([2, 6]) -> false
/// ["h", "e", "l", "l", "o"].contains(["l", "o"]) -> true
///
/// - Parameter elements: array of elements to check.
/// - Returns: true if array contains all given items.
/// - Complexity: _O(m·n)_, where _m_ is the length of `elements` and _n_ is the length of this sequence.
func contains(_ elements: [Element]) -> Bool {
return elements.allSatisfy { contains($0) }
}
}
public extension Sequence where Element: Hashable {
/// SwifterSwift: Check if array contains an array of elements.
///
/// [1, 2, 3, 4, 5].contains([1, 2]) -> true
/// [1.2, 2.3, 4.5, 3.4, 4.5].contains([2, 6]) -> false
/// ["h", "e", "l", "l", "o"].contains(["l", "o"]) -> true
///
/// - Parameter elements: array of elements to check.
/// - Returns: true if array contains all given items.
/// - Complexity: _O(m + n)_, where _m_ is the length of `elements` and _n_ is the length of this sequence.
func contains(_ elements: [Element]) -> Bool {
let set = Set(self)
return elements.allSatisfy { set.contains($0) }
}
/// SwifterSwift: Check whether a sequence contains duplicates.
///
/// - Returns: true if the receiver contains duplicates.
func containsDuplicates() -> Bool {
var set = Set<Element>()
return contains { !set.insert($0).inserted }
}
/// SwifterSwift: Getting the duplicated elements in a sequence.
///
/// [1, 1, 2, 2, 3, 3, 3, 4, 5].duplicates().sorted() -> [1, 2, 3])
/// ["h", "e", "l", "l", "o"].duplicates().sorted() -> ["l"])
///
/// - Returns: An array of duplicated elements.
///
func duplicates() -> [Element] {
var set = Set<Element>()
var duplicates = Set<Element>()
forEach {
if !set.insert($0).inserted {
duplicates.insert($0)
}
}
return Array(duplicates)
}
}
// MARK: - Methods (AdditiveArithmetic)
public extension Sequence where Element: AdditiveArithmetic {
/// SwifterSwift: Sum of all elements in array.
///
/// [1, 2, 3, 4, 5].sum() -> 15
///
/// - Returns: sum of the array's elements.
func sum() -> Element {
return reduce(.zero, +)
}
}
#if canImport(Dispatch)
import Dispatch
#endif
// MARK: - Properties
public extension Collection {
/// SwifterSwift: The full range of the collection.
var fullRange: Range<Index> { startIndex..<endIndex }
}
// MARK: - Methods
public extension Collection {
#if canImport(Dispatch)
/// SwifterSwift: Performs `each` closure for each element of collection in parallel.
///
/// array.forEachInParallel { item in
/// print(item)
/// }
///
/// - Parameter each: closure to run for each element.
func forEachInParallel(_ each: (Self.Element) -> Void) {
DispatchQueue.concurrentPerform(iterations: count) {
each(self[index(startIndex, offsetBy: $0)])
}
}
#endif
/// SwifterSwift: Safe protects the array from out of bounds by use of optional.
///
/// let arr = [1, 2, 3, 4, 5]
/// arr[safe: 1] -> 2
/// arr[safe: 10] -> nil
///
/// - Parameter index: index of element to access element.
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
/// SwifterSwift: Returns an array of slices of length "size" from the array. If array can't be split evenly, the final slice will be the remaining elements.
///
/// [0, 2, 4, 7].group(by: 2) -> [[0, 2], [4, 7]]
/// [0, 2, 4, 7, 6].group(by: 2) -> [[0, 2], [4, 7], [6]]
///
/// - Parameter size: The size of the slices to be returned.
/// - Returns: grouped self.
func group(by size: Int) -> [[Element]]? {
// Inspired by: https://lodash.com/docs/4.17.4#chunk
guard size > 0, !isEmpty else { return nil }
var start = startIndex
var slices = [[Element]]()
while start != endIndex {
let end = index(start, offsetBy: size, limitedBy: endIndex) ?? endIndex
slices.append(Array(self[start..<end]))
start = end
}
return slices
}
/// SwifterSwift: Get all indices where condition is met.
///
/// [1, 7, 1, 2, 4, 1, 8].indices(where: { $0 == 1 }) -> [0, 2, 5]
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: all indices where the specified condition evaluates to true (optional).
func indices(where condition: (Element) throws -> Bool) rethrows -> [Index]? {
let indices = try self.indices.filter { try condition(self[$0]) }
return indices.isEmpty ? nil : indices
}
/// SwifterSwift: Calls the given closure with an array of size of the parameter slice.
///
/// [0, 2, 4, 7].forEach(slice: 2) { print($0) } -> // print: [0, 2], [4, 7]
/// [0, 2, 4, 7, 6].forEach(slice: 2) { print($0) } -> // print: [0, 2], [4, 7], [6]
///
/// - Parameters:
/// - slice: size of array in each interation.
/// - body: a closure that takes an array of slice size as a parameter.
func forEach(slice: Int, body: ([Element]) throws -> Void) rethrows {
var start = startIndex
while case let end = index(start, offsetBy: slice, limitedBy: endIndex) ?? endIndex,
start != end {
try body(Array(self[start..<end]))
start = end
}
}
/// SwifterSwift: Unique pair of elements in a collection.
///
/// let array = [1, 2, 3]
/// for (first, second) in array.adjacentPairs() {
/// print(first, second) // print: (1, 2) (1, 3) (2, 3)
/// }
///
///
/// - Returns: a sequence of adjacent pairs of elements from this collection.
func adjacentPairs() -> AnySequence<(Element, Element)> {
guard var index1 = index(startIndex, offsetBy: 0, limitedBy: endIndex),
var index2 = index(index1, offsetBy: 1, limitedBy: endIndex) else {
return AnySequence {
EmptyCollection.Iterator()
}
}
return AnySequence {
AnyIterator {
if index1 >= endIndex || index2 >= endIndex {
return nil
}
defer {
index2 = self.index(after: index2)
if index2 >= endIndex {
index1 = self.index(after: index1)
index2 = self.index(after: index1)
}
}
return (self[index1], self[index2])
}
}
}
}
// MARK: - Methods (Equatable)
public extension Collection where Element: Equatable {
/// SwifterSwift: All indices of specified item.
///
/// [1, 2, 2, 3, 4, 2, 5].indices(of 2) -> [1, 2, 5]
/// [1.2, 2.3, 4.5, 3.4, 4.5].indices(of 2.3) -> [1]
/// ["h", "e", "l", "l", "o"].indices(of "l") -> [2, 3]
///
/// - Parameter item: item to check.
/// - Returns: an array with all indices of the given item.
func indices(of item: Element) -> [Index] {
return indices.filter { self[$0] == item }
}
}
// MARK: - Methods (BinaryInteger)
public extension Collection where Element: BinaryInteger {
/// SwifterSwift: Average of all elements in array.
///
/// - Returns: the average of the array's elements.
func average() -> Double {
// http://stackoverflow.com/questions/28288148/making-my-function-calculate-average-of-array-swift
guard !isEmpty else { return .zero }
return Double(reduce(.zero, +)) / Double(count)
}
}
// MARK: - Methods (FloatingPoint)
public extension Collection where Element: FloatingPoint {
/// SwifterSwift: Average of all elements in array.
///
/// [1.2, 2.3, 4.5, 3.4, 4.5].average() = 3.18
///
/// - Returns: average of the array's elements.
func average() -> Element {
guard !isEmpty else { return .zero }
return reduce(.zero, +) / Element(count)
}
}
public extension MutableCollection where Self: RandomAccessCollection {
/// SwifterSwift: Sort the collection based on a keypath and a compare function.
///
/// - Parameter keyPath: Key path to sort by. The key path type must be Comparable.
/// - Parameter compare: Comparison function that will determine the ordering.
mutating func sort<T>(by keyPath: KeyPath<Element, T>, with compare: (T, T) -> Bool) {
sort { compare($0[keyPath: keyPath], $1[keyPath: keyPath]) }
}
/// SwifterSwift: Sort the collection based on a keypath.
///
/// - Parameter keyPath: Key path to sort by. The key path type must be Comparable.
mutating func sort<T: Comparable>(by keyPath: KeyPath<Element, T>) {
sort { $0[keyPath: keyPath] < $1[keyPath: keyPath] }
}
/// SwifterSwift: Sort the collection based on two key paths. The second one will be used in case the values of the first one match.
///
/// - Parameters:
/// - keyPath1: Key path to sort by. Must be Comparable.
/// - keyPath2: Key path to sort by in case the values of `keyPath1` match. Must be Comparable.
mutating func sort<T: Comparable, U: Comparable>(by keyPath1: KeyPath<Element, T>,
and keyPath2: KeyPath<Element, U>) {
sort {
if $0[keyPath: keyPath1] != $1[keyPath: keyPath1] {
return $0[keyPath: keyPath1] < $1[keyPath: keyPath1]
}
return $0[keyPath: keyPath2] < $1[keyPath: keyPath2]
}
}
/// SwifterSwift: Sort the collection based on three key paths. Whenever the values of one key path match, the next one will be used.
///
/// - Parameters:
/// - keyPath1: Key path to sort by. Must be Comparable.
/// - keyPath2: Key path to sort by in case the values of `keyPath1` match. Must be Comparable.
/// - keyPath3: Key path to sort by in case the values of `keyPath1` and `keyPath2` match. Must be Comparable.
mutating func sort<T: Comparable, U: Comparable, V: Comparable>(by keyPath1: KeyPath<Element, T>,
and keyPath2: KeyPath<Element, U>,
and keyPath3: KeyPath<Element, V>) {
sort {
if $0[keyPath: keyPath1] != $1[keyPath: keyPath1] {
return $0[keyPath: keyPath1] < $1[keyPath: keyPath1]
}
if $0[keyPath: keyPath2] != $1[keyPath: keyPath2] {
return $0[keyPath: keyPath2] < $1[keyPath: keyPath2]
}
return $0[keyPath: keyPath3] < $1[keyPath: keyPath3]
}
}
}
public extension MutableCollection {
/// SwifterSwift: Assign a given value to a field `keyPath` of all elements in the collection.
///
/// - Parameters:
/// - value: The new value of the field.
/// - keyPath: The actual field of the element.
mutating func assignToAll<Value>(value: Value, by keyPath: WritableKeyPath<Element, Value>) {
for idx in indices {
self[idx][keyPath: keyPath] = value
}
}
}
public extension RangeReplaceableCollection {
/// SwifterSwift: Creates a new collection of a given size where for each position of the collection the value will be the result of a call of the given expression.
///
/// let values = Array(expression: "Value", count: 3)
/// print(values)
/// // Prints "["Value", "Value", "Value"]"
///
/// - Parameters:
/// - expression: The expression to execute for each position of the collection.
/// - count: The count of the collection.
init(expression: @autoclosure () throws -> Element, count: Int) rethrows {
self.init()
// swiftlint:disable:next empty_count
if count > 0 {
reserveCapacity(count)
while self.count < count {
append(try expression())
}
}
}
}
// MARK: - Methods
public extension RangeReplaceableCollection {
/// SwifterSwift: Returns a new rotated collection by the given places.
///
/// [1, 2, 3, 4].rotated(by: 1) -> [4,1,2,3]
/// [1, 2, 3, 4].rotated(by: 3) -> [2,3,4,1]
/// [1, 2, 3, 4].rotated(by: -1) -> [2,3,4,1]
///
/// - Parameter places: Number of places that the array be rotated. If the value is positive the end becomes the start, if it negative it's that start become the end.
/// - Returns: The new rotated collection.
func rotated(by places: Int) -> Self {
// Inspired by: https://ruby-doc.org/core-2.2.0/Array.html#method-i-rotate
var copy = self
return copy.rotate(by: places)
}
/// SwifterSwift: Rotate the collection by the given places.
///
/// [1, 2, 3, 4].rotate(by: 1) -> [4,1,2,3]
/// [1, 2, 3, 4].rotate(by: 3) -> [2,3,4,1]
/// [1, 2, 3, 4].rotated(by: -1) -> [2,3,4,1]
///
/// - Parameter places: The number of places that the array should be rotated. If the value is positive the end becomes the start, if it negative it's that start become the end.
/// - Returns: self after rotating.
@discardableResult
mutating func rotate(by places: Int) -> Self {
guard places != 0 else { return self }
let placesToMove = places % count
if placesToMove > 0 {
let range = index(endIndex, offsetBy: -placesToMove)...
let slice = self[range]
removeSubrange(range)
insert(contentsOf: slice, at: startIndex)
} else {
let range = startIndex..<index(startIndex, offsetBy: -placesToMove)
let slice = self[range]
removeSubrange(range)
append(contentsOf: slice)
}
return self
}
/// SwifterSwift: Removes the first element of the collection which satisfies the given predicate.
///
/// [1, 2, 2, 3, 4, 2, 5].removeFirst { $0 % 2 == 0 } -> [1, 2, 3, 4, 2, 5]
/// ["h", "e", "l", "l", "o"].removeFirst { $0 == "e" } -> ["h", "l", "l", "o"]
///
/// - Parameter predicate: A closure that takes an element as its argument and returns a Boolean value that indicates whether the passed element represents a match.
/// - Returns: The first element for which predicate returns true, after removing it. If no elements in the collection satisfy the given predicate, returns `nil`.
@discardableResult
mutating func removeFirst(where predicate: (Element) throws -> Bool) rethrows -> Element? {
guard let index = try firstIndex(where: predicate) else { return nil }
return remove(at: index)
}
/// SwifterSwift: Remove a random value from the collection.
@discardableResult
mutating func removeRandomElement() -> Element? {
guard let randomIndex = indices.randomElement() else { return nil }
return remove(at: randomIndex)
}
/// SwifterSwift: Keep elements of Array while condition is true.
///
/// [0, 2, 4, 7].keep(while: { $0 % 2 == 0 }) -> [0, 2, 4]
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: self after applying provided condition.
/// - Throws: provided condition exception.
@discardableResult
mutating func keep(while condition: (Element) throws -> Bool) rethrows -> Self {
if let idx = try firstIndex(where: { try !condition($0) }) {
removeSubrange(idx...)
}
return self
}
/// SwifterSwift: Take element of Array while condition is true.
///
/// [0, 2, 4, 7, 6, 8].take( where: {$0 % 2 == 0}) -> [0, 2, 4]
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: All elements up until condition evaluates to false.
func take(while condition: (Element) throws -> Bool) rethrows -> Self {
return Self(try prefix(while: condition))
}
/// SwifterSwift: Skip elements of Array while condition is true.
///
/// [0, 2, 4, 7, 6, 8].skip( where: {$0 % 2 == 0}) -> [6, 8]
///
/// - Parameter condition: condition to evaluate each element against.
/// - Returns: All elements after the condition evaluates to false.
func skip(while condition: (Element) throws -> Bool) rethrows -> Self {
guard let idx = try firstIndex(where: { try !condition($0) }) else { return Self() }
return Self(self[idx...])
}
/// SwifterSwift: Remove all duplicate elements using KeyPath to compare.
///
/// - Parameter path: Key path to compare, the value must be Equatable.
mutating func removeDuplicates<E: Equatable>(keyPath path: KeyPath<Element, E>) {
var items = [Element]()
removeAll { element -> Bool in
guard items.contains(where: { $0[keyPath: path] == element[keyPath: path] }) else {
items.append(element)
return false
}
return true
}
}
/// SwifterSwift: Remove all duplicate elements using KeyPath to compare.
///
/// - Parameter path: Key path to compare, the value must be Hashable.
mutating func removeDuplicates<E: Hashable>(keyPath path: KeyPath<Element, E>) {
var set = Set<E>()
removeAll { !set.insert($0[keyPath: path]).inserted }
}
/// SwifterSwift: Accesses the element at the specified position.
///
/// - Parameter offset: The offset position of the element to access. `offset` must be a valid index offset of the collection that is not equal to the `endIndex` property.
subscript(offset: Int) -> Element {
get {
return self[index(startIndex, offsetBy: offset)]
}
set {
let offsetIndex = index(startIndex, offsetBy: offset)
replaceSubrange(offsetIndex..<index(after: offsetIndex), with: [newValue])
}
}
/// SwifterSwift: Accesses a contiguous subrange of the collection’s elements.
///
/// - Parameter range: A range of the collection’s indices offsets. The bounds of the range must be valid indices of the collection.
subscript<R>(range: R) -> SubSequence where R: RangeExpression, R.Bound == Int {
get {
let indexRange = range.relative(to: 0..<count)
return self[index(startIndex, offsetBy: indexRange.lowerBound)..<index(startIndex,
offsetBy: indexRange.upperBound)]
}
set {
let indexRange = range.relative(to: 0..<count)
replaceSubrange(
index(startIndex, offsetBy: indexRange.lowerBound)..<index(startIndex, offsetBy: indexRange.upperBound),
with: newValue)
}
}
/**
SwifterSwift: Adds a new element at the end of the array, mutates the array in place
- Parameter newElement: The optional element to append to the array
*/
mutating func appendIfNonNil(_ newElement: Element?) {
guard let newElement = newElement else { return }
self.append(newElement)
}
/**
SwifterSwift: Adds the elements of a sequence to the end of the array, mutates the array in place
- Parameter newElements: The optional sequence to append to the array
*/
mutating func appendIfNonNil<S>(contentsOf newElements: S?) where Element == S.Element, S : Sequence {
guard let newElements = newElements else { return }
self.append(contentsOf: newElements)
}
}
public extension BidirectionalCollection {
/// SwifterSwift: Returns the element at the specified position. If offset is negative, the `n`th element from the end will be returned where `n` is the result of `abs(distance)`.
///
/// let arr = [1, 2, 3, 4, 5]
/// arr[offset: 1] -> 2
/// arr[offset: -2] -> 4
///
/// - Parameter distance: The distance to offset.
subscript(offset distance: Int) -> Element {
let index = distance >= 0 ? startIndex : endIndex
return self[indices.index(index, offsetBy: distance)]
}
/// SwifterSwift: Returns the last element of the sequence with having property by given key path equals to given `value`.
///
/// - Parameters:
/// - keyPath: The `KeyPath` of property for `Element` to compare.
/// - value: The value to compare with `Element` property
/// - Returns: The last element of the collection that has property by given key path equals to given `value` or `nil` if there is no such element.
func last<T: Equatable>(where keyPath: KeyPath<Element, T>, equals value: T) -> Element? {
return last { $0[keyPath: keyPath] == value }
}
}
#if canImport(Foundation)
import Foundation
// MARK: - Methods
public extension BinaryFloatingPoint {
#if canImport(Foundation)
/// SwifterSwift: Returns a rounded value with the specified number of decimal places and rounding rule. If `numberOfDecimalPlaces` is negative, `0` will be used.
///
/// let num = 3.1415927
/// num.rounded(numberOfDecimalPlaces: 3, rule: .up) -> 3.142
/// num.rounded(numberOfDecimalPlaces: 3, rule: .down) -> 3.141
/// num.rounded(numberOfDecimalPlaces: 2, rule: .awayFromZero) -> 3.15
/// num.rounded(numberOfDecimalPlaces: 4, rule: .towardZero) -> 3.1415
/// num.rounded(numberOfDecimalPlaces: -1, rule: .toNearestOrEven) -> 3
///
/// - Parameters:
/// - numberOfDecimalPlaces: The expected number of decimal places.
/// - rule: The rounding rule to use.
/// - Returns: The rounded value.
func rounded(numberOfDecimalPlaces: Int, rule: FloatingPointRoundingRule) -> Self {
let factor = Self(pow(10.0, Double(max(0, numberOfDecimalPlaces))))
return (self * factor).rounded(rule) / factor
}
#endif
}
#endif
// MARK: - Properties
public extension BinaryInteger {
/// SwifterSwift: The raw bytes of the integer.
///
/// var number = Int16(-128)
/// print(number.bytes)
/// // prints "[255, 128]"
///
var bytes: [UInt8] {
var result = [UInt8]()
result.reserveCapacity(MemoryLayout<Self>.size)
var value = self
for _ in 0..<MemoryLayout<Self>.size {
result.append(UInt8(truncatingIfNeeded: value))
value >>= 8
}
return result.reversed()
}
}
// MARK: - Initializers
public extension BinaryInteger {
/// SwifterSwift: Creates a `BinaryInteger` from a raw byte representation.
///
/// var number = Int16(bytes: [0xFF, 0b1111_1101])
/// print(number!)
/// // prints "-3"
///
/// - Parameter bytes: An array of bytes representing the value of the integer.
init?(bytes: [UInt8]) {
// https://stackoverflow.com/a/43518567/9506784
precondition(bytes.count <= MemoryLayout<Self>.size,
"Integer with a \(bytes.count) byte binary representation of '\(bytes.map({ String($0, radix: 2) }).joined(separator: " "))' overflows when stored into a \(MemoryLayout<Self>.size) byte '\(Self.self)'")
var value: Self = 0
for byte in bytes {
value <<= 8
value |= Self(byte)
}
self.init(exactly: value)
}
}
// MARK: - Properties
public extension Bool {
/// SwifterSwift: Return 1 if true, or 0 if false.
///
/// false.int -> 0
/// true.int -> 1
///
var int: Int {
return self ? 1 : 0
}
/// SwifterSwift: Return "true" if true, or "false" if false.
///
/// false.string -> "false"
/// true.string -> "true"
///
var string: String {
return self ? "true" : "false"
}
}
public extension Character {
/// SwifterSwift: Check if character is emoji.
///
/// Character("😀").isEmoji -> true
///
var isEmoji: Bool {
// http://stackoverflow.com/questions/30757193/find-out-if-character-in-string-is-emoji
let scalarValue = String(self).unicodeScalars.first!.value
switch scalarValue {
case 0x1F600...0x1F64F, // Emoticons
0x1F300...0x1F5FF, // Misc Symbols and Pictographs
0x1F680...0x1F6FF, // Transport and Map
0x1F1E6...0x1F1FF, // Regional country flags
0x2600...0x26FF, // Misc symbols
0x2700...0x27BF, // Dingbats
0xE0020...0xE007F, // Tags
0xFE00...0xFE0F, // Variation Selectors
0x1F900...0x1F9FF, // Supplemental Symbols and Pictographs
127_000...127_600, // Various asian characters
65024...65039, // Variation selector
9100...9300, // Misc items
8400...8447: // Combining Diacritical Marks for Symbols
return true
default:
return false
}
}
/// SwifterSwift: Integer from character (if applicable).
///
/// Character("1").int -> 1
/// Character("A").int -> nil
///
var int: Int? {
return Int(String(self))
}
/// SwifterSwift: String from character.
///
/// Character("a").string -> "a"
///
var string: String {
return String(self)
}
/// SwifterSwift: Return the character lowercased.
///
/// Character("A").lowercased -> Character("a")
///
var lowercased: Character {
return String(self).lowercased().first!
}
/// SwifterSwift: Return the character uppercased.
///
/// Character("a").uppercased -> Character("A")
///
var uppercased: Character {
return String(self).uppercased().first!
}
}
// MARK: - Methods
public extension Character {
/// SwifterSwift: Random character.
///
/// Character.random() -> k
///
/// - Returns: A random character.
static func randomAlphanumeric() -> Character {
return "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".randomElement()!
}
}
// MARK: - Operators
public extension Character {
/// SwifterSwift: Repeat character multiple times.
///
/// Character("-") * 10 -> "----------"
///
/// - Parameters:
/// - lhs: character to repeat.
/// - rhs: number of times to repeat character.
/// - Returns: string with character repeated n times.
static func * (lhs: Character, rhs: Int) -> String {
guard rhs > 0 else { return "" }
return String(repeating: String(lhs), count: rhs)
}
/// SwifterSwift: Repeat character multiple times.
///
/// 10 * Character("-") -> "----------"
///
/// - Parameters:
/// - lhs: number of times to repeat character.
/// - rhs: character to repeat.
/// - Returns: string with character repeated n times.
static func * (lhs: Int, rhs: Character) -> String {
guard lhs > 0 else { return "" }
return String(repeating: String(rhs), count: lhs)
}
}
public extension Comparable {
/// SwifterSwift: Returns true if value is in the provided range.
///
/// 1.isBetween(5...7) // false
/// 7.isBetween(6...12) // true
/// date.isBetween(date1...date2)
/// "c".isBetween(a...d) // true
/// 0.32.isBetween(0.31...0.33) // true
///
/// - Parameter range: Closed range against which the value is checked to be included.
/// - Returns: `true` if the value is included in the range, `false` otherwise.
func isBetween(_ range: ClosedRange<Self>) -> Bool {
return range ~= self
}
/// SwifterSwift: Returns value limited within the provided range.
///
/// 1.clamped(to: 3...8) // 3
/// 4.clamped(to: 3...7) // 4
/// "c".clamped(to: "e"..."g") // "e"
/// 0.32.clamped(to: 0.1...0.29) // 0.29
///
/// - Parameter range: Closed range that limits the value.
/// - Returns: A value limited to the range, i.e. between `range.lowerBound` and `range.upperBound`.
func clamped(to range: ClosedRange<Self>) -> Self {
return max(range.lowerBound, min(self, range.upperBound))
}
}
#if canImport(Foundation)
import Foundation
#endif
public extension Decodable {
#if canImport(Foundation)
/// SwifterSwift: Parsing the model in Decodable type.
/// - Parameters:
/// - data: Data.
/// - decoder: JSONDecoder. Initialized by default.
init?(from data: Data, using decoder: JSONDecoder = .init()) {
guard let parsed = try? decoder.decode(Self.self, from: data) else { return nil }
self = parsed
}
#endif
}
#if canImport(Foundation)
import Foundation
#endif
// MARK: - Methods
public extension Dictionary {
/// SwifterSwift: Creates a Dictionary from a given sequence grouped by a given key path.
///
/// - Parameters:
/// - sequence: Sequence being grouped.
/// - keypath: The key path to group by.
init<S: Sequence>(grouping sequence: S, by keyPath: KeyPath<S.Element, Key>) where Value == [S.Element] {
self.init(grouping: sequence, by: { $0[keyPath: keyPath] })
}
/// SwifterSwift: Check if key exists in dictionary.
///
/// let dict: [String: Any] = ["testKey": "testValue", "testArrayKey": [1, 2, 3, 4, 5]]
/// dict.has(key: "testKey") -> true
/// dict.has(key: "anotherKey") -> false
///
/// - Parameter key: key to search for.
/// - Returns: true if key exists in dictionary.
func has(key: Key) -> Bool {
return index(forKey: key) != nil
}
/// SwifterSwift: Remove all keys contained in the keys parameter from the dictionary.
///
/// var dict : [String: String] = ["key1" : "value1", "key2" : "value2", "key3" : "value3"]
/// dict.removeAll(keys: ["key1", "key2"])
/// dict.keys.contains("key3") -> true
/// dict.keys.contains("key1") -> false
/// dict.keys.contains("key2") -> false
///
/// - Parameter keys: keys to be removed.
mutating func removeAll<S: Sequence>(keys: S) where S.Element == Key {
keys.forEach { removeValue(forKey: $0) }
}
/// SwifterSwift: Remove a value for a random key from the dictionary.
@discardableResult
mutating func removeValueForRandomKey() -> Value? {
guard let randomKey = keys.randomElement() else { return nil }
return removeValue(forKey: randomKey)
}
#if canImport(Foundation)
/// SwifterSwift: JSON Data from dictionary.
///
/// - Parameter prettify: set true to prettify data (default is false).
/// - Returns: optional JSON Data (if applicable).
func jsonData(prettify: Bool = false) -> Data? {
guard JSONSerialization.isValidJSONObject(self) else {
return nil
}
let options = (prettify == true) ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization
.WritingOptions()
return try? JSONSerialization.data(withJSONObject: self, options: options)
}
#endif
#if canImport(Foundation)
/// SwifterSwift: JSON String from dictionary.
///
/// dict.jsonString() -> "{"testKey":"testValue","testArrayKey":[1,2,3,4,5]}"
///
/// dict.jsonString(prettify: true)
/// /*
/// returns the following string:
///
/// "{
/// "testKey" : "testValue",
/// "testArrayKey" : [
/// 1,
/// 2,
/// 3,
/// 4,
/// 5
/// ]
/// }"
///
/// */
///
/// - Parameter prettify: set true to prettify string (default is false).
/// - Returns: optional JSON String (if applicable).
func jsonString(prettify: Bool = false) -> String? {
guard JSONSerialization.isValidJSONObject(self) else { return nil }
let options = (prettify == true) ? JSONSerialization.WritingOptions.prettyPrinted : JSONSerialization
.WritingOptions()
guard let jsonData = try? JSONSerialization.data(withJSONObject: self, options: options) else { return nil }
return String(data: jsonData, encoding: .utf8)
}
#endif
/// SwifterSwift: Returns a dictionary containing the results of mapping the given closure over the sequence’s elements.
/// - Parameter transform: A mapping closure. `transform` accepts an element of this sequence as its parameter and returns a transformed value of the same or of a different type.
/// - Returns: A dictionary containing the transformed elements of this sequence.
func mapKeysAndValues<K, V>(_ transform: ((key: Key, value: Value)) throws -> (K, V)) rethrows -> [K: V] {
return [K: V](uniqueKeysWithValues: try map(transform))
}
/// SwifterSwift: Returns a dictionary containing the non-`nil` results of calling the given transformation with each element of this sequence.
/// - Parameter transform: A closure that accepts an element of this sequence as its argument and returns an optional value.
/// - Returns: A dictionary of the non-`nil` results of calling `transform` with each element of the sequence.
/// - Complexity: *O(m + n)*, where _m_ is the length of this sequence and _n_ is the length of the result.
func compactMapKeysAndValues<K, V>(_ transform: ((key: Key, value: Value)) throws -> (K, V)?) rethrows -> [K: V] {
return [K: V](uniqueKeysWithValues: try compactMap(transform))
}
/// SwifterSwift: Creates a new dictionary using specified keys.
///
/// var dict = ["key1": 1, "key2": 2, "key3": 3, "key4": 4]
/// dict.pick(keys: ["key1", "key3", "key4"]) -> ["key1": 1, "key3": 3, "key4": 4]
/// dict.pick(keys: ["key2"]) -> ["key2": 2]
///
/// - Complexity: O(K), where _K_ is the length of the keys array.
///
/// - Parameter keys: An array of keys that will be the entries in the resulting dictionary.
///
/// - Returns: A new dictionary that contains the specified keys only. If none of the keys exist, an empty dictionary will be returned.
func pick(keys: [Key]) -> [Key: Value] {
keys.reduce(into: [Key: Value]()) { result, item in
result[item] = self[item]
}
}
}
// MARK: - Methods (Value: Equatable)
public extension Dictionary where Value: Equatable {
/// SwifterSwift: Returns an array of all keys that have the given value in dictionary.
///
/// let dict = ["key1": "value1", "key2": "value1", "key3": "value2"]
/// dict.keys(forValue: "value1") -> ["key1", "key2"]
/// dict.keys(forValue: "value2") -> ["key3"]
/// dict.keys(forValue: "value3") -> []
///
/// - Parameter value: Value for which keys are to be fetched.
/// - Returns: An array containing keys that have the given value.
func keys(forValue value: Value) -> [Key] {
return keys.filter { self[$0] == value }
}
}
// MARK: - Methods (ExpressibleByStringLiteral)
public extension Dictionary where Key: StringProtocol {
/// SwifterSwift: Lowercase all keys in dictionary.
///
/// var dict = ["tEstKeY": "value"]
/// dict.lowercaseAllKeys()
/// print(dict) // prints "["testkey": "value"]"
///
mutating func lowercaseAllKeys() {
// http://stackoverflow.com/questions/33180028/extend-dictionary-where-key-is-of-type-string
for key in keys {
if let lowercaseKey = String(describing: key).lowercased() as? Key {
self[lowercaseKey] = removeValue(forKey: key)
}
}
}
}
// MARK: - Subscripts
public extension Dictionary {
/// SwifterSwift: Deep fetch or set a value from nested dictionaries.
///
/// var dict = ["key": ["key1": ["key2": "value"]]]
/// dict[path: ["key", "key1", "key2"]] = "newValue"
/// dict[path: ["key", "key1", "key2"]] -> "newValue"
///
/// - Note: Value fetching is iterative, while setting is recursive.
///
/// - Complexity: O(N), _N_ being the length of the path passed in.
///
/// - Parameter path: An array of keys to the desired value.
///
/// - Returns: The value for the key-path passed in. `nil` if no value is found.
subscript(path path: [Key]) -> Any? {
get {
guard !path.isEmpty else { return nil }
var result: Any? = self
for key in path {
if let element = (result as? [Key: Any])?[key] {
result = element
} else {
return nil
}
}
return result
}
set {
if let first = path.first {
if path.count == 1, let new = newValue as? Value {
return self[first] = new
}
if var nested = self[first] as? [Key: Any] {
nested[path: Array(path.dropFirst())] = newValue
return self[first] = nested as? Value
}
}
}
}
}
// MARK: - Operators
public extension Dictionary {
/// SwifterSwift: Merge the keys/values of two dictionaries.
///
/// let dict: [String: String] = ["key1": "value1"]
/// let dict2: [String: String] = ["key2": "value2"]
/// let result = dict + dict2
/// result["key1"] -> "value1"
/// result["key2"] -> "value2"
///
/// - Parameters:
/// - lhs: dictionary.
/// - rhs: dictionary.
/// - Returns: An dictionary with keys and values from both.
static func + (lhs: [Key: Value], rhs: [Key: Value]) -> [Key: Value] {
var result = lhs
rhs.forEach { result[$0] = $1 }
return result
}
// MARK: - Operators
/// SwifterSwift: Append the keys and values from the second dictionary into the first one.
///
/// var dict: [String: String] = ["key1": "value1"]
/// let dict2: [String: String] = ["key2": "value2"]
/// dict += dict2
/// dict["key1"] -> "value1"
/// dict["key2"] -> "value2"
///
/// - Parameters:
/// - lhs: dictionary.
/// - rhs: dictionary.
static func += (lhs: inout [Key: Value], rhs: [Key: Value]) {
rhs.forEach { lhs[$0] = $1 }
}
/// SwifterSwift: Remove keys contained in the sequence from the dictionary.
///
/// let dict: [String: String] = ["key1": "value1", "key2": "value2", "key3": "value3"]
/// let result = dict-["key1", "key2"]
/// result.keys.contains("key3") -> true
/// result.keys.contains("key1") -> false
/// result.keys.contains("key2") -> false
///
/// - Parameters:
/// - lhs: dictionary.
/// - keys: array with the keys to be removed.
/// - Returns: a new dictionary with keys removed.
static func - <S: Sequence>(lhs: [Key: Value], keys: S) -> [Key: Value] where S.Element == Key {
var result = lhs
result.removeAll(keys: keys)
return result
}
/// SwifterSwift: Remove keys contained in the sequence from the dictionary.
///
/// var dict: [String: String] = ["key1": "value1", "key2": "value2", "key3": "value3"]
/// dict-=["key1", "key2"]
/// dict.keys.contains("key3") -> true
/// dict.keys.contains("key1") -> false
/// dict.keys.contains("key2") -> false
///
/// - Parameters:
/// - lhs: dictionary.
/// - keys: array with the keys to be removed.
static func -= <S: Sequence>(lhs: inout [Key: Value], keys: S) where S.Element == Key {
lhs.removeAll(keys: keys)
}
}
#if canImport(CoreGraphics)
import CoreGraphics
#endif
#if os(macOS) || os(iOS)
import Darwin
#elseif os(Linux)
import Glibc
#endif
// MARK: - Properties
public extension Double {
/// SwifterSwift: Int.
var int: Int {
return Int(self)
}
/// SwifterSwift: Float.
var float: Float {
return Float(self)
}
#if canImport(CoreGraphics)
/// SwifterSwift: CGFloat.
var cgFloat: CGFloat {
return CGFloat(self)
}
#endif
}
// MARK: - Operators
precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence }
infix operator **: PowerPrecedence
/// SwifterSwift: Value of exponentiation.
///
/// - Parameters:
/// - lhs: base double.
/// - rhs: exponent double.
/// - Returns: exponentiation result (example: 4.4 ** 0.5 = 2.0976176963).
public func ** (lhs: Double, rhs: Double) -> Double {
// http://nshipster.com/swift-operators/
return pow(lhs, rhs)
}
#if canImport(CoreGraphics)
import CoreGraphics
#endif
#if os(macOS) || os(iOS)
import Darwin
#elseif os(Linux)
import Glibc
#endif
// MARK: - Properties
public extension Float {
/// SwifterSwift: Int.
var int: Int {
return Int(self)
}
/// SwifterSwift: Double.
var double: Double {
return Double(self)
}
#if canImport(CoreGraphics)
/// SwifterSwift: CGFloat.
var cgFloat: CGFloat {
return CGFloat(self)
}
#endif
}
// MARK: - Operators
precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence }
infix operator **: PowerPrecedence
/// SwifterSwift: Value of exponentiation.
///
/// - Parameters:
/// - lhs: base float.
/// - rhs: exponent float.
/// - Returns: exponentiation result (4.4 ** 0.5 = 2.0976176963).
public func ** (lhs: Float, rhs: Float) -> Float {
// http://nshipster.com/swift-operators/
return pow(lhs, rhs)
}
#if canImport(Foundation)
import Foundation
#endif
// MARK: - Properties
public extension FloatingPoint {
/// SwifterSwift: Absolute value of number.
var abs: Self {
return Swift.abs(self)
}
/// SwifterSwift: Check if number is positive.
var isPositive: Bool {
return self > 0
}
/// SwifterSwift: Check if number is negative.
var isNegative: Bool {
return self < 0
}
#if canImport(Foundation)
/// SwifterSwift: Ceil of number.
var ceil: Self {
return Foundation.ceil(self)
}
#endif
/// SwifterSwift: Radian value of degree input.
var degreesToRadians: Self {
return Self.pi * self / Self(180)
}
#if canImport(Foundation)
/// SwifterSwift: Floor of number.
var floor: Self {
return Foundation.floor(self)
}
#endif
/// SwifterSwift: Degree value of radian input.
var radiansToDegrees: Self {
return self * Self(180) / Self.pi
}
}
// MARK: - Operators
// swiftlint:disable identifier_name
infix operator ±
/// SwifterSwift: Tuple of plus-minus operation.
///
/// - Parameters:
/// - lhs: number.
/// - rhs: number.
/// - Returns: tuple of plus-minus operation ( 2.5 ± 1.5 -> (4, 1)).
public func ± <T: FloatingPoint>(lhs: T, rhs: T) -> (T, T) {
// http://nshipster.com/swift-operators/
return (lhs + rhs, lhs - rhs)
}
// swiftlint:enable identifier_name
// swiftlint:disable identifier_name
prefix operator ±
/// SwifterSwift: Tuple of plus-minus operation.
///
/// - Parameter int: number.
/// - Returns: tuple of plus-minus operation (± 2.5 -> (2.5, -2.5)).
public prefix func ± <T: FloatingPoint>(number: T) -> (T, T) {
// http://nshipster.com/swift-operators/
return 0 ± number
}
// swiftlint:enable identifier_name
// swiftlint:disable identifier_name
prefix operator √
/// SwifterSwift: Square root of float.
///
/// - Parameter float: float value to find square root for.
/// - Returns: square root of given float.
public prefix func √ <T>(float: T) -> T where T: FloatingPoint {
// http://nshipster.com/swift-operators/
return sqrt(float)
}
// swiftlint:enable identifier_name
#if canImport(CoreGraphics)
import CoreGraphics
#endif
#if os(macOS) || os(iOS)
import Darwin
#elseif os(Linux)
import Glibc
#endif
// MARK: - Properties
public extension Int {
/// SwifterSwift: CountableRange 0..<Int.
var countableRange: CountableRange<Int> {
return 0..<self
}
/// SwifterSwift: Radian value of degree input.
var degreesToRadians: Double {
return Double.pi * Double(self) / 180.0
}
/// SwifterSwift: Degree value of radian input.
var radiansToDegrees: Double {
return Double(self) * 180 / Double.pi
}
/// SwifterSwift: UInt.
var uInt: UInt {
return UInt(self)
}
/// SwifterSwift: Double.
var double: Double {
return Double(self)
}
/// SwifterSwift: Float.
var float: Float {
return Float(self)
}
#if canImport(CoreGraphics)
/// SwifterSwift: CGFloat.
var cgFloat: CGFloat {
return CGFloat(self)
}
#endif
/// SwifterSwift: String formatted for values over ±1000 (example: 1k, -2k, 100k, 1kk, -5kk..).
var kFormatted: String {
var sign: String {
return self >= 0 ? "" : "-"
}
let abs = Swift.abs(self)
if abs == 0 {
return "0k"
} else if abs >= 0, abs < 1000 {
return "0k"
} else if abs >= 1000, abs < 1_000_000 {
return String(format: "\(sign)%ik", abs / 1000)
}
return String(format: "\(sign)%ikk", abs / 100_000)
}
/// SwifterSwift: Array of digits of integer value.
var digits: [Int] {
guard self != 0 else { return [0] }
var digits = [Int]()
var number = abs
while number != 0 {
let xNumber = number % 10
digits.append(xNumber)
number /= 10
}
digits.reverse()
return digits
}
/// SwifterSwift: Number of digits of integer value.
var digitsCount: Int {
guard self != 0 else { return 1 }
let number = Double(abs)
return Int(log10(number) + 1)
}
}
// MARK: - Methods
public extension Int {
/// SwifterSwift: check if given integer prime or not. Warning: Using big numbers can be computationally expensive!
/// - Returns: true or false depending on prime-ness.
func isPrime() -> Bool {
// To improve speed on latter loop :)
if self == 2 { return true }
guard self > 1, self % 2 != 0 else { return false }
// Explanation: It is enough to check numbers until
// the square root of that number. If you go up from N by one,
// other multiplier will go 1 down to get similar result
// (integer-wise operation) such way increases speed of operation
let base = Int(sqrt(Double(self)))
for int in Swift.stride(from: 3, through: base, by: 2) where self % int == 0 {
return false
}
return true
}
/// SwifterSwift: Roman numeral string from integer (if applicable).
///
/// 10.romanNumeral() -> "X"
///
/// - Returns: The roman numeral string.
func romanNumeral() -> String? {
// https://gist.github.com/kumo/a8e1cb1f4b7cff1548c7
guard self > 0 else { // there is no roman numeral for 0 or negative numbers
return nil
}
let romanValues = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
let arabicValues = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
var romanValue = ""
var startingValue = self
for (index, romanChar) in romanValues.enumerated() {
let arabicValue = arabicValues[index]
let div = startingValue / arabicValue
for _ in 0..<div {
romanValue.append(romanChar)
}
startingValue -= arabicValue * div
}
return romanValue
}
/// SwifterSwift: Rounds to the closest multiple of n.
func roundToNearest(_ number: Int) -> Int {
return number == 0 ? self : Int(round(Double(self) / Double(number))) * number
}
}
// MARK: - Operators
precedencegroup PowerPrecedence { higherThan: MultiplicationPrecedence }
infix operator **: PowerPrecedence
/// SwifterSwift: Value of exponentiation.
///
/// - Parameters:
/// - lhs: base integer.
/// - rhs: exponent integer.
/// - Returns: exponentiation result (example: 2 ** 3 = 8).
public func ** (lhs: Int, rhs: Int) -> Double {
// http://nshipster.com/swift-operators/
return pow(Double(lhs), Double(rhs))
}
// swiftlint:disable identifier_name
prefix operator √
/// SwifterSwift: Square root of integer.
///
/// - Parameter int: integer value to find square root for.
/// - Returns: square root of given integer.
public prefix func √ (int: Int) -> Double {
// http://nshipster.com/swift-operators/
return sqrt(Double(int))
}
// swiftlint:enable identifier_name
// swiftlint:disable identifier_name
infix operator ±
/// SwifterSwift: Tuple of plus-minus operation.
///
/// - Parameters:
/// - lhs: integer number.
/// - rhs: integer number.
/// - Returns: tuple of plus-minus operation (example: 2 ± 3 -> (5, -1)).
public func ± (lhs: Int, rhs: Int) -> (Int, Int) {
// http://nshipster.com/swift-operators/
return (lhs + rhs, lhs - rhs)
}
// swiftlint:enable identifier_name
// swiftlint:disable identifier_name
prefix operator ±
/// SwifterSwift: Tuple of plus-minus operation.
///
/// - Parameter int: integer number.
/// - Returns: tuple of plus-minus operation (example: ± 2 -> (2, -2)).
public prefix func ± (int: Int) -> (Int, Int) {
// http://nshipster.com/swift-operators/
return (int, -int)
}
// swiftlint:enable identifier_name
#if canImport(Foundation)
import Foundation
#endif
// MARK: - Properties
public extension SignedInteger {
/// SwifterSwift: Absolute value of integer number.
var abs: Self {
return Swift.abs(self)
}
/// SwifterSwift: Check if integer is positive.
var isPositive: Bool {
return self > 0
}
/// SwifterSwift: Check if integer is negative.
var isNegative: Bool {
return self < 0
}
/// SwifterSwift: Check if integer is even.
var isEven: Bool {
return (self % 2) == 0
}
/// SwifterSwift: Check if integer is odd.
var isOdd: Bool {
return (self % 2) != 0
}
/// SwifterSwift: String of format (XXh XXm) from seconds Int.
var timeString: String {
guard self > 0 else {
return "0 sec"
}
if self < 60 {
return "\(self) sec"
}
if self < 3600 {
return "\(self / 60) min"
}
let hours = self / 3600
let mins = (self % 3600) / 60
if hours != 0, mins == 0 {
return "\(hours)h"
}
return "\(hours)h \(mins)m"
}
}
// MARK: - Methods
public extension SignedInteger {
/// SwifterSwift: Greatest common divisor of integer value and n.
///
/// - Parameter number: integer value to find gcd with.
/// - Returns: greatest common divisor of self and n.
func gcd(of number: Self) -> Self {
return number == 0 ? self : number.gcd(of: self % number)
}
/// SwifterSwift: Least common multiple of integer and n.
///
/// - Parameter number: integer value to find lcm with.
/// - Returns: least common multiple of self and n.
func lcm(of number: Self) -> Self {
return (self * number).abs / gcd(of: number)
}
#if canImport(Foundation)
/// SwifterSwift: Ordinal representation of an integer.
///
/// print((12).ordinalString()) // prints "12th"
///
/// - Parameter locale: locale, default is .current.
/// - Returns: string ordinal representation of number in specified locale language. E.g. input 92, output in "en": "92nd".
@available(macOS 10.11, *)
func ordinalString(locale: Locale = .current) -> String? {
let formatter = NumberFormatter()
formatter.locale = locale
formatter.numberStyle = .ordinal
guard let number = self as? NSNumber else { return nil }
return formatter.string(from: number)
}
#endif
}
#if canImport(Foundation)
import Foundation
#endif
// MARK: - Properties
public extension SignedNumeric {
/// SwifterSwift: String.
var string: String {
return String(describing: self)
}
#if canImport(Foundation)
/// SwifterSwift: String with number and current locale currency.
var asLocaleCurrency: String? {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale.current
// swiftlint:disable:next force_cast
return formatter.string(from: self as! NSNumber)
}
#endif
}
// MARK: - Methods
public extension SignedNumeric {
#if canImport(Foundation)
/// SwifterSwift: Spelled out representation of a number.
///
/// print((12.32).spelledOutString()) // prints "twelve point three two"
///
/// - Parameter locale: Locale, default is .current.
/// - Returns: String representation of number spelled in specified locale language. E.g. input 92, output in "en": "ninety-two".
func spelledOutString(locale: Locale = .current) -> String? {
let formatter = NumberFormatter()
formatter.locale = locale
formatter.numberStyle = .spellOut
guard let number = self as? NSNumber else { return nil }
return formatter.string(from: number)
}
#endif
}
#if canImport(Foundation)
import Foundation
#endif
public extension KeyedDecodingContainer {
#if canImport(Foundation)
/// SwifterSwift: Try to decode a Bool as Int then String before decoding as Bool.
///
/// - Parameter key: Key.
/// - Returns: Decoded Bool value.
/// - Throws: Decoding error.
func decodeBoolAsIntOrString(forKey key: Key) throws -> Bool {
if let intValue = try? decode(Int.self, forKey: key) {
return (intValue as NSNumber).boolValue
} else if let stringValue = try? decode(String.self, forKey: key) {
return (stringValue as NSString).boolValue
} else {
return try decode(Bool.self, forKey: key)
}
}
#endif
#if canImport(Foundation)
/// SwifterSwift: Try to decode a Bool as Int then String before decoding as Bool if present.
///
/// - Parameter key: Key.
/// - Returns: Decoded Bool value.
/// - Throws: Decoding error.
func decodeBoolAsIntOrStringIfPresent(forKey key: Key) throws -> Bool? {
if let intValue = try? decodeIfPresent(Int.self, forKey: key) {
return (intValue as NSNumber).boolValue
} else if let stringValue = try? decodeIfPresent(String.self, forKey: key) {
return (stringValue as NSString).boolValue
} else {
return try decodeIfPresent(Bool.self, forKey: key)
}
}
#endif
}
import Foundation
// MARK: - Data extension
/// This extension add some useful functions to Data.
public extension Data {
// MARK: - Functions
/// Convert self to a UTF8 String.
///
/// - Returns: Returns self as UTF8 NSString.
func utf8() -> String? {
String(data: self, encoding: .utf8)
}
/// Convert self to a ASCII String.
///
/// - Returns: Returns self as ASCII String.
func ascii() -> String? {
String(data: self, encoding: .ascii)
}
/// Convert self UUID to String.
///
/// Useful for push notifications.
///
/// - Returns: Returns self as String from UUID.
func readableUUID() -> String {
description.trimmingCharacters(in: CharacterSet(charactersIn: "<>")).replacingOccurrences(of: " ", with: "").replacingOccurrences(of: "-", with: "")
}
}
import Foundation
// MARK: - Date extension
/// This extension add some useful functions to Date.
public extension Date {
// MARK: - Variables
/// Set and get current year.
var year: Int {
get {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
return calendar.component(.year, from: self)
}
set {
update(components: [.year: newValue])
}
}
/// Set and get current month.
var month: Int {
get {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
return calendar.component(.month, from: self)
}
set {
update(components: [.month: newValue])
}
}
/// Set and get current day.
var day: Int {
get {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
return calendar.component(.day, from: self)
}
set {
update(components: [.day: newValue])
}
}
/// Set and get current hour.
var hour: Int {
get {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
return calendar.component(.hour, from: self)
}
set {
update(components: [.hour: newValue])
}
}
/// Set and get current minute.
var minute: Int {
get {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
return calendar.component(.minute, from: self)
}
set {
update(components: [.minute: newValue])
}
}
/// Set and get current second.
var second: Int {
get {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
return calendar.component(.second, from: self)
}
set {
update(components: [.second: newValue])
}
}
/// Get current nanosecond.
var nanosecond: Int {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
return calendar.component(.nanosecond, from: self)
}
/// Get the weekday number from
/// - 1 - Sunday.
/// - 2 - Monday.
/// - 3 - Tuerday.
/// - 4 - Wednesday.
/// - 5 - Thursday.
/// - 6 - Friday.
/// - 7 - Saturday.
var weekday: Int {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
return calendar.component(.weekday, from: self)
}
/// components
func component(_ unit: Calendar.Component) -> Int {
let components = systemCalendar.dateComponents([unit], from: self)
return components.value(for: unit)!
}
/// addComponent
func addComponent(_ value: Int, unit: Calendar.Component) -> Date {
var dateComponents = DateComponents()
dateComponents.setValue(value, for: unit)
return systemCalendar.date(byAdding: dateComponents, to: self)!
}
/// toDateComponents
static func toDateComponents(_ timeInterval: TimeInterval, unit: Calendar.Component) -> DateComponents {
let date1 = Date()
let date2 = Date(timeInterval: timeInterval, since: date1)
return systemCalendar.dateComponents([unit], from: date1, to: date2)
}
/// timeInterval
func timeInterval(_ date: Date, unit: Calendar.Component) -> Int {
let interval = timeIntervalSince(date)
let components = Date.toDateComponents(interval, unit: unit)
return components.value(for: unit)!
}
/// Editable date components.
///
/// - year: Year component.
/// - month: Month component.
/// - day: Day component.
/// - hour: Hour component.
/// - minute: Minute component.
/// - second: Second component.
enum EditableDateComponents: Int {
case year
case month
case day
case hour
case minute
case second
}
// MARK: - Functions
/// Update current Date components.
///
/// - Parameters:
/// - components: Dictionary of components and values to be updated.
mutating func update(components: [EditableDateComponents: Int]) {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
var dateComponents = calendar.dateComponents([.year, .month, .day, .weekday, .hour, .minute, .second, .nanosecond], from: self)
for (component, value) in components {
switch component {
case .year:
dateComponents.year = value
case .month:
dateComponents.month = value
case .day:
dateComponents.day = value
case .hour:
dateComponents.hour = value
case .minute:
dateComponents.minute = value
case .second:
dateComponents.second = value
}
}
guard let date = calendar.date(from: dateComponents) else {
return
}
self = date
}
/// Creates a Date object from year, month and day as Int.
///
/// - Parameters:
/// - year: Year.
/// - month: Month.
/// - day: Day.
/// - hour: Hour.
/// - minute: Minute.
/// - second: Second.
init?(year: Int, month: Int, day: Int, hour: Int = 0, minute: Int = 0, second: Int = 0) {
var components = DateComponents()
components.year = year
components.month = month
components.day = day
components.hour = hour
components.minute = minute
components.second = second
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
guard let date = calendar.date(from: components) else {
return nil
}
self = date
}
/// Creates a Date object from a date String in a given format.
///
/// - Parameters:
/// - dateString: Date String.
/// - format: Date String format. Default is "yyyy-MM-dd". Example: "2014-05-20".
/// - locale: Locale, default is "en_US_POSIX". You can use Locale.current.identifier.
init?(parse dateString: String, format: String = "yyyy-MM-dd", locale: String = "en_US_POSIX") {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
let dateFormatter = DateFormatter()
dateFormatter.calendar = calendar
dateFormatter.locale = Locale(identifier: locale)
dateFormatter.timeZone = TimeZone.current
dateFormatter.dateFormat = format
guard let parsed = dateFormatter.date(from: dateString) else {
return nil
}
self = parsed
}
/// Create a Date with other two Date objects.
/// Taken from the first date: day, month and year.
/// Taken from the second date: hours and minutes.
///
/// - Parameters:
/// - date: The first date for date.
/// - time: The second date for time.
/// - dateSeparator: Date separator, default is "-".
/// - timeSeparator: Time separator, default is ":".
init?(date: Date, time: Date, dateSeparator: String = "-", timeSeparator: String = ":") {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy\(dateSeparator)MM\(dateSeparator)dd"
let datePortion: String = dateFormatter.string(from: date)
dateFormatter.dateFormat = "HH\(timeSeparator)mm"
let timePortion: String = dateFormatter.string(from: time)
let dateTime = "\(datePortion) \(timePortion)"
dateFormatter.dateFormat = "yyyy\(dateSeparator)MM\(dateSeparator)dd HH\(timeSeparator)mm"
guard let parsed = dateFormatter.date(from: dateTime) else {
return nil
}
self = parsed
}
/// Create an ISO 8601 date from a String.
///
/// - Parameter date: ISO 8601 String.
init?(iso8601: String) {
guard let date = Date(parse: iso8601, format: "yyyy-MM-dd'T'HH:mm:ss.SSSZ") else {
return nil
}
self = date
}
#if !os(Linux)
/// Creates an ISO 8601 String form
///
/// - Returns: Returns an ISO 8601 String form
func iso8601() -> String {
let dateFormatter = DateFormatter()
dateFormatter.calendar = Calendar(identifier: .iso8601)
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
return dateFormatter.string(from: self)
}
#endif
/// Get the months number between self and another date.
///
/// - Parameter toDate: The another date.
/// - Returns: Returns the months between the two dates.
func monthsBetween(_ toDate: Date) -> Int {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
let components = calendar.dateComponents([.month], from: self, to: toDate)
guard let month = components.month else {
return 0
}
return abs(month)
}
/// Get the days number between self and another date.
///
/// - Parameter anotherDate: The another date.
/// - Returns: Returns the days between the two dates.
func daysBetween(_ anotherDate: Date) -> Int {
let time: TimeInterval = timeIntervalSince(anotherDate)
return Int(abs(time / 60 / 60 / 24))
}
/// Returns if self is today.
///
/// - Returns: Returns if self is today.
func isToday() -> Bool {
isSame(Date())
}
/// Compare self with another date.
///
/// - Parameter anotherDate: The another date to compare as Date.
/// - Returns: Returns true if is same day, otherwise false.
func isSame(_ anotherDate: Date) -> Bool {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
let componentsSelf = calendar.dateComponents([.year, .month, .day], from: self)
let componentsAnotherDate = calendar.dateComponents([.year, .month, .day], from: anotherDate)
return componentsSelf.year == componentsAnotherDate.year && componentsSelf.month == componentsAnotherDate.month && componentsSelf.day == componentsAnotherDate.day
}
/// Add days to
///
/// - Parameter days: The number of days to add.
/// - Returns: Returns self by adding the gived days number.
func addingDays(_ days: Int) -> Date? {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
return calendar.date(byAdding: .day, value: days, to: self)
}
/// Add days to
///
/// - Parameter days: The number of days to add.
mutating func addDays(_ days: Int) {
guard let date = addingDays(days) else {
return
}
self = date
}
/// Get the year string from
///
/// - Returns: Returns the year string from
func yearString() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy"
return dateFormatter.string(from: self)
}
/// Get the String date from
///
/// - Parameters:
/// - format: Date format, default is "yyyy-MM-dd".
/// - locale: Locale, default is "en_US_POSIX".
/// - Returns: Returns the String data from
func dateString(format: String = "yyyy-MM-dd", locale: String = "en_US_POSIX") -> String {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: locale)
dateFormatter.dateFormat = format
return dateFormatter.string(from: self)
}
/// Returns date with the year, month and day only.
///
/// - Returns: Date after removing all components but not year, month and day.
func shortDate() -> Date {
#if os(Linux)
let calendar = Calendar(identifier: .gregorian)
#else
let calendar = Calendar.autoupdatingCurrent
#endif
let components = calendar.dateComponents([.year, .month, .day], from: self)
guard let date = calendar.date(from: components) else {
return self
}
return date
}
/// Check if the given date is less than
///
/// - Parameter date: Date to compare.
/// - Returns: Returns a true if self is greater than another one, otherwise false.
func isGreaterThan(_ date: Date) -> Bool {
var isGreater = false
if compare(date) == ComparisonResult.orderedDescending {
isGreater = true
}
return isGreater
}
/// Check if the given date is greater than
///
/// - Parameter date: Date to compare.
/// - Returns: Returns a true if self is less than another one, otherwise false.
func isLessThan(_ date: Date) -> Bool {
var isLess = false
if compare(date) == ComparisonResult.orderedAscending {
isLess = true
}
return isLess
}
/// Just an alias for `isSame(_ anotherDate: Date)`.
///
/// - Parameter date: Date to compare.
/// - Returns: Returns a true if self is equal to another one, otherwise false.
func isEqual(_ date: Date) -> Bool {
isSame(date)
}
/// Create a Date with the yesterday date.
///
/// - Returns: Returns a Date with the yesterday date.
func yesterday() -> Date {
var date = self
date.day -= 1
return date
}
/// Get weekday as a localized string from current weekday number.
///
/// - Returns: Return weekday as a localized string.
func localizedWeekday() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEEE"
if let locale = NSLocale.preferredLanguages.first {
dateFormatter.locale = Locale(identifier: locale)
}
return dateFormatter.string(from: self).capitalized
}
/// Get month as a localized string from current month.
///
/// - Returns: Returns the given month as a localized string.
func localizedMonth() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM"
if let locale = NSLocale.preferredLanguages.first {
dateFormatter.locale = Locale(identifier: locale)
}
return dateFormatter.string(from: self).capitalized
}
/// Get the given Date structure as a formatted string.
///
/// - Parameters:
/// - info: The Date to be formatted.
/// - dateSeparator: The string to be used as date separator. (Currently does not work on Linux).
/// - usFormat: Set if the timestamp is in US format or not.
/// - nanosecond: Set if the timestamp has to have the nanosecond.
/// - Returns: Returns a String in the following format (dateSeparator = "/", usFormat to false and nanosecond to false). D/M/Y H:M:S. Example: 15/10/2013 10:38:43.
func description(dateSeparator: String = "/", usFormat: Bool = false, nanosecond: Bool = false) -> String {
var description: String
#if os(Linux)
if usFormat {
description = String(format: "%04li-%02li-%02li %02li:%02li:%02li", year, month, day, hour, minute, second)
} else {
description = String(format: "%02li-%02li-%04li %02li:%02li:%02li", month, day, year, hour, minute, second)
}
#else
if usFormat {
description = String(format: "%04li%@%02li%@%02li %02li:%02li:%02li", year, dateSeparator, month, dateSeparator, day, hour, minute, second)
} else {
description = String(format: "%02li%@%02li%@%04li %02li:%02li:%02li", month, dateSeparator, day, dateSeparator, year, hour, minute, second)
}
#endif
if nanosecond {
description += String(format: ":%03li", self.nanosecond / 1_000_000)
}
return description
}
}
import Foundation
// MARK: - Dictionary extension
/// This extension adds some useful functions to Dictionary.
public extension Dictionary {
// MARK: - Functions
/// Append a Value for a given Key in the Dictionary.
/// If the Key already exist it will be ovrewritten.
///
/// - Parameters:
/// - value: Value to be added.
/// - key: Key to be added.
mutating func append(_ value: Value, forKey key: Key) {
self[key] = value
}
}
import Foundation
// MARK: - FileManager extension
/// This extension adds some useful functions to FileManager.
public extension FileManager {
// MARK: - Variables
/// Path type enum.
enum PathType: Int {
/// Main bundle path.
case mainBundle
/// Library path.
case library
/// Documents path.
case documents
/// Cache path.
case cache
/// Application Support path.
case applicationSupport
/// Temporary path.
case temporary
}
// MARK: - Functions
/// Get the path for a PathType.
///
/// - Parameter path: Path type.
/// - Returns: Returns the path type String.
func pathFor(_ path: PathType) -> String? {
var pathString: String?
switch path {
case .mainBundle:
pathString = mainBundlePath()
case .library:
pathString = libraryPath()
case .documents:
pathString = documentsPath()
case .cache:
pathString = cachePath()
case .applicationSupport:
pathString = applicationSupportPath()
case .temporary:
pathString = temporaryPath()
}
return pathString
}
/// Save a file with given content.
///
/// - Parameters:
/// - file: File to be saved.
/// - path: File path.
/// - content: Content to be saved.
/// - Throws: write(toFile:, atomically:, encoding:) errors.
func save(file: String, in path: PathType, content: String) throws {
guard let path = FileManager.default.pathFor(path) else {
return
}
try content.write(toFile: path.appendingPathComponent(file), atomically: true, encoding: .utf8)
}
/// Read a file an returns the content as String.
///
/// - Parameters:
/// - file: File to be read.
/// - path: File path.
/// - Returns: Returns the content of the file a String.
/// - Throws: Throws String(contentsOfFile:, encoding:) errors.
func read(file: String, from path: PathType) throws -> String? {
guard let path = FileManager.default.pathFor(path) else {
return nil
}
return try String(contentsOfFile: path.appendingPathComponent(file), encoding: .utf8)
}
/// Save an object into a PLIST with given filename.
///
/// - Parameters:
/// - object: Object to save into PLIST.
/// - path: Path of PLIST.
/// - filename: PLIST filename.
/// - Returns: Returns true if the operation was successful, otherwise false.
@discardableResult
func savePlist(object: Any, in path: PathType, filename: String) -> Bool {
let path = checkPlist(path: path, filename: filename)
guard !path.exist else {
return NSKeyedArchiver.archiveRootObject(object, toFile: path.path)
}
return false
}
/// Load an object from a PLIST with given filename.
///
/// - Parameters:
/// - path: Path of PLIST.
/// - filename: PLIST filename.
/// - Returns: Returns the loaded object.
func readPlist(from path: PathType, filename: String) -> Any? {
let path = checkPlist(path: path, filename: filename)
guard !path.exist else {
return NSKeyedUnarchiver.unarchiveObject(withFile: path.path)
}
return nil
}
/// Check if plist exist.
///
/// - Parameters:
/// - path: Path of plist.
/// - filename: Plist filename.
/// - Returns: Returns if plist exists and path.
private func checkPlist(path: PathType, filename: String) -> (exist: Bool, path: String) {
guard let path = FileManager.default.pathFor(path), let finalPath = path.appendingPathComponent(filename).appendingPathExtension("plist") else {
return (false, "")
}
return (true, finalPath)
}
/// Get Main Bundle path for a filename.
/// If no file is specified, the main bundle path will be returned.
///
/// - Parameter file: Filename.
/// - Returns: Returns the path as a String.
func mainBundlePath(file: String = "") -> String? {
file.isEmpty ? Bundle.main.bundlePath : Bundle.main.path(forResource: file.deletingPathExtension, ofType: file.pathExtension)
}
/// Get Documents path for a filename.
///
/// - Parameter file: Filename
/// - Returns: Returns the path as a String.
func documentsPath(file: String = "") -> String? {
guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
return nil
}
return documentsURL.path.appendingPathComponent(file)
}
/// Get Library path for a filename.
///
/// - Parameter file: Filename
/// - Returns: Returns the path as a String.
func libraryPath(file: String = "") -> String? {
guard let libraryURL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first else {
return nil
}
return libraryURL.path.appendingPathComponent(file)
}
/// Get Cache path for a filename.
///
/// - Parameter file: Filename
/// - Returns: Returns the path as a String.
func cachePath(file: String = "") -> String? {
guard let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
return nil
}
return cacheURL.path.appendingPathComponent(file)
}
/// Get Application Support path for a filename.
///
/// - Parameter file: Filename
/// - Returns: Returns the path as a String.
func applicationSupportPath(file: String = "") -> String? {
guard let applicationSupportURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
return nil
}
if !FileManager.default.fileExists(atPath: applicationSupportURL.absoluteString, isDirectory: nil) {
do {
try FileManager.default.createDirectory(atPath: applicationSupportURL.path, withIntermediateDirectories: true, attributes: nil)
} catch {
return nil
}
}
return applicationSupportURL.path.appendingPathComponent(file)
}
/// Get Temporary path for a filename.
///
/// - Parameter file: Filename.
/// - Returns: Returns the path as a String.
func temporaryPath(file: String = "") -> String? {
NSTemporaryDirectory().appendingPathComponent(file)
}
/// Returns the file size.
///
/// - Parameters:
/// - file: Filename.
/// - path: Path of the file.
/// - Returns: Returns the file size.
/// - Throws: Throws FileManager.default.attributesOfItem(atPath:) errors.
func size(file: String, from path: PathType) throws -> Float? {
if !file.isEmpty {
guard let path = FileManager.default.pathFor(path) else {
return nil
}
let finalPath = path.appendingPathComponent(file)
if FileManager.default.fileExists(atPath: finalPath) {
let fileAttributes = try FileManager.default.attributesOfItem(atPath: finalPath)
return fileAttributes[FileAttributeKey.size] as? Float
}
}
return nil
}
/// Delete a file with the given filename.
///
/// - Parameters:
/// - file: File to delete.
/// - path: Path of the file.
/// - Throws: Throws FileManager.default.removeItem(atPath:) errors.
func delete(file: String, from path: PathType) throws {
if !file.isEmpty {
guard let path = FileManager.default.pathFor(path) else {
throw BFKitError.pathNotExist
}
if FileManager.default.fileExists(atPath: path.appendingPathComponent(file)) {
try FileManager.default.removeItem(atPath: path.appendingPathComponent(file))
}
}
}
/// Move a file from a path to another.
///
/// - Parameters:
/// - file: Filename to move.
/// - origin: Origin path of the file.
/// - destination: Destination path of the file.
/// - Returns: Returns true if the operation was successful, otherwise false.
/// - Throws: Throws FileManager.default.moveItem(atPath:, toPath:) and BFKitError errors.
func move(file: String, from origin: PathType, to destination: PathType) throws {
let paths = try check(file: file, origin: origin, destination: destination)
if paths.fileExist {
try FileManager.default.moveItem(atPath: paths.origin, toPath: paths.destination)
}
}
/// Copy a file into another path.
///
/// - Parameters:
/// - file: Filename to copy.
/// - origin: Origin path
/// - destination: Destination path
/// - Returns: Returns true if the operation was successful, otherwise false.
/// - Throws: Throws FileManager.default.copyItem(atPath:, toPath:) and BFKitError errors.
func copy(file: String, from origin: PathType, to destination: PathType) throws {
let paths = try check(file: file, origin: origin, destination: destination)
if paths.fileExist {
try FileManager.default.copyItem(atPath: paths.origin, toPath: paths.destination)
}
}
/// Check is orign path, destination path and file exists.
///
/// - Parameters:
/// - file: File.
/// - origin: Origin path.
/// - destination: Destination path.
/// - Returns: Returns a tuple with origin, destination and if file exist.
/// - Throws: Throws BFKitError errors.
private func check(file: String, origin: PathType, destination: PathType) throws -> (origin: String, destination: String, fileExist: Bool) { // swiftlint:disable:this large_tuple
guard let originPath = FileManager.default.pathFor(origin), let destinationPath = FileManager.default.pathFor(destination) else {
throw BFKitError.pathNotExist
}
guard destination != .mainBundle else {
throw BFKitError.pathNotAllowed
}
let finalOriginPath = originPath.appendingPathComponent(file)
let finalDestinationPath = destinationPath.appendingPathComponent(file)
guard !FileManager.default.fileExists(atPath: finalOriginPath) else {
return (finalOriginPath, finalDestinationPath, true)
}
return (finalOriginPath, finalDestinationPath, false)
}
/// Rename a file with another filename.
///
/// - Parameters:
/// - file: Filename to rename.
/// - origin: Origin path.
/// - newName: New filename.
/// - Returns: Returns true if the operation was successful, otherwise false.
/// - Throws: Throws FileManager.default.copyItem(atPath:, toPath:), FileManager.default.removeItem(atPath:, toPath:) and BFKitError errors.
func rename(file: String, in origin: PathType, to newName: String) throws {
guard let originPath = FileManager.default.pathFor(origin) else {
throw BFKitError.pathNotExist
}
let finalOriginPath = originPath.appendingPathComponent(file)
if FileManager.default.fileExists(atPath: finalOriginPath) {
let destinationPath: String = finalOriginPath.replacingOccurrences(of: file, with: newName)
try FileManager.default.copyItem(atPath: finalOriginPath, toPath: destinationPath)
try FileManager.default.removeItem(atPath: finalOriginPath)
}
}
/// Set settings for object and key. The file will be saved in the Library path if not exist.
///
/// - Parameters:
/// - filename: Settings filename. "-Settings" will be automatically added.
/// - object: Object to set.
/// - objKey: Object key.
/// - Returns: Returns true if the operation was successful, otherwise false.
/// - Throws: Throws BFKitError errors.
@discardableResult
func setSettings(filename: String, object: Any, forKey objectKey: String) -> Bool {
guard var path = FileManager.default.pathFor(.applicationSupport) else {
return false
}
path = path.appendingPathComponent("\(filename)-Settings.plist")
var settings: [String: Any]
if let plistData = try? Data(contentsOf: URL(fileURLWithPath: path)), let plistFile = try? PropertyListSerialization.propertyList(from: plistData, format: nil), let plistDictionary = plistFile as? [String: Any] {
settings = plistDictionary
} else {
settings = [:]
}
settings[objectKey] = object
do {
let plistData = try PropertyListSerialization.data(fromPropertyList: settings, format: .xml, options: 0)
try plistData.write(to: URL(fileURLWithPath: path))
return true
} catch {
return false
}
}
/// Get settings for key.
///
/// - Parameters:
/// - filename: Settings filename. "-Settings" will be automatically added.
/// - forKey: Object key.
/// - Returns: Returns the object for the given key.
func getSettings(filename: String, forKey objectKey: String) -> Any? {
guard var path = FileManager.default.pathFor(.applicationSupport) else {
return nil
}
path = path.appendingPathComponent("\(filename)-Settings.plist")
var settings: [String: Any]
if let plistData = try? Data(contentsOf: URL(fileURLWithPath: path)), let plistFile = try? PropertyListSerialization.propertyList(from: plistData, format: nil), let plistDictionary = plistFile as? [String: Any] {
settings = plistDictionary
} else {
settings = [:]
}
return settings[objectKey]
}
}
import Foundation
// MARK: - NSObject exntesion
/// This extension adds some useful functions to NSObject.
public extension NSObject {
// MARK: - Functions
/// Check if the object is valid (not null).
///
/// - Returns: Returns if the object is valid
func isValid() -> Bool {
!(self is NSNull)
}
}
import Dispatch
import Foundation
// MARK: - Global functions
/// Degrees to radians conversion.
///
/// - Parameter degrees: Degrees to be converted.
/// - Returns: Returns the convertion result.
public func degreesToRadians(_ degrees: Double) -> Double {
degrees * Double.pi / 180
}
/// Radians to degrees conversion.
///
/// - Parameter radians: Radians to be converted.
/// - Returns: Returns the convertion result.
public func radiansToDegrees(_ radians: Double) -> Double {
radians * 180 / Double.pi
}
// MARK: - Extensions
/// This extesion adds some useful functions to Double.
public extension Double {
/// Gets the individual numbers, and puts them into an array. All negative numbers will start with 0.
var array: [Int] {
description.map { Int(String($0)) ?? 0 }
}
}
/// This extesion adds some useful functions to Float.
public extension Float {
/// Gets the individual numbers, and puts them into an array. All negative numbers will start with 0.
var array: [Int] {
description.map { Int(String($0)) ?? 0 }
}
}
/// This extesion adds some useful functions to Int.
public extension Int {
/// Gets the individual numbers, and puts them into an array. All negative numbers will start with 0.
var array: [Int] {
description.map { Int(String($0)) ?? 0 }
}
}
/// Infix operator `<>` with ComparisonPrecedence.
infix operator <>: ComparisonPrecedence
/// Infix operator `<=>` with ComparisonPrecedence.
infix operator <=>: ComparisonPrecedence
/// Returns true if `left` it is in `right` range but not equal.
/// If you want to check if its even equal use the `<=>` operator.
///
/// - Parameters:
/// - left: Left number to be compared.
/// - right: Right tuple to be compared (Number, Number).
/// - Returns: Returns true if `left` it is in `right` range but not equal.
public func <> <T: Comparable>(left: T, right: (T, T)) -> Bool {
left > right.0 && left < right.1
}
/// Returns true if `left` is in `right` range or equal.
///
/// - Parameters:
/// - left: Left number to be compared.
/// - right: Right tuple to be compared (Number, Number).
/// - Returns: Returns true if `left` it is in `right` range or equal.
public func <=> <T: Comparable>(left: T, right: (T, T)) -> Bool {
left >= right.0 && left <= right.1
}
import Foundation
// MARK: - ProcessInfo extension
/// This extesion adds some useful functions to ProcessInfo.
public extension ProcessInfo {
/// Returns system uptime.
///
/// - Returns: eturns system uptime.
static func uptime() -> TimeInterval {
ProcessInfo.processInfo.systemUptime
}
/// Returns sysyem uptime as Date.
///
/// - Returns: Returns sysyem uptime as Date.
static func uptimeDate() -> Date {
Date(timeIntervalSinceNow: -uptime())
}
}
import Foundation
// MARK: - Set extension
/// This extension adds some useful functions to Set.
public extension Set {
/// Randomly selects an element from self and returns it.
///
/// - returns: An element that was randomly selected from the set.
func random() -> Element {
let randomOffset = Int.random(in: 0...count - 1)
return self[index(startIndex, offsetBy: randomOffset)]
}
}
import Dispatch
import Foundation
// MARK: - Global functions
/// Runs a block on main thread.
///
/// - Parameter block: Block to be executed.
public func runOnMainThread(_ block: @escaping () -> Void) {
DispatchQueue.main.async {
block()
}
}
/// Runs a block in background.
///
/// - Parameter block: block Block to be executed.
public func runInBackground(_ block: @escaping () -> Void) {
DispatchQueue.global().async {
block()
}
}
import Foundation
import WebKit
// MARK: - WKWebView extension
/// This exension adds some useful functions to WKWebView.
public extension WKWebView {
// MARK: - Functions
/// Load the requested website.
///
/// - Parameter website: Website to load
func load(website: String) {
guard let url = URL(string: website) else {
return
}
load(URLRequest(url: url))
}
func clearCache() {
let dataStore = WKWebsiteDataStore.default()
dataStore.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { records in
dataStore.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), for: records) {
print("Cache cleared!")
}
}
}
}