Skip to content

Commit

Permalink
Fix Array support (#42)
Browse files Browse the repository at this point in the history
* [Configen] Fix support for arrays

* [Configen] Small tweak to utils class

* [Project] Update README

* [Project] Re-indent files
  • Loading branch information
theblixguy authored Jan 14, 2020
1 parent 79cec19 commit f0717c8
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 24 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ You have to make the type in your plist a string, and input either a number -- e

# Arrays

Configen also supports reading arrays from the plist. For example, you can create an array of certificate hashes for pinning purposes and configen will automatically map them to an array in the generated configuration file.
Configen also supports reading arrays from the plist. For example, you can create an array of certificate hashes for pinning purposes and configen will automatically map them to an array in the generated configuration file. The arrays can be of any depth (for example: `[String]`, `[[URL]]`, `[[[Any]]]`, etc).

The downside of using an array in the plist is that the order of the array and whether there are any array elements at all is not guaranteed, so keep that in mind when using this functionality.

Expand Down
19 changes: 18 additions & 1 deletion configen.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
2E378F961D5B811C00E9F524 /* OptionsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E378F951D5B811C00E9F524 /* OptionsParser.swift */; };
2E4A8F311D5C77AB0025C677 /* FileTemplates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E4A8F301D5C77AB0025C677 /* FileTemplates.swift */; };
2E4A8F331D5CB7D60025C677 /* FileGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E4A8F321D5CB7D60025C677 /* FileGenerator.swift */; };
7CBA523323CDB84600B7136C /* ArrayUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CBA523223CDB84600B7136C /* ArrayUtils.swift */; };
7CBA523623CDBB6300B7136C /* Character+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CBA523523CDBB6300B7136C /* Character+Extensions.swift */; };
91ED18E61E02A48F00475042 /* CommandLineKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91084DEB1E02A0A9004E353B /* CommandLineKit.swift */; };
91ED18E71E02A49200475042 /* Option.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91084DED1E02A0A9004E353B /* Option.swift */; };
91ED18E81E02A49500475042 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91084DEE1E02A0A9004E353B /* StringExtensions.swift */; };
Expand All @@ -31,7 +33,9 @@
/* Begin PBXFileReference section */
2E378F951D5B811C00E9F524 /* OptionsParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionsParser.swift; sourceTree = "<group>"; };
2E4A8F301D5C77AB0025C677 /* FileTemplates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileTemplates.swift; sourceTree = "<group>"; };
2E4A8F321D5CB7D60025C677 /* FileGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = FileGenerator.swift; sourceTree = "<group>"; tabWidth = 2; };
2E4A8F321D5CB7D60025C677 /* FileGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = FileGenerator.swift; sourceTree = "<group>"; tabWidth = 4; };
7CBA523223CDB84600B7136C /* ArrayUtils.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = ArrayUtils.swift; sourceTree = "<group>"; tabWidth = 4; };
7CBA523523CDBB6300B7136C /* Character+Extensions.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = "Character+Extensions.swift"; sourceTree = "<group>"; tabWidth = 4; };
91084DEB1E02A0A9004E353B /* CommandLineKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandLineKit.swift; sourceTree = "<group>"; };
91084DEC1E02A0A9004E353B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
91084DED1E02A0A9004E353B /* Option.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Option.swift; sourceTree = "<group>"; };
Expand All @@ -51,6 +55,14 @@
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
7CBA523423CDBB5400B7136C /* Extensions */ = {
isa = PBXGroup;
children = (
7CBA523523CDBB6300B7136C /* Character+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
91084DEA1E02A0A9004E353B /* CommandLineKit */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -81,11 +93,13 @@
EAD7A2A01BBABE83006A3921 /* configen */ = {
isa = PBXGroup;
children = (
7CBA523423CDBB5400B7136C /* Extensions */,
91084DEA1E02A0A9004E353B /* CommandLineKit */,
EAD7A2A11BBABE83006A3921 /* main.swift */,
2E378F951D5B811C00E9F524 /* OptionsParser.swift */,
2E4A8F301D5C77AB0025C677 /* FileTemplates.swift */,
2E4A8F321D5CB7D60025C677 /* FileGenerator.swift */,
7CBA523223CDB84600B7136C /* ArrayUtils.swift */,
);
path = configen;
sourceTree = "<group>";
Expand Down Expand Up @@ -131,6 +145,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = EAD7A2951BBABE83006A3921;
Expand All @@ -151,9 +166,11 @@
91ED18E81E02A49500475042 /* StringExtensions.swift in Sources */,
91ED18E71E02A49200475042 /* Option.swift in Sources */,
91ED18E61E02A48F00475042 /* CommandLineKit.swift in Sources */,
7CBA523623CDBB6300B7136C /* Character+Extensions.swift in Sources */,
EAD7A2A21BBABE83006A3921 /* main.swift in Sources */,
2E4A8F331D5CB7D60025C677 /* FileGenerator.swift in Sources */,
2E378F961D5B811C00E9F524 /* OptionsParser.swift in Sources */,
7CBA523323CDB84600B7136C /* ArrayUtils.swift in Sources */,
2E4A8F311D5C77AB0025C677 /* FileTemplates.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
115 changes: 115 additions & 0 deletions configen/ArrayUtils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//
// ArrayUtils.swift
// configen
//
// Created by Suyash Srijan on 13/01/2020.
// Copyright © 2020 The App Business. All rights reserved.
//

import Foundation

final class ArrayUtils {
enum Error: Swift.Error {
case invalidArrayType
}

/// Check if this is a valid Array type by checking if the brackets are balanced
static func isValidArrayType(_ rawType: String) -> Result<String, Error> {
var stack: [Character] = []
var elementNameChars: [Character] = []

// A quick and simple check to see if this could *potentially* be an
// Array type.
guard rawType.first == .lSquare && rawType.last == .rSquare else {
return .failure(.invalidArrayType)
}

// Okay, let's properly check if the brackets are balanced
for character in rawType {
if case .lSquare = character {
stack.append(character)
continue
}

if case .rSquare = character {
guard stack.last == .lSquare else {
return .failure(.invalidArrayType)
}
_ = stack.popLast()
continue
}

// We're looking at something that is not a bracket. This is likely
// the name of the array's element type (the innermost name if this is
// a nested array). It can only be alphanumeric (i.e. String, UInt8, etc).
if character.isLetter || character.isNumber {
elementNameChars.append(character)
}
}

// If we extracted the name of the element type and the brackets were
// balanced, then this is a valid type.
if !elementNameChars.isEmpty && stack.isEmpty {
return .success(String(elementNameChars))
}

// Otherwise, either we're looking at a different type or the user
// has made a mistake when writing the type name.
return .failure(.invalidArrayType)
}

/// Format a raw value of NSArray type to a String representation
static func transformArrayToString(_ arrayElementType: String, rawValue: Any) -> String {
precondition(rawValue is NSArray, "Expected an 'NSArray', got '\(type(of: rawValue))'")
let rawValueToConvert = castArray(rawValue as! NSArray, arrayElementType: arrayElementType)
var rawValueString = "\(rawValueToConvert)".trimmingCharacters(in: .whitespacesAndNewlines)
// Special case: If we have an array of URLs, then we need to drop all
// double quotes.
if arrayElementType == "URL" {
rawValueString = rawValueString.filter { $0 != "\"" }
}
return rawValueString
}

/// Transform an NSArray to Swift Array by explicitly bridging each element.
private static func castArray(_ array: NSArray, arrayElementType: String) -> Any {
var temp: [Any] = []
for index in 0..<array.count {
if let item = array[index] as? NSArray {
temp.append(castArray(item, arrayElementType: arrayElementType))
} else {
temp.append(castArrayElement(array[index], arrayElementType: arrayElementType))
}
}

return temp
}

/// Cast a value to the given array element type
private static func castArrayElement(_ value: Any, arrayElementType: String) -> Any {
func emitFailedCastError() -> Never {
fatalError("Cast from element of type '\(type(of: value))' to " +
"type '\(arrayElementType)' is unsupported")
}
switch arrayElementType {
case "String":
return value as! String
case "Bool":
return value as! Bool
case "Int":
return value as! Int
case "Double":
return value as! Double
case "URL":
guard let str = value as? String, let _ = URL(string: str) else {
emitFailedCastError()
}
return "URL(string: \(str))!"
case "Any":
return value
default:
// TODO: Handle Data, Date, etc types
emitFailedCastError()
}
}
}
14 changes: 14 additions & 0 deletions configen/Extensions/Character+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// Character+Extensions.swift
// configen
//
// Created by Suyash Srijan on 13/01/2020.
// Copyright © 2020 The App Business. All rights reserved.
//

import Foundation

extension Character {
static let lSquare = Character("[")
static let rSquare = Character("]")
}
31 changes: 9 additions & 22 deletions configen/FileGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,16 @@ struct FileGenerator {
fatalError("Found URL without host: \(url) for setting: \(hint.variableName)")
}
line = template.urlImplementation
case let str where str.match(regex: "^(?:\\[)\\w+(?:\\])$"):
line = template.customImplementation
line.replace(token: template.variableNameToken, withString: hint.variableName)
line.replace(token: template.customTypeToken, withString: hint.type)
line.replace(token: template.valueToken, withString: formatRawArrayString(rawValue: value, rawType: str))
return line

default:
// Check if this is an Array type
if case let .success(arrayElementType) = ArrayUtils.isValidArrayType(hint.type) {
let arrayString = ArrayUtils.transformArrayToString(arrayElementType, rawValue: value)
line = template.customImplementation
line.replace(token: template.variableNameToken, withString: hint.variableName)
line.replace(token: template.customTypeToken, withString: hint.type)
line.replace(token: template.valueToken, withString: arrayString)
return line
}
guard value is String else {
fatalError("Value (\(value)) must be a string in order to be used by custom type \(hint.type)")
}
Expand All @@ -136,21 +138,6 @@ struct FileGenerator {

return line
}

private func formatRawArrayString(rawValue: AnyObject, rawType: String) -> String {
var rawTypeCopy = rawType
var rawValueStr = "\(rawValue)"

rawTypeCopy = String(rawTypeCopy.dropFirst()) // drop [
rawTypeCopy = String(rawTypeCopy.dropLast()) // drop ]

rawValueStr = String(rawValueStr.dropFirst()) // drop (
rawValueStr = String(rawValueStr.dropLast()) // drop )

rawValueStr = rawValueStr.trimmingCharacters(in: .whitespacesAndNewlines)

return "[\(rawValueStr)]"
}
}

extension String {
Expand Down

0 comments on commit f0717c8

Please sign in to comment.