Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a bunch of extension and utilities from our main project. #34

Merged
merged 8 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions MasMagicPills.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions Source/Combine/Extensions/CurrentValueSubjectExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Combine
import Foundation

public extension CurrentValueSubject where Output: Equatable {
/// Emit new value if distinct, ignore
func sendIfDistinct(_ input: Output) {
if input != value {
send(input)
}
}

Check warning on line 10 in Source/Combine/Extensions/CurrentValueSubjectExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Combine/Extensions/CurrentValueSubjectExtensions.swift#L6-L10

Added lines #L6 - L10 were not covered by tests
}
12 changes: 12 additions & 0 deletions Source/Combine/Extensions/PassthroughSubjectExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Combine
import Foundation

public extension PassthroughSubject where Output == String {
func sendType<T>(_ type: T.Type, tag: String = "") {
send("\(type.self)-\(tag)")
}

Check warning on line 7 in Source/Combine/Extensions/PassthroughSubjectExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Combine/Extensions/PassthroughSubjectExtensions.swift#L5-L7

Added lines #L5 - L7 were not covered by tests

func filterType<T>(_ type: T.Type, tag: String = "") -> Publishers.Filter<PassthroughSubject<Output, Failure>> {
filter { $0 == "\(type.self)-\(tag)" }
}

Check warning on line 11 in Source/Combine/Extensions/PassthroughSubjectExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Combine/Extensions/PassthroughSubjectExtensions.swift#L9-L11

Added lines #L9 - L11 were not covered by tests
}
15 changes: 15 additions & 0 deletions Source/Combine/Extensions/PublisherExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Combine
import Foundation

public extension Publisher {
/**
Projects each element from a publisher into a new publisher and then transforms an publisher into an publisher producing values only from the most recent value.
- parameter transform: A transform function to apply to each element.
- returns: A publisher whose elements are the result of invoking the transform function on each value of source producing a publisher and that at any point in time produces the elements of the most recent inner sequence that has been received.
*/
func flatMapLatest<T>(_ transform: @escaping (Output) -> AnyPublisher<T, Failure>) -> AnyPublisher<T, Failure> {
map(transform)
.switchToLatest()
.eraseToAnyPublisher()
}

Check warning on line 14 in Source/Combine/Extensions/PublisherExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Combine/Extensions/PublisherExtensions.swift#L10-L14

Added lines #L10 - L14 were not covered by tests
}
83 changes: 83 additions & 0 deletions Source/Combine/Publishers/Publishers.WithLatestFrom.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Combine
import Foundation

public extension Publisher {
func withLatestFrom<Other: Publisher>(_ other: Other) -> Publishers.WithLatestFrom<Self, Other> {
Publishers.WithLatestFrom(upstream: self, other: other)
}

Check warning on line 7 in Source/Combine/Publishers/Publishers.WithLatestFrom.swift

View check run for this annotation

Codecov / codecov/patch

Source/Combine/Publishers/Publishers.WithLatestFrom.swift#L5-L7

Added lines #L5 - L7 were not covered by tests
}

public extension Publishers {
struct WithLatestFrom<Upstream: Publisher, Other: Publisher>: Publisher where Upstream.Failure == Other.Failure {
public typealias Output = (Upstream.Output, Other.Output)
public typealias Failure = Upstream.Failure

public let upstream: Upstream
public let other: Other

public init(upstream: Upstream, other: Other) {
self.upstream = upstream
self.other = other
}

Check warning on line 21 in Source/Combine/Publishers/Publishers.WithLatestFrom.swift

View check run for this annotation

Codecov / codecov/patch

Source/Combine/Publishers/Publishers.WithLatestFrom.swift#L18-L21

Added lines #L18 - L21 were not covered by tests

public func receive<Downstream: Subscriber>(subscriber: Downstream)
where Output == Downstream.Input, Downstream.Failure == Upstream.Failure {
let merged = mergedStream(upstream, other)
let result = resultStream(from: merged)
result.subscribe(subscriber)
}

Check warning on line 28 in Source/Combine/Publishers/Publishers.WithLatestFrom.swift

View check run for this annotation

Codecov / codecov/patch

Source/Combine/Publishers/Publishers.WithLatestFrom.swift#L24-L28

Added lines #L24 - L28 were not covered by tests
}
}

private extension Publishers.WithLatestFrom {
// MARK: - Types
enum MergedElement {
case upstream1(Upstream.Output)
case upstream2(Other.Output)
}

typealias ScanResult =
(value1: Upstream.Output?,
value2: Other.Output?, shouldEmit: Bool)

// MARK: - Pipelines
func mergedStream(_ upstream1: Upstream, _ upstream2: Other)
-> AnyPublisher<MergedElement, Failure> {
let mergedElementUpstream1 = upstream1
.map { MergedElement.upstream1($0) }
let mergedElementUpstream2 = upstream2
.map { MergedElement.upstream2($0) }
return mergedElementUpstream1
.merge(with: mergedElementUpstream2)
.eraseToAnyPublisher()
}

Check warning on line 53 in Source/Combine/Publishers/Publishers.WithLatestFrom.swift

View check run for this annotation

Codecov / codecov/patch

Source/Combine/Publishers/Publishers.WithLatestFrom.swift#L45-L53

Added lines #L45 - L53 were not covered by tests

func resultStream(
from mergedStream: AnyPublisher<MergedElement, Failure>
) -> AnyPublisher<Output, Failure> {
mergedStream
.scan(nil) { (prevResult: ScanResult?, mergedElement: MergedElement) -> ScanResult? in
var newValue1: Upstream.Output?
var newValue2: Other.Output?
let shouldEmit: Bool

switch mergedElement {
case .upstream1(let value):
newValue1 = value
shouldEmit = prevResult?.value2 != nil

case .upstream2(let value):
newValue2 = value
shouldEmit = false
}

return ScanResult(value1: newValue1 ?? prevResult?.value1,
value2: newValue2 ?? prevResult?.value2,
shouldEmit: shouldEmit)
}
.compactMap { $0 }
.filter { $0.shouldEmit }
.map { Output($0.value1!, $0.value2!) }
.eraseToAnyPublisher()
}

Check warning on line 82 in Source/Combine/Publishers/Publishers.WithLatestFrom.swift

View check run for this annotation

Codecov / codecov/patch

Source/Combine/Publishers/Publishers.WithLatestFrom.swift#L57-L82

Added lines #L57 - L82 were not covered by tests
}
41 changes: 30 additions & 11 deletions Source/Foundation/Extensions/BundleExtensions.swift
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
import Foundation

public extension Bundle {
/// The release ("Major"."Minor"."Patch") or version number of the bundle (read-only, optional)
var versionNumber: String? {
object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
/// The release ("Major"."Minor"."Patch") or version number of the bundle
var versionNumber: String {
(object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String) ?? "0"
}

@available(*, deprecated, renamed: "versionNumber")
var appVersion: String {
versionNumber

Check warning on line 11 in Source/Foundation/Extensions/BundleExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Foundation/Extensions/BundleExtensions.swift#L10-L11

Added lines #L10 - L11 were not covered by tests
}

/// The version number of the bundle. (read-only, optional)
var buildNumber: String? {
object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String
var buildNumber: String {
(object(forInfoDictionaryKey: kCFBundleVersionKey as String) as? String) ?? "0"
}

@available(*, deprecated, renamed: "buildNumber")
var appBuild: String {
buildNumber

Check warning on line 21 in Source/Foundation/Extensions/BundleExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Foundation/Extensions/BundleExtensions.swift#L20-L21

Added lines #L20 - L21 were not covered by tests
}

/// Release number with version number or version number if are the same (read-only, optional)
var fullVersionNumber: String? {
guard let versionNumber = self.versionNumber,
let buildNumber = self.buildNumber else {
return nil
}
var fullVersionNumber: String {
versionNumber == buildNumber ? "v\(versionNumber)" : "v\(versionNumber)(\(buildNumber))"
}

@available(*, deprecated, renamed: "fullVersionNumber")
var appFullVersion: String {
fullVersionNumber
}

Check warning on line 32 in Source/Foundation/Extensions/BundleExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Foundation/Extensions/BundleExtensions.swift#L30-L32

Added lines #L30 - L32 were not covered by tests

var isRunningFromTestFlight: Bool {
appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
}

Check warning on line 36 in Source/Foundation/Extensions/BundleExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Foundation/Extensions/BundleExtensions.swift#L34-L36

Added lines #L34 - L36 were not covered by tests

return versionNumber == buildNumber ? "v\(versionNumber)" : "v\(versionNumber)(\(buildNumber))"
@available(*, deprecated, renamed: "isRunningFromTestFlight")
var runningFromTestFlight: Bool {
isRunningFromTestFlight

Check warning on line 40 in Source/Foundation/Extensions/BundleExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Foundation/Extensions/BundleExtensions.swift#L39-L40

Added lines #L39 - L40 were not covered by tests
}
}
25 changes: 25 additions & 0 deletions Source/Foundation/Extensions/DecimalExtensions.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
import Foundation

public extension Decimal {
/// Return the percentage value from self
func asPercentage(_ value: Decimal) -> Decimal {
self / (100 / value)
}

Check warning on line 7 in Source/Foundation/Extensions/DecimalExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Foundation/Extensions/DecimalExtensions.swift#L5-L7

Added lines #L5 - L7 were not covered by tests

/// Return the negate value
var negated: Decimal {
-self
}

Check warning on line 12 in Source/Foundation/Extensions/DecimalExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Foundation/Extensions/DecimalExtensions.swift#L10-L12

Added lines #L10 - L12 were not covered by tests

/// Return true if value is != 0
var isNotZero: Bool {
!isZero
}

Check warning on line 17 in Source/Foundation/Extensions/DecimalExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Foundation/Extensions/DecimalExtensions.swift#L15-L17

Added lines #L15 - L17 were not covered by tests

/// Return double from decimal value.
var doubleValue: Double {
Double(truncating: self as NSNumber)
}

Check warning on line 22 in Source/Foundation/Extensions/DecimalExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Foundation/Extensions/DecimalExtensions.swift#L20-L22

Added lines #L20 - L22 were not covered by tests

@available(*, deprecated, renamed: "doubleValue")
var asDouble: Double {
doubleValue
}

Check warning on line 27 in Source/Foundation/Extensions/DecimalExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Foundation/Extensions/DecimalExtensions.swift#L25-L27

Added lines #L25 - L27 were not covered by tests

/// Convert milliseconds(self) to seconds
var millisecondsToSeconds: Decimal {
self / 1_000
Expand Down
11 changes: 10 additions & 1 deletion Source/Foundation/Extensions/IntExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,17 @@
"\(self)"
}

var decimalValue: Decimal {
Decimal(self)
}

@available(*, deprecated, renamed: "decimalValue")
var toDecimal: Decimal {
decimalValue
}

Check warning on line 15 in Source/Foundation/Extensions/IntExtensions.swift

View check run for this annotation

Codecov / codecov/patch

Source/Foundation/Extensions/IntExtensions.swift#L13-L15

Added lines #L13 - L15 were not covered by tests

@available(*, deprecated, renamed: "stringValue")
var toString: String {
"\(self)"
stringValue
}
}
34 changes: 34 additions & 0 deletions Source/Foundation/Extensions/StringExtensions+Crypto.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,40 @@ public extension String {
return hash.map { String(format: "%02x", $0) }.joined().uppercased()
}

/// Decode Base64 string if possible. Returns nil if fails.
var base64decoded: String? {
guard let data = Data(base64Encoded: self, options: .ignoreUnknownCharacters) else {
return nil
}

return String(data: data, encoding: .utf8)
}

/// Decode Base64 URL-Safe string if possible. Returns nil if fails.
var base64UrlDecoded: String? {
var base64 = self
.replacingOccurrences(of: "_", with: "/")
.replacingOccurrences(of: "-", with: "+")

if base64.count % 4 != 0 {
base64.append(String(repeating: "=", count: 4 - base64.count % 4))
}
return base64.base64decoded
}

/// Encode into Base64 String
var base64encoded: String {
dataUTF8.base64EncodedString()
}

/// Encode into Base64 URL-Safe string.
var base64UrlEncoded: String {
base64encoded
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "+", with: "-")
.replacingOccurrences(of: "=", with: "")
}

func hmac(_ digest: HMACDigest) -> String {
switch digest {
case .sha256(let secret):
Expand Down
50 changes: 50 additions & 0 deletions Source/Foundation/Extensions/StringExtensions+Formating.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,54 @@
}
return formatted
}

/// Return numbers from string
var onlyNumbers: String {
self.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
}

func removing(prefix: String) -> String {
guard hasPrefix(prefix) else { return self }
return String(dropFirst(prefix.count))
}

func removing(suffix: String) -> String {
guard hasSuffix(suffix) else { return self }
return String(dropLast(suffix.count))
}

Check warning on line 32 in Source/Foundation/Extensions/StringExtensions+Formating.swift

View check run for this annotation

Codecov / codecov/patch

Source/Foundation/Extensions/StringExtensions+Formating.swift#L29-L32

Added lines #L29 - L32 were not covered by tests

var addingTrailingSpaceIfNotEmpty: String {
isEmpty ? "" : "\(self) "
}

var capitalizedWords: String {
self.split(separator: " ")
.map { $0.capitalized }
.joined(separator: " ")
}

var capitalizedSentences: String {
self.components(separatedBy: ". ")
.map { String($0).capitalizedFirstLetter }
.joined(separator: ". ")
}

var capitalizedFirstLetter: String {
if starts(withAnyOf: ["¡", "¿"]) {
return prefix(2).uppercased() + dropFirst(2)
}
return prefix(1).uppercased() + dropFirst(1)
}

var lowercasedLeastTheFirstUnchanged: String {
prefix(1) + dropFirst().lowercased()
}

var removingWhiteSpaces: String {
components(separatedBy: .whitespaces).joined()
}

var trimmed: String {
trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
}
58 changes: 58 additions & 0 deletions Source/Foundation/Extensions/StringExtensions+Ranges.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import Foundation

public extension String {
/// Look for where is the first text occurence in string
///
/// - Parameters:
/// - text: Text to look for
/// - options: Options to compare
/// - range: Range of string where look for
/// - locale: Information for use in formatting data for presentation
/// - Returns: Range for the first text occurence in string (optional)
func firstRangeOcurrence(_ text: String,
options: String.CompareOptions = [],
range: Range<Index>? = nil,
locale: Locale? = nil) -> NSRange? {
guard let range = self.range(of: text,
options: options,
range: range ?? startIndex..<endIndex,
locale: locale ?? .current) else { return nil }
return NSRange(range, in: self)
}

/// Look Ranges of texts occurrences in all string
///
/// - Parameters:
/// - text: Text to look for
/// - options: Options to compare
/// - range: Range of string where look for
/// - locale: Information for use in formatting data for presentation
/// - Returns: Ranges of texts occurrences in all string (if not find any, return empty array)
func ocurrencesRanges(_ text: String,
options: String.CompareOptions = [],
range: Range<Index>? = nil,
locale: Locale? = nil) -> [NSRange] {
var start = range?.lowerBound ?? startIndex
let end = range?.upperBound ?? endIndex
var ranges: [NSRange] = []
while start < end, let range = self.range(of: text,
options: options,
range: start..<end,
locale: locale ?? .current) {
ranges.append(NSRange(range, in: self))
start = range.upperBound
}
return ranges
}

var bodyRange: NSRange {
nsRange(of: self)!
}

func nsRange(of string: String) -> NSRange? {
guard let range = self.range(of: string, options: [], range: startIndex..<endIndex, locale: .current) else {
return nil
}
return NSRange(range, in: self)
}
}
14 changes: 14 additions & 0 deletions Source/Foundation/Extensions/StringExtensions+Regex.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation

public extension String {
func satisfiesRegex(_ regex: String) -> Bool {
range(of: regex, options: .regularExpression) != nil
}

/// Replaces the matches of the given regex pattern with an user-defined String.
func replacingRegexMatches(of pattern: String, with replacing: String) throws -> String {
let regex = try NSRegularExpression(pattern: pattern, options: NSRegularExpression.Options.caseInsensitive)
let range = NSRange(location: 0, length: self.count)
return regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replacing)
}
}
Loading
Loading