Last active
March 18, 2023 11:02
-
-
Save cjnevin/b6c68722514d65cafb50d7ea916114b1 to your computer and use it in GitHub Desktop.
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
import Foundation | |
var injectedLines = """ | |
@Injected | |
private var someSharedValue: SharedValue.Type | |
@Injected | |
private var someUnregisteredValue: UnregisteredValue | |
#if os(tvOS) | |
@Injected | |
private var someTvValue: TvValue | |
#elseif os(iOS) | |
@Injected | |
private var someMobileValue: MobileValue | |
@Injected | |
private var someMobileValue2: MobileValue2 | |
#endif | |
some both | |
""" | |
var registeredLines = """ | |
register(SomeSharedObject(), for: SharedValue.Type) | |
#if DEBUG | |
#if os(iOS) | |
register(SomeObj(), for: TvValue2.self) | |
#endif | |
#else | |
#if os(iOS) | |
register(SomeRealObj(), for: TvValue2.self) | |
#endif | |
#endif | |
#if os(tvOS) | |
#if DEBUG | |
register(SomeTvDebugObject(), for: TvValue.self) | |
#else | |
register(SomeTvObject(), for: TvValue.self) | |
register(SomeTvObject(), for: TvValue.self) | |
#endif | |
#else | |
register(SomeUnregisteredObject(), for: UnregisteredValue.self) | |
register(SomeUninjectedObject(), for: UninjectedValue.self) | |
register(SomeMobileObject2(), for: MobileValue2.self) | |
register(SomeMobileObject(), for: MobileValue.self) | |
#endif | |
""" | |
enum Platform: String, Hashable { | |
case iOS | |
case tvOS | |
case both | |
} | |
typealias PlatformValues = [Platform: [String]] | |
extension String { | |
func platformSeparatedLines( | |
matching pattern: String, | |
sameLine: Bool, | |
operation: (String) -> String? | |
) -> PlatformValues { | |
var currentPlatform: Platform = .both | |
var platformItems: PlatformValues = [ | |
.both: [], | |
.iOS: [], | |
.tvOS: [] | |
] | |
func store(value: String, for platform: Platform) { | |
if platform == .both { | |
store(value: value, for: .iOS) | |
store(value: value, for: .tvOS) | |
return | |
} | |
if let trimmed = operation(value.trimmingCharacters(in: .whitespaces)) { | |
var current = platformItems[platform] ?? [] | |
current.sortedInsert(trimmed) | |
platformItems[platform] = current | |
} | |
} | |
var isDebugBlock: Bool = false | |
var isDebug: Bool = false | |
var trackNextLine: Bool = false | |
for line in self.components(separatedBy: .newlines) { | |
if line.localizedCaseInsensitiveContains("#if") || | |
line.localizedCaseInsensitiveContains("#else") { | |
let trimmed = line.trimmingCharacters(in: .whitespaces) | |
if trimmed.hasPrefix("#if") || trimmed.hasPrefix("#elseif") { | |
if trimmed.localizedCaseInsensitiveContains("DEBUG") { | |
isDebug = true | |
isDebugBlock = true | |
} else if trimmed.localizedCaseInsensitiveContains("os(iOS)") { | |
currentPlatform = .iOS | |
} else if trimmed.localizedCaseInsensitiveContains("os(tvOS)") { | |
currentPlatform = .tvOS | |
} | |
} else if trimmed.hasPrefix("#else") { | |
if isDebug { | |
isDebug = false | |
} else if currentPlatform == .iOS { | |
currentPlatform = .tvOS | |
} else if currentPlatform == .tvOS { | |
currentPlatform = .iOS | |
} | |
} | |
} else if line.localizedCaseInsensitiveContains("#endif") { | |
if isDebugBlock { | |
isDebug = false | |
isDebugBlock = false | |
} else { | |
currentPlatform = .both | |
} | |
} else if trackNextLine && !sameLine && !isDebug { | |
store(value: line, for: currentPlatform) | |
trackNextLine = false | |
} else if (!trackNextLine || sameLine) && line.localizedCaseInsensitiveContains(pattern) { | |
if sameLine { | |
if !isDebug { | |
store(value: line, for: currentPlatform) | |
} | |
} else { | |
trackNextLine = true | |
} | |
} | |
} | |
return platformItems | |
} | |
} | |
extension Array where Element: Comparable { | |
mutating func sortedInsert(_ element: Element) { | |
var start = startIndex | |
var end = endIndex | |
while start < end { | |
let middle = start + (end - start) / 2 | |
if self[middle] < element { | |
start = middle + 1 | |
} else { | |
end = middle | |
} | |
} | |
assert(start == end) | |
insert(element, at: start) | |
} | |
} | |
enum DiffResult { | |
enum SideResult { | |
case only(String) | |
case duplicate(String) | |
} | |
case both(String) | |
case lhs(SideResult) | |
case rhs(SideResult) | |
} | |
extension String { | |
func trimmingSuffix(_ suffix: String) -> String { | |
if hasSuffix(suffix) { | |
return String(reversed().trimmingPrefix(suffix.reversed()).reversed()) | |
} else { | |
return self | |
} | |
} | |
} | |
extension String { | |
var valueType: String? { | |
components(separatedBy: ":").last?.trimmingCharacters(in: .whitespaces) | |
} | |
var registeredType: String? { | |
if let text = components(separatedBy: ", for: ").last?.trimmingCharacters(in: .whitespaces) { | |
return text.trimmingSuffix(")").trimmingSuffix(".self") | |
} | |
return nil | |
} | |
} | |
let injectedValueTypes = injectedLines.platformSeparatedLines(matching: "@Injected", sameLine: false, operation: \.valueType) | |
let registeredValueTypes = registeredLines.platformSeparatedLines(matching: "register(", sameLine: true, operation: \.registeredType) | |
extension Dictionary where Key == Platform, Value == [String] { | |
func subtracting(other: [Key: Value], for platform: Key) -> [String] { | |
if let items = self[platform], let otherItems = other[platform] { | |
return Set(items).subtracting(otherItems).sorted() | |
} | |
return [] | |
} | |
} | |
extension Array where Element: Comparable, Element: Hashable { | |
func duplicates() -> [Element] { | |
Dictionary(grouping: self, by: { $0 }).filter { $1.count > 1 }.keys.sorted() | |
} | |
} | |
func printResults(for platforms: [Platform], injected: PlatformValues, registered: PlatformValues) { | |
struct Results { | |
let duplicated: [String] | |
let uninjected: [String] | |
let unregistered: [String] | |
} | |
let results = platforms.map { platform in | |
Results( | |
duplicated: injected[platform]?.duplicates() ?? [], | |
uninjected: registered.subtracting(other: injected, for: platform), | |
unregistered: injected.subtracting(other: registered, for: platform) | |
) | |
} | |
func printResults(for keyPath: KeyPath<Results, [String]>, blurb: String) { | |
let count = results.reduce(0, { $0 + $1[keyPath: keyPath].count }) | |
if count > 0 { | |
print(blurb) | |
zip(platforms, results.map { $0[keyPath: keyPath] }).filter { !$1.isEmpty }.forEach { | |
print($0.rawValue + ":") | |
dump($1) | |
} | |
print("") | |
} | |
} | |
printResults(for: \.duplicated, blurb: "Some dependencies are registered multiple times on a particular platform.") | |
printResults(for: \.uninjected, blurb: "Some dependencies have been registered with the DependencyStore but are not @Injected anywhere.") | |
printResults(for: \.unregistered, blurb: "Some dependencies have been @Injected but are not registered with the DependencyStore.") | |
} | |
if injectedValueTypes == registeredValueTypes { | |
print("All @Injected values successfully registered.") | |
} else { | |
printResults(for: [.iOS, .tvOS], injected: injectedValueTypes, registered: registeredValueTypes) | |
} |
Author
cjnevin
commented
Mar 18, 2023
•
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment