forked from tectronics/swift-magic-pills
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a bunch of extension and utilities from our main project. (#34)
* Add a bunch of extension and utilities from our main project. * Visibility * Improve testing * Fix tests * Coverage up! * Coverage bump again * Bump tests
- Loading branch information
1 parent
2a974bd
commit 571ac4b
Showing
44 changed files
with
1,217 additions
and
322 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
11 changes: 11 additions & 0 deletions
11
Source/Combine/Extensions/CurrentValueSubjectExtensions.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
Source/Combine/Extensions/PassthroughSubjectExtensions.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)") | ||
} | ||
|
||
func filterType<T>(_ type: T.Type, tag: String = "") -> Publishers.Filter<PassthroughSubject<Output, Failure>> { | ||
filter { $0 == "\(type.self)-\(tag)" } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import Combine | ||
import Foundation | ||
|
||
public extension Publisher { | ||
/// Emit with the latest value from given publisher, if the given publisher is empty upstream value will loss. | ||
func withLatestFrom<Other: Publisher>(_ other: Other) -> Publishers.WithLatestFrom<Self, Other> { | ||
Publishers.WithLatestFrom(upstream: self, other: other) | ||
} | ||
} | ||
|
||
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 | ||
} | ||
|
||
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) | ||
} | ||
} | ||
} | ||
|
||
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() | ||
} | ||
|
||
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
||
/// 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 | ||
} | ||
|
||
/// 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 | ||
} | ||
|
||
var isRunningFromTestFlight: Bool { | ||
appStoreReceiptURL?.lastPathComponent == "sandboxReceipt" | ||
} | ||
|
||
return versionNumber == buildNumber ? "v\(versionNumber)" : "v\(versionNumber)(\(buildNumber))" | ||
@available(*, deprecated, renamed: "isRunningFromTestFlight") | ||
var runningFromTestFlight: Bool { | ||
isRunningFromTestFlight | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
58 changes: 58 additions & 0 deletions
58
Source/Foundation/Extensions/StringExtensions+Ranges.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.