Created
August 6, 2020 00:02
-
-
Save aclima93/71669b068105b04b2268c56abea70768 to your computer and use it in GitHub Desktop.
// A build phase script for fetching, validating and generating a Swift wrapper over configuration files in iOS projects
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
#!/usr/bin/env xcrun --sdk macosx swift | |
// A build phase script for fetching, validating and generating a Swift wrapper over configuration files in iOS projects | |
// Source: https://github.com/pgorzelany/SwiftConfiguration | |
import Foundation | |
public struct ParsedArguments { | |
public let configurationPlistFilePath: String | |
public let outputFilePath: String | |
} | |
public class ArgumentsParser { | |
public init() {} | |
public func parseArguments(_ arguments: [String]) throws -> ParsedArguments { | |
guard arguments.count == 3 else { | |
throw ConfigurationError(message: "Insufficient number of arguments provided. Refer to the docs.") | |
} | |
return ParsedArguments(configurationPlistFilePath: arguments[1], | |
outputFilePath: arguments[2]) | |
} | |
} | |
public typealias Configuration = Dictionary<String, Any> | |
public struct ConfigurationError: LocalizedError { | |
private let message: String | |
init(message: String) { | |
self.message = message | |
} | |
public var errorDescription: String? { | |
return message | |
} | |
} | |
public class ConfigurationManagerGenerator { | |
// MARK: - Properties | |
private let configurationPlistFilePath: String | |
private let outputFilePath: String | |
private let configurationKey: String | |
private lazy var fileManager = FileManager.default | |
// MARK: - Lifecycle | |
public init(configurationPlistFilePath: String, outputFilePath: String, configurationKey: String) { | |
self.outputFilePath = outputFilePath | |
self.configurationKey = configurationKey | |
self.configurationPlistFilePath = configurationPlistFilePath | |
} | |
// MARK: - Methods | |
public func generateConfigurationManagerFile(for configuration: Configuration) throws { | |
let template = ConfigurationManagerTemplate(configuration: configuration, | |
configurationKey: configurationKey, | |
configurationPlistFilePath: configurationPlistFilePath) | |
if fileManager.fileExists(atPath: outputFilePath) { | |
try template.configurationManagerString.write(toFile: outputFilePath, atomically: true, encoding: .utf8) | |
} else { | |
let outputFileUrl = URL(fileURLWithPath: outputFilePath) | |
let outputFileDirectoryUrl = outputFileUrl.deletingLastPathComponent() | |
try fileManager.createDirectory(at: outputFileDirectoryUrl, withIntermediateDirectories: true, attributes: nil) | |
try template.configurationManagerString.write(toFile: outputFilePath, atomically: true, encoding: .utf8) | |
} | |
} | |
} | |
class ConfigurationManagerTemplate { | |
private let configurationDictionaryFileName: String | |
private let configurationKey: String | |
private let configuration: Configuration | |
private lazy var configurationKeysString = generateConfigurationKeysString() | |
private lazy var configurationPropertiesString = generateConfigurationPropertiesString() | |
lazy var configurationManagerString = #""" | |
// This file is autogenerated. Do not modify! | |
// Generated by https://github.com/pgorzelany/SwiftConfiguration | |
import Foundation | |
public typealias Configuration = Dictionary<String, Any> | |
class SwiftConfiguration { | |
enum ConfigurationKey: String, CaseIterable { | |
\#(configurationKeysString) | |
} | |
// MARK: Shared instance | |
static let current = SwiftConfiguration() | |
// MARK: Properties | |
private let configurationKey = "\#(configurationKey)" | |
private let configurationPlistFileName = "\#(configurationDictionaryFileName)" | |
private let configurationDictionary: NSDictionary | |
let configuration: Configuration | |
// MARK: Configuration properties | |
\#(configurationPropertiesString) | |
// MARK: Lifecycle | |
init(targetConfiguration: Configuration? = nil) { | |
let bundle = Bundle(for: SwiftConfiguration.self) | |
guard let configurationDictionaryPath = bundle.path(forResource: configurationPlistFileName, ofType: nil), | |
let configurationDictionary = NSDictionary(contentsOfFile: configurationDictionaryPath), | |
let configuration = configurationDictionary[configurationKey] as? Configuration | |
else { | |
fatalError("Configuration Error") | |
} | |
self.configuration = configuration | |
self.configurationDictionary = configurationDictionary | |
} | |
// MARK: Methods | |
func value<T>(for key: ConfigurationKey) -> T { | |
guard let value = configuration[key.rawValue] as? T else { | |
fatalError("No value satisfying requirements") | |
} | |
return value | |
} | |
} | |
"""# | |
init(configuration: Configuration, configurationKey: String, configurationPlistFilePath: String) { | |
self.configuration = configuration | |
self.configurationKey = configurationKey | |
self.configurationDictionaryFileName = (configurationPlistFilePath as NSString).lastPathComponent | |
} | |
func generateConfigurationKeysString() -> String { | |
var configurationKeysString = "" | |
let allKeys = configuration.keys | |
for key in allKeys.sorted() { | |
configurationKeysString += "case \(key)\n\t\t" | |
} | |
return configurationKeysString | |
} | |
func generateConfigurationPropertiesString() -> String { | |
var configurationPropertiesString = "" | |
let sortedContents = configuration | |
.sorted(by: {$0.key <= $1.key}) | |
for (key, value) in sortedContents { | |
configurationPropertiesString += """ | |
\tvar \(key): \(getPlistType(for: value)) { | |
\t\treturn value(for: .\(key)) | |
\t}\n\n | |
""" | |
} | |
return configurationPropertiesString | |
} | |
} | |
private func getPlistType<T>(for value: T) -> String { | |
if value is String { | |
return "String" | |
} else if let numberValue = value as? NSNumber { | |
let boolTypeId = CFBooleanGetTypeID() | |
let valueTypeId = CFGetTypeID(numberValue) | |
if boolTypeId == valueTypeId { | |
return "Bool" | |
} else if value is Int { | |
return "Int" | |
} else if value is Double { | |
return "Double" | |
} | |
fatalError("Unsuported type") | |
} else if value is Date { | |
return "Date" | |
} else { | |
fatalError("Unsuported type") | |
} | |
} | |
public class ConfigurationProvider { | |
public init() {} | |
// MARK: Methods | |
public func getConfiguration(at configurationPlistFilePath: String, key configurationKey: String) throws -> Configuration { | |
guard let configurationDictionary = NSDictionary(contentsOfFile: configurationPlistFilePath) else { | |
throw ConfigurationError(message: "Could not load configuration dictionary at: \(configurationPlistFilePath)") | |
} | |
print(configurationDictionary) | |
guard let configuration = configurationDictionary[configurationKey] as? Configuration else { | |
throw ConfigurationError(message: "The configuration file has invalid format. Please refer to the docs.") | |
} | |
return configuration | |
} | |
} | |
public struct Environment { | |
public let plistFilePath: String | |
} | |
public class EnvironmentParser { | |
public init() {} | |
private let processEnvironemnt = ProcessInfo.processInfo.environment | |
public func parseEnvironment() throws -> Environment { | |
guard let _ = processEnvironemnt["CONFIGURATION"] else { | |
throw ConfigurationError(message: "Could not obtain the active configuration from the environment variables") | |
} | |
guard let projectDirectory = processEnvironemnt["PROJECT_DIR"] else { | |
throw ConfigurationError(message: "Could not obtain the PROJECT_DIR path from the environment variables") | |
} | |
guard let relativePlistFilePath = processEnvironemnt["INFOPLIST_FILE"] else { | |
throw ConfigurationError(message: "Could not obtain the INFOPLIST_FILE path from the environment variables") | |
} | |
let plistFilePath = "\(projectDirectory)/\(relativePlistFilePath)" | |
return Environment(plistFilePath: plistFilePath) | |
} | |
} | |
public class MessagePrinter { | |
public init() {} | |
/// The warning will show up in compiler build time warnings | |
public func printWarning(_ items: Any...) { | |
for item in items { | |
print("warning: \(item)") | |
} | |
} | |
/// The error will show up in compiler build time errors | |
public func printError(_ items: Any...) { | |
for item in items { | |
print("error: \(item)") | |
} | |
} | |
} | |
private let environment = ProcessInfo.processInfo.environment | |
private let printer = MessagePrinter() | |
private let argumentsParser = ArgumentsParser() | |
private let configurationProvider = ConfigurationProvider() | |
private let configurationKey = "SwiftConfiguration.currentConfiguration" | |
do { | |
let arguments = try argumentsParser.parseArguments(CommandLine.arguments) | |
let configurationManagerGenerator = ConfigurationManagerGenerator(configurationPlistFilePath: arguments.configurationPlistFilePath, | |
outputFilePath: arguments.outputFilePath, | |
configurationKey: configurationKey) | |
let configuration = try configurationProvider.getConfiguration(at: arguments.configurationPlistFilePath, key: configurationKey) | |
try configurationManagerGenerator.generateConfigurationManagerFile(for: configuration) | |
exit(0) | |
} catch { | |
printer.printError(error.localizedDescription) | |
exit(0) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment