// // Settings.swift // App // // Created by James Magahern on 3/9/21. // import Foundation @propertyWrapper public struct SettingProperty { public var key: String public var defaultValue: T public init(wrappedValue: T, key: String) { self.key = key self.defaultValue = wrappedValue } public var wrappedValue: T { get { guard let rawValue = UserDefaults.standard.object(forKey: key) as? T.RawValue else { return defaultValue } return T(rawValue: rawValue) ?? defaultValue } set { UserDefaults.standard.setValue(newValue.rawValue, forKey: key) } } } // - These coercions into RawRepresentable are stupid. How do I write specializations for each type instead? extension Dictionary: RawRepresentable where Key == String, Value == String { public typealias RawValue = [String: String] public init?(rawValue: [String : String]) { self.init() self.merge(rawValue, uniquingKeysWith: { $1 }) } public var rawValue: [String : String] { return self } } extension Optional: RawRepresentable where Wrapped == String { public typealias RawValue = String? public init?(rawValue: String?) { if let rawValue { self = String(rawValue: rawValue) } else { self = .none } } public var rawValue: String? { return self } } extension String: RawRepresentable { public typealias RawValue = String public init?(rawValue: String) { self.init(rawValue) } public var rawValue: String { return self } } class Settings { static let shared = Settings() // Map of search engine name -> URL template containing %q or %s placeholder // Defaults preserve the previous built-in engines @SettingProperty(key: "searchEngines") public var searchEngines: [String: String] = [ "Google": "https://google.com/search?q=%q&gbv=1", "DuckDuckGo": "https://html.duckduckgo.com/html/?q=%q", ] // Name of the default search engine from `searchEngines` @SettingProperty(key: "defaultSearchEngine") public var defaultSearchEngineName: String = "Searx.nor" // Convenience to build a SearchProvider from current default func currentSearchProvider() -> SearchProvider { if let template = searchEngines[defaultSearchEngineName] { return SearchProvider.fromTemplate(template) } // Fallback to Google if something goes wrong return SearchProvider.fromTemplate("https://google.com/search?q=%q&gbv=1") } @SettingProperty(key: "redirectRules") public var redirectRules: [String: String] = [:] func redirectRule(for url: URL) -> URL? { if var components = URLComponents(url: url, resolvingAgainstBaseURL: false), var host = url.host { // Remove "www." if necessary. host = host.replacingOccurrences(of: "www.", with: "") if let alternateHost = redirectRules[host] { components.host = alternateHost // Since redirected hosts are usually self-hosted. // If https is required, it will be upgraded anyway. components.scheme = "http" return components.url } } return nil } @SettingProperty(key: "userScript") public var userScript: String = "" @SettingProperty(key: "userStylesheet") public var userStylesheet: String = "" @SettingProperty(key: "syncServer") public var syncServer: Optional = "https://attractor.severnaya.net" }