field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | LICENSE

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 }