diff --git a/CHANGELOG.md b/CHANGELOG.md index f2270820..85941318 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ * Refactor stencil node tests to not use templates and output files. [David Jennes](https://github.com/djbe) [#17](https://github.com/SwiftGen/StencilSwiftKit/issues/17) +* Add a "parameters parser" able to transform parameters passed as a set of strings (`a=1 b.x=2 b.y=3 c=4 c=5`) — typically provided as the command line arguments of a CLI — into a Dictionary suitable for Stencil contexts. + [David Jennes](https://github.com/djbe) + [#8](https://github.com/SwiftGen/StencilSwiftKit/pull/8) ## Before 5.0.0 diff --git a/Podfile.lock b/Podfile.lock index c2751df3..3e4d5506 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -23,7 +23,7 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: PathKit: f8260c3e41bf4d552f3603853e32a5b325a176d4 Stencil: 4177c0cabcdc40dd9be9b7f701d710d0a121023a - StencilSwiftKit: e06089cf69771405f2afabe48df97ed9ac159593 + StencilSwiftKit: e2caece14b65d144da6bbe513a73b01be4833e7b PODFILE CHECKSUM: 254c61439b3ceadef162f42c05a5d1c157f1a6df diff --git a/Pods/Local Podspecs/StencilSwiftKit.podspec.json b/Pods/Local Podspecs/StencilSwiftKit.podspec.json index 8a2acaa8..f975110d 100644 --- a/Pods/Local Podspecs/StencilSwiftKit.podspec.json +++ b/Pods/Local Podspecs/StencilSwiftKit.podspec.json @@ -2,7 +2,7 @@ "name": "StencilSwiftKit", "version": "0.0.1", "summary": "Stencil additions dedicated for Swift code generation", - "description": "TODO", + "description": "This pod contains some additional nodes and filters for\n[Stencil](https://github.com/kylef/Stencil).\nThese additional nodes & filters are mainly dedicated\nfor writing Stencil templates generating *Swift* code.", "homepage": "https://github.com/SwiftGen/StencilSwiftKit", "license": "MIT", "authors": { diff --git a/Pods/Manifest.lock b/Pods/Manifest.lock index c2751df3..3e4d5506 100644 --- a/Pods/Manifest.lock +++ b/Pods/Manifest.lock @@ -23,7 +23,7 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: PathKit: f8260c3e41bf4d552f3603853e32a5b325a176d4 Stencil: 4177c0cabcdc40dd9be9b7f701d710d0a121023a - StencilSwiftKit: e06089cf69771405f2afabe48df97ed9ac159593 + StencilSwiftKit: e2caece14b65d144da6bbe513a73b01be4833e7b PODFILE CHECKSUM: 254c61439b3ceadef162f42c05a5d1c157f1a6df diff --git a/Pods/Pods.xcodeproj/project.pbxproj b/Pods/Pods.xcodeproj/project.pbxproj index b5299d1b..5cb65420 100644 --- a/Pods/Pods.xcodeproj/project.pbxproj +++ b/Pods/Pods.xcodeproj/project.pbxproj @@ -44,6 +44,8 @@ B65CC4D6EE2210495A18E7695A3E6DC5 /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27AB13C581DBF4D4B41876C69872B183 /* Extension.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; C22E1920B40D3E75EE6C1B253A0B3E91 /* IfTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D887BD539929925B9535E5136F861F20 /* IfTag.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; DD426D511E3B8E9B007D843B /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD426D4F1E3B8E90007D843B /* Environment.swift */; }; + DD426D5D1E3CE811007D843B /* Parameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD426D5C1E3CE811007D843B /* Parameters.swift */; }; + DDFD1F671E5357BF0023AE2B /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFD1F661E5357BF0023AE2B /* Context.swift */; }; E436943415A371D33CD28BF20935A2F9 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2497E0C37C035DB8276C0C7335E3FC93 /* Cocoa.framework */; }; F83DF009352A70B747BDA63DBF87103F /* ForTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CCB7B571DD30214319D05B2A2D01268 /* ForTag.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; F93EC375CD4D233014C44DBDE515C6D2 /* Lexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31A007C042C580A003C9B6DB5AD1B534 /* Lexer.swift */; settings = {COMPILER_FLAGS = "-w -Xanalyzer -analyzer-disable-all-checks"; }; }; @@ -158,8 +160,10 @@ D5846CC8D36C9B97B51ACC82399D6A12 /* PathKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "PathKit-prefix.pch"; sourceTree = ""; }; D6C2D28B3980163C3F38852C8437E5C2 /* Stencil.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "sourcecode.module-map"; path = Stencil.modulemap; sourceTree = ""; }; D887BD539929925B9535E5136F861F20 /* IfTag.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = IfTag.swift; path = Sources/IfTag.swift; sourceTree = ""; }; - E09480DB5774EB9FBBAE030F88F5BCC3 /* StencilSwiftTemplate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StencilSwiftTemplate.swift; sourceTree = ""; }; DD426D4F1E3B8E90007D843B /* Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; + DD426D5C1E3CE811007D843B /* Parameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Parameters.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + DDFD1F661E5357BF0023AE2B /* Context.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; + E09480DB5774EB9FBBAE030F88F5BCC3 /* StencilSwiftTemplate.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StencilSwiftTemplate.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -214,8 +218,10 @@ isa = PBXGroup; children = ( 95F48C2804B8C05C9EE9EB78ACC7C2E5 /* CallNode.swift */, + DDFD1F661E5357BF0023AE2B /* Context.swift */, DD426D4F1E3B8E90007D843B /* Environment.swift */, B9C2D0FB00398A5111567EEFD1CCB01B /* Filters.swift */, + DD426D5C1E3CE811007D843B /* Parameters.swift */, A03EA653264F980DD920923E3D631E27 /* SetNode.swift */, E09480DB5774EB9FBBAE030F88F5BCC3 /* StencilSwiftTemplate.swift */, 268C13CD136CE7EDCA3ABF3F31928DD5 /* SwiftIdentifier.swift */, @@ -576,7 +582,9 @@ A082AAC03E6892EC107B7421BC93838D /* SetNode.swift in Sources */, 59F231D22411C3AA93D3D9B9D9E555F5 /* StencilSwiftKit-dummy.m in Sources */, 02A898432BFB56500E31943EA2E63D91 /* SwiftIdentifier.swift in Sources */, + DDFD1F671E5357BF0023AE2B /* Context.swift in Sources */, 24DD620B7037C62A14EA6FD0AE38FF3D /* StencilSwiftTemplate.swift in Sources */, + DD426D5D1E3CE811007D843B /* Parameters.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/README.md b/README.md index 2c9aba59..1cda7db2 100644 --- a/README.md +++ b/README.md @@ -46,3 +46,33 @@ This template subclass aims to remove those lines generated by using a simple wo ## Stencil.Extension & swiftStencilEnvironment This framework also contains [helper methods for `Stencil.Extension` and `Stencil.Environment`](https://github.com/SwiftGen/StencilSwiftKit/blob/master/Sources/Environment.swift), to easily register all the tags and filters listed above on an existing `Stencil.Extension`, as well as to easily get a `Stencil.Environment` preconfigured with both those tags & filters `Extension` and the `StencilSwiftTemplate`. + +## Parameters + +This framework contains an additional parser, meant to parse a list of parameters from the CLI. For example, using [Commander](https://github.com/kylef/Commander), if you receive a `[String]` from a `VariadicOption`, you can use the parser to convert it into a structured dictionary. For example: + +```swift +["foo=1", "bar=2", "baz.qux=hello", "baz.items=a", "baz.items=b", "something"] +``` + +will become + +```swift +[ + "foo": "1", + "bar": "2", + "baz": [ + "qux": "hello", + "items": [ + "a", + "b" + ] + ], + something: true +] +``` + +For easier use, you can use the `enrich(context:parameters:)` function to add the following variables to a context: + +- `param`: the parsed parameters using the parser mentioned above. +- `env`: a dictionary with all available environment variables (such as `PATH`). diff --git a/Sources/Context.swift b/Sources/Context.swift new file mode 100644 index 00000000..aef8bbb2 --- /dev/null +++ b/Sources/Context.swift @@ -0,0 +1,18 @@ +// +// Context.swift +// Pods +// +// Created by David Jennes on 14/02/2017. +// +// + +import Foundation + +public func enrich(context: [AnyHashable: Any], parameters: [String]) throws -> [AnyHashable: Any] { + var context = context + + context["env"] = ProcessInfo().environment + context["param"] = try Parameters.parse(items: parameters) + + return context +} diff --git a/Sources/Parameters.swift b/Sources/Parameters.swift new file mode 100644 index 00000000..69a88b92 --- /dev/null +++ b/Sources/Parameters.swift @@ -0,0 +1,80 @@ +// +// StencilSwiftKit +// Copyright (c) 2017 SwiftGen +// MIT Licence +// + +import Foundation + +public enum ParametersError: Error { + case invalidSyntax(value: Any) + case invalidKey(key: String, value: Any) + case invalidStructure(key: String, oldValue: Any, newValue: Any) +} + +public enum Parameters { + typealias Parameter = (key: String, value: Any) + public typealias StringDict = [String: Any] + + public static func parse(items: [String]) throws -> StringDict { + let parameters: [Parameter] = try items.map { item in + let parts = item.components(separatedBy: "=") + if parts.count >= 2 { + return (key: parts[0], value: parts.dropFirst().joined(separator: "=")) + } else if let part = parts.first, parts.count == 1 && validate(key: part) { + return (key: part, value: true) + } else { + throw ParametersError.invalidSyntax(value: item) + } + } + + return try parameters.reduce(StringDict()) { + try parse(parameter: $1, result: $0) + } + } + + private static func parse(parameter: Parameter, result: StringDict) throws -> StringDict { + let parts = parameter.key.components(separatedBy: ".") + let key = parts.first ?? "" + var result = result + + // validate key + guard validate(key: key) else { throw ParametersError.invalidKey(key: parameter.key, value: parameter.value) } + + // no sub keys, may need to convert to array if repeat key if possible + if parts.count == 1 { + if let current = result[key] as? [Any] { + result[key] = current + [parameter.value] + } else if let current = result[key] as? String { + result[key] = [current, parameter.value] + } else if let current = result[key] { + throw ParametersError.invalidStructure(key: key, oldValue: current, newValue: parameter.value) + } else { + result[key] = parameter.value + } + } else if parts.count > 1 { + guard result[key] is StringDict || result[key] == nil else { + throw ParametersError.invalidStructure(key: key, oldValue: result[key] ?? "", newValue: parameter.value) + } + + // recurse into sub keys + let current = result[key] as? StringDict ?? StringDict() + let sub = (key: parts.suffix(from: 1).joined(separator: "."), value: parameter.value) + result[key] = try parse(parameter: sub, result: current) + } + + return result + } + + // a valid key is not empty and only alphanumerical or dot + private static func validate(key: String) -> Bool { + return !key.isEmpty && + key.rangeOfCharacter(from: notAlphanumericsAndDot) == nil + } + + private static let notAlphanumericsAndDot: CharacterSet = { + var result = CharacterSet.alphanumerics + result.insert(".") + return result.inverted + }() +} diff --git a/StencilSwiftKit.xcodeproj/project.pbxproj b/StencilSwiftKit.xcodeproj/project.pbxproj index b7652816..e0b709d2 100644 --- a/StencilSwiftKit.xcodeproj/project.pbxproj +++ b/StencilSwiftKit.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ DD5F34301E21A3A200AEB5DA /* StringFiltersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5F342C1E21A3A200AEB5DA /* StringFiltersTests.swift */; }; DD5F34311E21A3A200AEB5DA /* SwiftIdentifierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD5F342D1E21A3A200AEB5DA /* SwiftIdentifierTests.swift */; }; DDE1E2F61E3E33E30043367C /* MacroNodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1E2F51E3E33E30043367C /* MacroNodeTests.swift */; }; + DDE1E2F91E3FABE70043367C /* ParametersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDE1E2F81E3FABE70043367C /* ParametersTests.swift */; }; + DDFD1F691E5358C70023AE2B /* ContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDFD1F681E5358C70023AE2B /* ContextTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXBuildRule section */ @@ -61,6 +63,8 @@ DD5F342C1E21A3A200AEB5DA /* StringFiltersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringFiltersTests.swift; sourceTree = ""; }; DD5F342D1E21A3A200AEB5DA /* SwiftIdentifierTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftIdentifierTests.swift; sourceTree = ""; }; DDE1E2F51E3E33E30043367C /* MacroNodeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacroNodeTests.swift; sourceTree = ""; }; + DDE1E2F81E3FABE70043367C /* ParametersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParametersTests.swift; sourceTree = ""; }; + DDFD1F681E5358C70023AE2B /* ContextTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -133,7 +137,9 @@ isa = PBXGroup; children = ( DD5F342A1E21A3A200AEB5DA /* CallNodeTests.swift */, + DDFD1F681E5358C70023AE2B /* ContextTests.swift */, DDE1E2F51E3E33E30043367C /* MacroNodeTests.swift */, + DDE1E2F81E3FABE70043367C /* ParametersTests.swift */, DD5F342B1E21A3A200AEB5DA /* SetNodeTests.swift */, DD5F342C1E21A3A200AEB5DA /* StringFiltersTests.swift */, DD5F342D1E21A3A200AEB5DA /* SwiftIdentifierTests.swift */, @@ -271,7 +277,9 @@ DDE1E2F61E3E33E30043367C /* MacroNodeTests.swift in Sources */, DD5F341B1E21993A00AEB5DA /* TestsHelper.swift in Sources */, DD5F34301E21A3A200AEB5DA /* StringFiltersTests.swift in Sources */, + DDFD1F691E5358C70023AE2B /* ContextTests.swift in Sources */, DD5F342E1E21A3A200AEB5DA /* CallNodeTests.swift in Sources */, + DDE1E2F91E3FABE70043367C /* ParametersTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/StencilSwiftKit.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme b/StencilSwiftKit.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme index 94299b3d..e407e256 100644 --- a/StencilSwiftKit.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme +++ b/StencilSwiftKit.xcodeproj/xcshareddata/xcschemes/Tests.xcscheme @@ -5,6 +5,22 @@ + + + + + +