BuildConfig.swift (5476B)
1 import Foundation 2 3 enum BuildConfigKey: String { 4 case envFile = "RADROOTS_FIELD_IOS_ENV_FILE" 5 case runtimeMode = "RADROOTS_FIELD_IOS_RUNTIME_MODE" 6 case loggingStdout = "RADROOTS_FIELD_IOS_LOGGING_STDOUT" 7 case loggingFilter = "RADROOTS_FIELD_IOS_LOGGING_FILTER" 8 case loggingFileEnabled = "RADROOTS_FIELD_IOS_LOGGING_FILE_ENABLED" 9 case loggingFileName = "RADROOTS_FIELD_IOS_LOGGING_FILE_NAME" 10 case nostrRelayUrls = "RADROOTS_FIELD_IOS_NOSTR_RELAY_URLS" 11 case keychainServicePrefix = "RADROOTS_FIELD_IOS_KEYCHAIN_SERVICE_PREFIX" 12 case keychainAccessPolicy = "RADROOTS_FIELD_IOS_KEYCHAIN_ACCESS_POLICY" 13 case resetLocalState = "RADROOTS_FIELD_IOS_RESET_LOCAL_STATE" 14 } 15 16 enum BuildConfig { 17 static func string(_ key: BuildConfigKey) -> String? { 18 debugLaunchOverrideString(key) ?? infoString(key).map { stripOuterQuotes($0) } 19 } 20 21 static func bool(_ key: BuildConfigKey) -> Bool? { 22 if let raw = debugLaunchOverrideString(key), 23 let parsed = parseBool(raw) { 24 return parsed 25 } 26 if let v = infoValue(for: key.rawValue) { 27 if let b = v as? Bool { return b } 28 if let s = v as? String, let parsed = parseBool(s) { return parsed } 29 if let n = v as? NSNumber { return n.boolValue } 30 } 31 return nil 32 } 33 34 static func array(_ key: BuildConfigKey, splitBy set: CharacterSet = .whitespacesAndNewlines) -> [String]? { 35 if let raw = debugLaunchOverrideString(key) { 36 return parseArray(raw, splitBy: set) 37 } 38 if let direct = infoArray(key) { 39 return direct 40 .map { stripOuterQuotes($0).trimmingCharacters(in: .whitespacesAndNewlines) } 41 .filter { !$0.isEmpty } 42 } 43 guard let raw = infoString(key) else { return nil } 44 return parseArray(raw, splitBy: set) 45 } 46 47 static func effectiveDictionary(keys: [BuildConfigKey]) -> [String: Any] { 48 var out: [String: Any] = [:] 49 for k in keys { 50 switch k { 51 case .loggingStdout, .loggingFileEnabled, .resetLocalState: 52 if let b = bool(k) { 53 out[k.rawValue] = b 54 } 55 case .nostrRelayUrls: 56 if let arr = array(.nostrRelayUrls) { 57 out[k.rawValue] = arr 58 } 59 default: 60 if let s = string(k) { 61 out[k.rawValue] = s 62 } 63 } 64 } 65 return out 66 } 67 68 private static func parseArray(_ value: String, splitBy set: CharacterSet) -> [String]? { 69 var raw = value.trimmingCharacters(in: .whitespacesAndNewlines) 70 guard !raw.isEmpty else { return nil } 71 if raw.first == "[" { 72 if let data = raw.data(using: .utf8), 73 let arr = try? JSONSerialization.jsonObject(with: data) as? [String] { 74 return arr 75 .map { stripOuterQuotes($0).trimmingCharacters(in: .whitespacesAndNewlines) } 76 .filter { !$0.isEmpty } 77 } 78 } 79 raw = stripOuterQuotes(raw) 80 let separators = set.union(CharacterSet(charactersIn: ",;")) 81 raw = raw.replacingOccurrences(of: "\n", with: " ") 82 .replacingOccurrences(of: "\r", with: " ") 83 return raw 84 .components(separatedBy: separators) 85 .map { stripOuterQuotes($0).trimmingCharacters(in: .whitespacesAndNewlines) } 86 .filter { !$0.isEmpty } 87 } 88 89 private static func infoString(_ key: BuildConfigKey) -> String? { 90 if let v = infoValue(for: key.rawValue) as? String, !v.isEmpty { return v } 91 if let n = infoValue(for: key.rawValue) as? NSNumber { return n.stringValue } 92 return nil 93 } 94 95 private static func infoArray(_ key: BuildConfigKey) -> [String]? { 96 if let v = infoValue(for: key.rawValue) as? [String] { return v } 97 if let nested = Bundle.main.object(forInfoDictionaryKey: "Radroots") as? [String: Any], 98 let v = nested[key.rawValue] as? [String] { 99 return v 100 } 101 return nil 102 } 103 104 private static func infoValue(for key: String) -> Any? { 105 if let v = Bundle.main.object(forInfoDictionaryKey: key) { 106 return v 107 } 108 if let nested = Bundle.main.object(forInfoDictionaryKey: "Radroots") as? [String: Any] { 109 return nested[key] 110 } 111 return nil 112 } 113 114 private static func parseBool(_ s: String) -> Bool? { 115 switch s.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() { 116 case "1", "true", "yes": return true 117 case "0", "false", "no": return false 118 default: return nil 119 } 120 } 121 122 private static func stripOuterQuotes(_ s: String) -> String { 123 guard s.count >= 2 else { return s } 124 if (s.hasPrefix("\"") && s.hasSuffix("\"")) || (s.hasPrefix("'") && s.hasSuffix("'")) { 125 return String(s.dropFirst().dropLast()) 126 } 127 return s 128 } 129 130 #if DEBUG 131 private static func debugLaunchOverrideString(_ key: BuildConfigKey) -> String? { 132 guard FieldUITestHarness.isRequested else { 133 return nil 134 } 135 return FieldUITestHarness.string(key.rawValue) 136 .flatMap { $0.isEmpty ? nil : stripOuterQuotes($0) } 137 } 138 #else 139 private static func debugLaunchOverrideString(_ key: BuildConfigKey) -> String? { 140 nil 141 } 142 #endif 143 }