Skip to content

Instantly share code, notes, and snippets.

@aclima93
Created August 6, 2020 00:02
Show Gist options
  • Save aclima93/71669b068105b04b2268c56abea70768 to your computer and use it in GitHub Desktop.
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
#!/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