Skip to content

Instantly share code, notes, and snippets.

@cjnevin
Last active March 18, 2023 11:02
Show Gist options
  • Save cjnevin/b6c68722514d65cafb50d7ea916114b1 to your computer and use it in GitHub Desktop.
Save cjnevin/b6c68722514d65cafb50d7ea916114b1 to your computer and use it in GitHub Desktop.
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)
}
@cjnevin
Copy link
Author

cjnevin commented Mar 18, 2023

Some dependencies have been registered with the DependencyStore but are not @Injected anywhere.
iOS:
▿ 1 element
  - "UninjectedValue"

Some dependencies have been @Injected but are not registered with the DependencyStore.
tvOS:
▿ 1 element
  - "UnregisteredValue"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment