Skip to content

Commit

Permalink
Allow properties to be defined as instance variables
Browse files Browse the repository at this point in the history
Should be an edge case for usage, but useful for any application where
you might need to refer to configuration values by keypaths or
reflection
  • Loading branch information
dhardiman committed Sep 25, 2019
1 parent ed7c560 commit e462436
Show file tree
Hide file tree
Showing 11 changed files with 90 additions and 45 deletions.
17 changes: 12 additions & 5 deletions Sources/Config/ConfigurationFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ struct Configuration {
}

// swiftlint:disable:next identifier_name IV is a well understood abbreviation
func stringRepresentation(scheme: String, iv: IV, encryptionKey: String?, requiresNonObjcDeclarations: Bool, publicProperties: Bool, indentWidth: Int = 0) -> String {
func stringRepresentation(scheme: String, iv: IV, encryptionKey: String?, requiresNonObjcDeclarations: Bool, publicProperties: Bool, instanceProperties: Bool, indentWidth: Int = 0) -> String {
let separator = "\n\n"
let propertiesString = properties.values.map({ $0.propertyDeclaration(for: keyForProperty($0, in: scheme), iv: iv, encryptionKey: encryptionKey, requiresNonObjCDeclarations: requiresNonObjcDeclarations, isPublic: publicProperties, indentWidth: indentWidth) })
let propertiesString = properties.values.map({ $0.propertyDeclaration(for: keyForProperty($0, in: scheme), iv: iv, encryptionKey: encryptionKey, requiresNonObjCDeclarations: requiresNonObjcDeclarations, isPublic: publicProperties, instanceProperty: instanceProperties, indentWidth: indentWidth) })
.sorted()
.joined(separator: separator)

Expand All @@ -67,7 +67,7 @@ struct Configuration {
className = className.replacingCharacters(in: startIndex...startIndex, with: firstLetter.description.uppercased())
return """
\(String.indent(for: indentWidth))public enum \(className) {
\(config.value.stringRepresentation(scheme: scheme, iv: iv, encryptionKey: encryptionKey, requiresNonObjcDeclarations: requiresNonObjcDeclarations, publicProperties: publicProperties, indentWidth: indentWidth + 1))
\(config.value.stringRepresentation(scheme: scheme, iv: iv, encryptionKey: encryptionKey, requiresNonObjcDeclarations: requiresNonObjcDeclarations, publicProperties: publicProperties, instanceProperties: instanceProperties, indentWidth: indentWidth + 1))
\(String.indent(for: indentWidth))}
"""
}
Expand Down Expand Up @@ -183,6 +183,10 @@ struct ConfigurationFile: Template {

let defaultType: PropertyType?

let entityType: String?

let outputAsInstanceVariables: Bool

init(config: [String: Any], name: String, scheme: String, source: URL) throws {
self.scheme = scheme
self.name = name
Expand All @@ -200,6 +204,9 @@ struct ConfigurationFile: Template {
self.defaultType = nil
}

self.entityType = template?["entityType"] as? String
self.outputAsInstanceVariables = (template?["instanceVariables"] as? Bool) ?? false

var referenceSource: [String: Any]?
if let referenceSourceFileName = template?["referenceSource"] as? String {
let referenceSourceURL = source.appendingPathComponent(referenceSourceFileName).appendingPathExtension("config")
Expand Down Expand Up @@ -237,9 +244,9 @@ struct ConfigurationFile: Template {
var description: String {
let extendedClass = template?["extensionOn"] as? String
let requiresNonObjcDeclarations = template?["requiresNonObjC"] as? Bool ?? false
let values = rootConfiguration.stringRepresentation(scheme: scheme, iv: iv, encryptionKey: encryptionKey, requiresNonObjcDeclarations: requiresNonObjcDeclarations, publicProperties: extendedClass == nil)
let values = rootConfiguration.stringRepresentation(scheme: scheme, iv: iv, encryptionKey: encryptionKey, requiresNonObjcDeclarations: requiresNonObjcDeclarations, publicProperties: extendedClass == nil, instanceProperties: outputAsInstanceVariables)

let entityType = extendedClass != nil ? "extension" : "enum"
let entityType = extendedClass != nil ? "extension" : (self.entityType ?? "enum")
let importsString = imports.sorted().map { "import \($0)" }.joined(separator: "\n")

return outputTemplate
Expand Down
16 changes: 8 additions & 8 deletions Sources/Config/ConfigurationProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,17 @@ struct ConfigurationProperty<T>: Property, AssociatedPropertyKeyProviding {
return defaultValue
}

func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, indentWidth: Int) -> String {
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String {
var template: String = ""
if let description = description {
template += "\(String.indent(for: indentWidth))/// \(description)\n"
}
if requiresNonObjCDeclarations {
template += computedProperty(nonObjc: true, indentWidth: indentWidth, isPublic: isPublic, outputProvidesReturn: type?.valueProvidesReturn ?? false)
template += computedProperty(nonObjc: true, indentWidth: indentWidth, isPublic: isPublic, instanceProperty: instanceProperty, outputProvidesReturn: type?.valueProvidesReturn ?? false)
} else {
template += (type?.computedProperty ?? false) ?
computedProperty(nonObjc: false, indentWidth: indentWidth, isPublic: isPublic, outputProvidesReturn: type?.valueProvidesReturn ?? false) :
staticProperty(indentWidth: indentWidth, isPublic: isPublic)
computedProperty(nonObjc: false, indentWidth: indentWidth, isPublic: isPublic, instanceProperty: instanceProperty, outputProvidesReturn: type?.valueProvidesReturn ?? false) :
storedProperty(indentWidth: indentWidth, isPublic: isPublic, instanceProperty: instanceProperty)
}
let propertyValue = value(for: scheme)
let outputValue: String
Expand Down Expand Up @@ -134,11 +134,11 @@ struct ConfigurationProperty<T>: Property, AssociatedPropertyKeyProviding {
return value
}

private func computedProperty(nonObjc: Bool, indentWidth: Int, isPublic: Bool, outputProvidesReturn: Bool) -> String {
private func computedProperty(nonObjc: Bool, indentWidth: Int, isPublic: Bool, instanceProperty: Bool, outputProvidesReturn: Bool) -> String {
let modifiers = [
nonObjc ? "@nonobjc" : nil,
isPublic ? "public" : nil,
"static",
(instanceProperty ? "" : "static"),
"var"
]
.compactMap { $0 }
Expand All @@ -150,7 +150,7 @@ struct ConfigurationProperty<T>: Property, AssociatedPropertyKeyProviding {
"""
}

private func staticProperty(indentWidth: Int, isPublic: Bool) -> String {
return "\(String.indent(for: indentWidth))\(isPublic ? "public " : "")static let {key}: {typeName} = {value}"
private func storedProperty(indentWidth: Int, isPublic: Bool, instanceProperty: Bool) -> String {
return "\(String.indent(for: indentWidth))\(isPublic ? "public " : "")\(instanceProperty ? "" : "static ")let {key}: {typeName} = {value}"
}
}
12 changes: 6 additions & 6 deletions Sources/Config/CustomType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ struct CustomProperty: Property {
return customType.typeName
}

func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, indentWidth: Int) -> String {
return template(for: description, isPublic: isPublic, indentWidth: indentWidth).replacingOccurrences(of: "{key}", with: key)
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String {
return template(for: description, isPublic: isPublic, instanceProperty: instanceProperty, indentWidth: indentWidth).replacingOccurrences(of: "{key}", with: key)
.replacingOccurrences(of: "{typeName}", with: typeName)
.replacingOccurrences(of: "{value}", with: outputValue(for: scheme, type: customType))
}
Expand Down Expand Up @@ -94,8 +94,8 @@ struct CustomPropertyArray: Property {
return "[\(customType.typeName)]"
}

func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, indentWidth: Int) -> String {
return template(for: description, isPublic: isPublic, indentWidth: indentWidth).replacingOccurrences(of: "{key}", with: key)
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String {
return template(for: description, isPublic: isPublic, instanceProperty: instanceProperty, indentWidth: indentWidth).replacingOccurrences(of: "{key}", with: key)
.replacingOccurrences(of: "{typeName}", with: typeName)
.replacingOccurrences(of: "{value}", with: outputValue(for: scheme, type: customType))
}
Expand Down Expand Up @@ -131,12 +131,12 @@ private func valueString(for placeholder: Placeholder, from value: Any) -> Strin
}
}

private func template(for description: String?, isPublic: Bool, indentWidth: Int) -> String {
private func template(for description: String?, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String {
var template: String = ""
if let description = description {
template += "\(String.indent(for: indentWidth))/// \(description)\n"
}
template += "\(String.indent(for: indentWidth))\(isPublic ? "public " : "")static let {key}: {typeName} = {value}"
template += "\(String.indent(for: indentWidth))\(isPublic ? "public " : "")\(instanceProperty ? "" : "static ")let {key}: {typeName} = {value}"
return template
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/Config/IV.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ struct IV: Property {
hash = try dict.hashRepresentation()
}

func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, indentWidth: Int) -> String {
return "\(String.indent(for: indentWidth))\(isPublic ? "public " : "")static let \(key): \(typeName) = \(byteArrayOutput(from: Array(hash.utf8)))"
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String {
return "\(String.indent(for: indentWidth))\(isPublic ? "public " : "")\(instanceProperty ? "" : "static ")let \(key): \(typeName) = \(byteArrayOutput(from: Array(hash.utf8)))"
}
}
2 changes: 1 addition & 1 deletion Sources/Config/Property.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ protocol Property {
var key: String { get }
var typeName: String { get }
var associatedProperty: String? { get }
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, indentWidth: Int) -> String
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String
}

private func dictionaryValue(_ dict: [String: Any]) -> String {
Expand Down
6 changes: 3 additions & 3 deletions Sources/Config/ReferenceProperty.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,19 @@ struct ReferenceProperty: Property {
return defaultValue
}

func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, indentWidth: Int) -> String {
func propertyDeclaration(for scheme: String, iv: IV, encryptionKey: String?, requiresNonObjCDeclarations: Bool, isPublic: Bool, instanceProperty: Bool, indentWidth: Int) -> String {
var template: String = ""
if let description = description {
template += "\(String.indent(for: indentWidth))/// \(description)\n"
}
if requiresNonObjCDeclarations {
template += """
\(String.indent(for: indentWidth))@nonobjc\(isPublic ? " public" : "") static var \(key): \(typeName) {
\(String.indent(for: indentWidth))@nonobjc\(isPublic ? " public" : "") \(instanceProperty ? "" : "static ")var \(key): \(typeName) {
\(String.indent(for: indentWidth + 1))return \(value(for: scheme))
\(String.indent(for: indentWidth))}
"""
} else {
template += "\(String.indent(for: indentWidth))\(isPublic ? "public " : "")static let \(key): \(typeName) = \(value(for: scheme))"
template += "\(String.indent(for: indentWidth))\(isPublic ? "public " : "")\(instanceProperty ? "" : "static ")let \(key): \(typeName) = \(value(for: scheme))"
}
return template
}
Expand Down
31 changes: 31 additions & 0 deletions Tests/ConfigTests/ConfigurationFileTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,26 @@ class ConfigurationFileTests: XCTestCase {
expect(config.description).to(equal(expectedOutput))
}

func testItCanOutputValuesAsInstanceVariables() throws {
let config = try ConfigurationFile(config: configurationForInstanceVariables, name: "Test", scheme: "any", source: URL(fileURLWithPath: "/"))
let expectedOutput = """
/* Test.swift auto-generated from any */
import Foundation
// swiftlint:disable force_unwrapping type_body_length file_length superfluous_disable_command
public struct Test {
public let property: String = #"A test"#
public let schemeName: String = #"any"#
}
// swiftlint:enable force_unwrapping type_body_length file_length superfluous_disable_command
"""
expect(config.description).to(equal(expectedOutput))
}

func givenAConfigDictionary(withTemplate template: [String: Any]? = nil) -> [String: Any] {
var dictionary: [String: Any] = [:]
dictionary["template"] = template
Expand Down Expand Up @@ -527,3 +547,14 @@ private let configurationWithCommonPatterns: [String: Any] = [
]
]
]

private let configurationForInstanceVariables: [String: Any] = [
"template": [
"entityType": "struct",
"instanceVariables": true
],
"property": [
"type": "String",
"defaultValue": "A test"
]
]
8 changes: 4 additions & 4 deletions Tests/ConfigTests/ConfigurationPropertyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ class ConfigurationPropertyTests: XCTestCase {
])
}

func whenTheDeclarationIsWritten<T>(for configurationProperty: ConfigurationProperty<T>?, scheme: String = "any", encryptionKey: String? = nil, isPublic: Bool = false, requiresNonObjC: Bool = false, indentWidth: Int = 0) throws -> String? {
func whenTheDeclarationIsWritten<T>(for configurationProperty: ConfigurationProperty<T>?, scheme: String = "any", encryptionKey: String? = nil, isPublic: Bool = false, instanceProperty: Bool = false, requiresNonObjC: Bool = false, indentWidth: Int = 0) throws -> String? {
let iv = try IV(dict: ["initialise": "me"])
print("\(iv.hash)")
return configurationProperty?.propertyDeclaration(for: scheme, iv: iv, encryptionKey: encryptionKey, requiresNonObjCDeclarations: requiresNonObjC, isPublic: isPublic, indentWidth: indentWidth)
return configurationProperty?.propertyDeclaration(for: scheme, iv: iv, encryptionKey: encryptionKey, requiresNonObjCDeclarations: requiresNonObjC, isPublic: isPublic, instanceProperty: instanceProperty, indentWidth: indentWidth)
}

func testItCanWriteADeclarationForAStringPropertyUsingTheDefaultValue() throws {
Expand All @@ -37,8 +37,8 @@ class ConfigurationPropertyTests: XCTestCase {

func testItCanWriteADeclarationForAnEmptyStringProperty() throws {
let stringProperty = givenAStringProperty()
let expectedValue = ##" static let test: String = """##
let actualValue = try whenTheDeclarationIsWritten(for: stringProperty, scheme: "empty")
let expectedValue = ##" let test: String = """##
let actualValue = try whenTheDeclarationIsWritten(for: stringProperty, scheme: "empty", instanceProperty: true)
expect(actualValue).to(equal(expectedValue))
}

Expand Down
Loading

0 comments on commit e462436

Please sign in to comment.