diff --git a/.gitignore b/.gitignore
index c94db42..4d51e0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
.idea/vcs.xml
.idea/workspace.xml
.idea/xcode.xml
+regen.xcodeproj/project.xcworkspace/xcuserdata/idomizrachi.xcuserdatad
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..4330c2b
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,7 @@
+os: osx
+osx_image: xcode10.2
+language: swift
+script:
+ - xcodebuild -scheme regen clean build test
+after_success:
+ - bash <(curl -s https://codecov.io/bash) -J 'regen'
diff --git a/README.md b/README.md
index 8850061..f0cc4e5 100644
--- a/README.md
+++ b/README.md
@@ -97,6 +97,5 @@ To:
`Localization.loginButton`
-
## Credits\Thanks
https://github.com/icodeforlove/Colors for the ANSI color library
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..84be136
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,4 @@
+coverage:
+ ignore:
+ - "regen/Dependencies/**/*"
+ - "regen-tests/**/*"
diff --git a/regen-tests/ArgumentsParserTests.swift b/regen-tests/ArgumentsParserTests.swift
new file mode 100644
index 0000000..1c4657e
--- /dev/null
+++ b/regen-tests/ArgumentsParserTests.swift
@@ -0,0 +1,79 @@
+//
+// ArgumentsParserTests.swift
+// regen-tests
+//
+// Created by Ido Mizrachi on 12/07/2019.
+// Copyright © 2019 Ido Mizrachi. All rights reserved.
+//
+
+import XCTest
+
+class ArgumentsParserTests: XCTestCase {
+
+ func testEmptyArguments() {
+ let argumentsParser = ArgumentsParser(arguments: [])
+ switch argumentsParser.operationType {
+ case .usage: break
+ default: XCTFail("Bad operation")
+ }
+ }
+}
+
+class ArgumentsParserLocalizationTests: XCTestCase {
+
+ func testLocalizationOperationDefaultArguments() {
+ let argumentsParser = ArgumentsParser(arguments: ["localization"])
+ switch argumentsParser.operationType {
+ case .usage: break
+ default: XCTFail("Bad operation")
+ }
+
+ }
+
+ func testLocalizationOperationWithMissingSearchPath() {
+ let argumentsParser = ArgumentsParser(arguments: ["localization", "--template", "hello", "--output", "Gen.m"])
+ switch argumentsParser.operationType {
+ case .usage: break
+ default: XCTFail("Bad operation")
+ }
+ }
+
+// func testLocalizationOperationWithMissingOutput() {
+// let argumentsParser = ArgumentsParser(arguments: ["localization", "--search-path", "hello", "--template", "hello"])
+// switch argumentsParser.operationType {
+// case .usage: break
+// default: XCTFail("Bad operation")
+// }
+// }
+
+ func testLocalizationOperationWithMissingTemplate() {
+ let argumentsParser = ArgumentsParser(arguments: ["localization", "--output", "hello", "--search-path", "hello"])
+ switch argumentsParser.operationType {
+ case .usage: break
+ default: XCTFail("Bad operation")
+ }
+ }
+
+ func testImagesOperationWithDefaultArguments() {
+ let argumentsParser = ArgumentsParser(arguments: ["images"])
+ switch argumentsParser.operationType {
+ case .usage: break
+ default: XCTFail("Bad operation")
+ }
+ }
+
+
+// func testLocalizationOperationWithValidParameters() {
+// let argumentsParser = ArgumentsParser(arguments: ["localization", "--template", "hello", "--output", "Gen.swift", "--search-path", "Search Path", "--base-language-code", "he"])
+// switch argumentsParser.operationType {
+// case .localization(let parameters):
+// XCTAssertEqual(parameters.templateFilepath, "hello")
+// XCTAssertEqual(parameters.outputFilename, "Gen.swift")
+// XCTAssertEqual(parameters.searchPath, "Search Path")
+// XCTAssertEqual(parameters.baseLanguageCode, "he")
+// default: XCTFail("Bad operation")
+// }
+// }
+}
+
+
diff --git a/regen-tests/Info.plist b/regen-tests/Info.plist
new file mode 100644
index 0000000..6c40a6c
--- /dev/null
+++ b/regen-tests/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ BNDL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+
+
diff --git a/regen-tests/StringClassNameTests.swift b/regen-tests/StringClassNameTests.swift
new file mode 100644
index 0000000..7022c9d
--- /dev/null
+++ b/regen-tests/StringClassNameTests.swift
@@ -0,0 +1,28 @@
+//
+// StringClassNameTests.swift
+// regen-tests
+//
+// Created by Ido Mizrachi on 24/07/2019.
+// Copyright © 2019 Ido Mizrachi. All rights reserved.
+//
+
+import XCTest
+
+class StringClassNameTests: XCTestCase {
+
+ func testConvertToClassName() {
+ let text = "helloThere"
+ XCTAssertEqual(text.className(), "HelloThere")
+ }
+
+ func testEmptyString() {
+ let text = ""
+ XCTAssertEqual(text.className(), "")
+ }
+
+ func testAlreadyCapitalizedString() {
+ let text = "HOLYSheep"
+ XCTAssertEqual(text.className(), "HOLYSheep")
+ }
+
+}
diff --git a/regen-tests/regen_tests.swift b/regen-tests/regen_tests.swift
new file mode 100644
index 0000000..ed4a149
--- /dev/null
+++ b/regen-tests/regen_tests.swift
@@ -0,0 +1,15 @@
+//
+// regen_tests.swift
+// regen-tests
+//
+// Created by Ido Mizrachi on 12/07/2019.
+// Copyright © 2019 Ido Mizrachi. All rights reserved.
+//
+
+import XCTest
+
+
+class regen_tests: XCTestCase {
+
+
+}
diff --git a/regen.xcodeproj/project.pbxproj b/regen.xcodeproj/project.pbxproj
index edba8df..5947c40 100644
--- a/regen.xcodeproj/project.pbxproj
+++ b/regen.xcodeproj/project.pbxproj
@@ -7,23 +7,102 @@
objects = {
/* Begin PBXBuildFile section */
+ 622B8F2D22D9D821001FC1E4 /* ImagesNamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 622B8F2C22D9D821001FC1E4 /* ImagesNamespace.swift */; };
+ 622B8F2E22D9D821001FC1E4 /* ImagesNamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 622B8F2C22D9D821001FC1E4 /* ImagesNamespace.swift */; };
+ 622B8F3122D9D919001FC1E4 /* ImagesetParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A01D40EC1B00738100 /* ImagesetParser.swift */; };
+ 6236BBA622ECD7AC002615E9 /* _SwiftSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5AE22D7BBD300D4585A /* _SwiftSupport.swift */; };
+ 6236BBA722ECD7AC002615E9 /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5BB22D7BBD400D4585A /* Context.swift */; };
+ 6236BBA822ECD7AC002615E9 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5AA22D7BBD300D4585A /* Environment.swift */; };
+ 6236BBA922ECD7AC002615E9 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B322D7BBD400D4585A /* Errors.swift */; };
+ 6236BBAA22ECD7AC002615E9 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B422D7BBD400D4585A /* Expression.swift */; };
+ 6236BBAB22ECD7AD002615E9 /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5AC22D7BBD300D4585A /* Extension.swift */; };
+ 6236BBAC22ECD7AD002615E9 /* Filters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B222D7BBD400D4585A /* Filters.swift */; };
+ 6236BBAD22ECD7AD002615E9 /* FilterTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5BC22D7BBD400D4585A /* FilterTag.swift */; };
+ 6236BBAE22ECD7AD002615E9 /* ForTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5AB22D7BBD300D4585A /* ForTag.swift */; };
+ 6236BBAF22ECD7AD002615E9 /* IfTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5A922D7BBD300D4585A /* IfTag.swift */; };
+ 6236BBB022ECD7AD002615E9 /* Include.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B722D7BBD400D4585A /* Include.swift */; };
+ 6236BBB122ECD7AD002615E9 /* Inheritence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B522D7BBD400D4585A /* Inheritence.swift */; };
+ 6236BBB222ECD7AD002615E9 /* KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5AD22D7BBD300D4585A /* KeyPath.swift */; };
+ 6236BBB322ECD7AD002615E9 /* Lexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B822D7BBD400D4585A /* Lexer.swift */; };
+ 6236BBB422ECD7AD002615E9 /* Loader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5AF22D7BBD300D4585A /* Loader.swift */; };
+ 6236BBB522ECD7AD002615E9 /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B022D7BBD300D4585A /* Node.swift */; };
+ 6236BBB622ECD7AD002615E9 /* NowTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B922D7BBD400D4585A /* NowTag.swift */; };
+ 6236BBB722ECD7AD002615E9 /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B122D7BBD300D4585A /* Parser.swift */; };
+ 6236BBB822ECD7AD002615E9 /* Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5A822D7BBD300D4585A /* Template.swift */; };
+ 6236BBB922ECD7AD002615E9 /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B622D7BBD400D4585A /* Tokenizer.swift */; };
+ 6236BBBA22ECD7AD002615E9 /* Variable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5BA22D7BBD400D4585A /* Variable.swift */; };
+ 6236BBBB22ECE207002615E9 /* PathKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5D422D7BE6200D4585A /* PathKit.swift */; };
+ 624516F222E632F500E86FB1 /* LocalizationParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6247EE3922DBB65D0096078D /* LocalizationParameters.swift */; };
+ 624516F322E632FF00E86FB1 /* ImagesParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62492B5E22DE5E2700537757 /* ImagesParameters.swift */; };
+ 624516F422E6334C00E86FB1 /* LocalizationParametersParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627F4E9C22DF59EF0090E24F /* LocalizationParametersParser.swift */; };
+ 624516F522E6336200E86FB1 /* CommandLineParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627F4E9922DF551A0090E24F /* CommandLineParameter.swift */; };
+ 624516F622E6337A00E86FB1 /* ImageesAssetsFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E49E1D40EC1B00738100 /* ImageesAssetsFinder.swift */; };
+ 624516F722E6339100E86FB1 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994BF8351F8275FF006A62C3 /* Image.swift */; };
+ 624516F822E633D700E86FB1 /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994BF8331F8275DB006A62C3 /* Property.swift */; };
+ 624516F922E633E100E86FB1 /* PropertyName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4AB1D40EC1B00738100 /* PropertyName.swift */; };
+ 624516FA22E633F200E86FB1 /* Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6252986222D87AD200082EC3 /* Tree.swift */; };
+ 624516FB22E633FF00E86FB1 /* ImageNodeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994BF8311F8275B9006A62C3 /* ImageNodeItem.swift */; };
+ 624516FC22E6340D00E86FB1 /* ImagesFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A11D40EC1B00738100 /* ImagesFinder.swift */; };
+ 624516FD22E6353200E86FB1 /* ImagesValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A41D40EC1B00738100 /* ImagesValidator.swift */; };
+ 624516FE22E6353B00E86FB1 /* ClassName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 992D521A1F8418EB00258395 /* ClassName.swift */; };
+ 6247EE3A22DBB65D0096078D /* LocalizationParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6247EE3922DBB65D0096078D /* LocalizationParameters.swift */; };
+ 62492B5D22DE5D9300537757 /* whitelist.txt in Copy Files */ = {isa = PBXBuildFile; fileRef = 62492B5C22DE5D3E00537757 /* whitelist.txt */; };
+ 62492B5F22DE5E2700537757 /* ImagesParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62492B5E22DE5E2700537757 /* ImagesParameters.swift */; };
+ 6252986322D87AD200082EC3 /* Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6252986222D87AD200082EC3 /* Tree.swift */; };
+ 6252987822D87DF900082EC3 /* regen_tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6252987722D87DF900082EC3 /* regen_tests.swift */; };
+ 6268E5BD22D7BBD400D4585A /* Template.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5A822D7BBD300D4585A /* Template.swift */; };
+ 6268E5BE22D7BBD400D4585A /* IfTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5A922D7BBD300D4585A /* IfTag.swift */; };
+ 6268E5BF22D7BBD400D4585A /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5AA22D7BBD300D4585A /* Environment.swift */; };
+ 6268E5C022D7BBD400D4585A /* ForTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5AB22D7BBD300D4585A /* ForTag.swift */; };
+ 6268E5C122D7BBD400D4585A /* Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5AC22D7BBD300D4585A /* Extension.swift */; };
+ 6268E5C222D7BBD400D4585A /* KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5AD22D7BBD300D4585A /* KeyPath.swift */; };
+ 6268E5C322D7BBD400D4585A /* _SwiftSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5AE22D7BBD300D4585A /* _SwiftSupport.swift */; };
+ 6268E5C422D7BBD400D4585A /* Loader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5AF22D7BBD300D4585A /* Loader.swift */; };
+ 6268E5C522D7BBD400D4585A /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B022D7BBD300D4585A /* Node.swift */; };
+ 6268E5C622D7BBD400D4585A /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B122D7BBD300D4585A /* Parser.swift */; };
+ 6268E5C722D7BBD400D4585A /* Filters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B222D7BBD400D4585A /* Filters.swift */; };
+ 6268E5C822D7BBD400D4585A /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B322D7BBD400D4585A /* Errors.swift */; };
+ 6268E5C922D7BBD400D4585A /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B422D7BBD400D4585A /* Expression.swift */; };
+ 6268E5CA22D7BBD400D4585A /* Inheritence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B522D7BBD400D4585A /* Inheritence.swift */; };
+ 6268E5CB22D7BBD400D4585A /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B622D7BBD400D4585A /* Tokenizer.swift */; };
+ 6268E5CC22D7BBD400D4585A /* Include.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B722D7BBD400D4585A /* Include.swift */; };
+ 6268E5CD22D7BBD400D4585A /* Lexer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B822D7BBD400D4585A /* Lexer.swift */; };
+ 6268E5CE22D7BBD400D4585A /* NowTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5B922D7BBD400D4585A /* NowTag.swift */; };
+ 6268E5CF22D7BBD400D4585A /* Variable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5BA22D7BBD400D4585A /* Variable.swift */; };
+ 6268E5D022D7BBD400D4585A /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5BB22D7BBD400D4585A /* Context.swift */; };
+ 6268E5D122D7BBD400D4585A /* FilterTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5BC22D7BBD400D4585A /* FilterTag.swift */; };
+ 6268E5D522D7BE6200D4585A /* PathKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6268E5D422D7BE6200D4585A /* PathKit.swift */; };
+ 62741CAA22DC5DC800B66956 /* ParametersLocalizationTemplate.swift in Copy Files */ = {isa = PBXBuildFile; fileRef = 62741CA922DC5DBF00B66956 /* ParametersLocalizationTemplate.swift */; };
+ 627F4E9A22DF551A0090E24F /* CommandLineParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627F4E9922DF551A0090E24F /* CommandLineParameter.swift */; };
+ 627F4E9D22DF59EF0090E24F /* LocalizationParametersParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 627F4E9C22DF59EF0090E24F /* LocalizationParametersParser.swift */; };
+ 62A625DC22E87AA1007AEA56 /* StringClassNameTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A625DB22E87AA1007AEA56 /* StringClassNameTests.swift */; };
+ 62A625DE22E87E8B007AEA56 /* ImagesParametersParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A625DD22E87E8B007AEA56 /* ImagesParametersParser.swift */; };
+ 62A625DF22E87E8B007AEA56 /* ImagesParametersParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A625DD22E87E8B007AEA56 /* ImagesParametersParser.swift */; };
+ 62A625E022E8966E007AEA56 /* ImagesTemplate.swift in Copy Files */ = {isa = PBXBuildFile; fileRef = 622B8F2F22D9D85B001FC1E4 /* ImagesTemplate.swift */; };
+ 62CEA90F22DB10E200A20932 /* LocalizationTemplate.swift in Copy Files */ = {isa = PBXBuildFile; fileRef = 62793D3B222D6A140039D3F8 /* LocalizationTemplate.swift */; };
+ 62F5CC0B22D87EB2002B28B5 /* Usage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4AC1D40EC1B00738100 /* Usage.swift */; };
+ 62F5CC0C22D87ECB002B28B5 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E49F1D40EC1B00738100 /* Colors.swift */; };
+ 62F5CC0F22D8A99F002B28B5 /* ArgumentsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E49D1D40EC1B00738100 /* ArgumentsParser.swift */; };
+ 62F5CC1022D8AAA7002B28B5 /* OperationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4AA1D40EC1B00738100 /* OperationType.swift */; };
+ 62F5CC1222D8AB41002B28B5 /* ArgumentsParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F5CC1122D8AB41002B28B5 /* ArgumentsParserTests.swift */; };
+ 62F5CC1422D8C1AA002B28B5 /* LocalizationNamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F5CC1322D8C1AA002B28B5 /* LocalizationNamespace.swift */; };
+ 62F5CC1522D8D607002B28B5 /* LocalizationNamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62F5CC1322D8C1AA002B28B5 /* LocalizationNamespace.swift */; };
+ 62F5CC1622D8D621002B28B5 /* ImagesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A21D40EC1B00738100 /* ImagesOperation.swift */; };
992D521B1F8418EB00258395 /* ClassName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 992D521A1F8418EB00258395 /* ClassName.swift */; };
994BF8321F8275B9006A62C3 /* ImageNodeItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994BF8311F8275B9006A62C3 /* ImageNodeItem.swift */; };
994BF8341F8275DB006A62C3 /* Property.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994BF8331F8275DB006A62C3 /* Property.swift */; };
994BF8361F8275FF006A62C3 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994BF8351F8275FF006A62C3 /* Image.swift */; };
99667CD11F8C064B00D4A853 /* FileUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99667CD01F8C064B00D4A853 /* FileUtils.swift */; };
- 9977F4681F834C7E00D77D2C /* Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9977F4671F834C7E00D77D2C /* Tree.swift */; };
99ABD9F41EC7185D00032E1B /* OperationTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99ABD9F31EC7185D00032E1B /* OperationTimer.swift */; };
99AF6AF21EC5EB9600C91BA8 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99AF6AF11EC5EB9600C91BA8 /* Logger.swift */; };
99B9E4AE1D40EC1B00738100 /* ArgumentsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E49D1D40EC1B00738100 /* ArgumentsParser.swift */; };
- 99B9E4AF1D40EC1B00738100 /* AssetsFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E49E1D40EC1B00738100 /* AssetsFinder.swift */; };
+ 99B9E4AF1D40EC1B00738100 /* ImageesAssetsFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E49E1D40EC1B00738100 /* ImageesAssetsFinder.swift */; };
99B9E4B01D40EC1B00738100 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E49F1D40EC1B00738100 /* Colors.swift */; };
99B9E4B11D40EC1B00738100 /* ImagesetParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A01D40EC1B00738100 /* ImagesetParser.swift */; };
- 99B9E4B21D40EC1B00738100 /* ImageFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A11D40EC1B00738100 /* ImageFinder.swift */; };
- 99B9E4B31D40EC1B00738100 /* ImageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A21D40EC1B00738100 /* ImageOperation.swift */; };
+ 99B9E4B21D40EC1B00738100 /* ImagesFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A11D40EC1B00738100 /* ImagesFinder.swift */; };
+ 99B9E4B31D40EC1B00738100 /* ImagesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A21D40EC1B00738100 /* ImagesOperation.swift */; };
99B9E4B41D40EC1B00738100 /* ImagesClassGeneratorObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A31D40EC1B00738100 /* ImagesClassGeneratorObjC.swift */; };
99B9E4B51D40EC1B00738100 /* ImagesValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A41D40EC1B00738100 /* ImagesValidator.swift */; };
- 99B9E4B61D40EC1B00738100 /* LocalizationClassGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A51D40EC1B00738100 /* LocalizationClassGenerator.swift */; };
99B9E4B71D40EC1B00738100 /* LocalizationFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A61D40EC1B00738100 /* LocalizationFinder.swift */; };
99B9E4B81D40EC1B00738100 /* LocalizationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A71D40EC1B00738100 /* LocalizationOperation.swift */; };
99B9E4B91D40EC1B00738100 /* LocalizationParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4A81D40EC1B00738100 /* LocalizationParser.swift */; };
@@ -34,41 +113,81 @@
99B9E4BE1D40EC1B00738100 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B9E4AD1D40EC1B00738100 /* Version.swift */; };
99D74E1B1EC3919D003BFACE /* ImagesClassGeneratorSwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D74E1A1EC3919D003BFACE /* ImagesClassGeneratorSwift.swift */; };
99D74E1D1EC391BE003BFACE /* ImagesClassGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D74E1C1EC391BE003BFACE /* ImagesClassGenerator.swift */; };
- 99D74E341EC39AF8003BFACE /* LocalizationClassGeneratorObjc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D74E331EC39AF8003BFACE /* LocalizationClassGeneratorObjc.swift */; };
- 99D74E361EC39B62003BFACE /* LocalizationClassGeneratorSwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D74E351EC39B62003BFACE /* LocalizationClassGeneratorSwift.swift */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
- 99B9E4911D40EBF900738100 /* CopyFiles */ = {
+ 99B9E4911D40EBF900738100 /* Copy Files */ = {
isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = /usr/share/man/man1/;
- dstSubfolderSpec = 0;
+ buildActionMask = 12;
+ dstPath = "";
+ dstSubfolderSpec = 16;
files = (
+ 62A625E022E8966E007AEA56 /* ImagesTemplate.swift in Copy Files */,
+ 62492B5D22DE5D9300537757 /* whitelist.txt in Copy Files */,
+ 62741CAA22DC5DC800B66956 /* ParametersLocalizationTemplate.swift in Copy Files */,
+ 62CEA90F22DB10E200A20932 /* LocalizationTemplate.swift in Copy Files */,
);
- runOnlyForDeploymentPostprocessing = 1;
+ name = "Copy Files";
+ runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 622B8F2C22D9D821001FC1E4 /* ImagesNamespace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesNamespace.swift; sourceTree = ""; };
+ 622B8F2F22D9D85B001FC1E4 /* ImagesTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesTemplate.swift; sourceTree = ""; };
+ 6247EE3922DBB65D0096078D /* LocalizationParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationParameters.swift; sourceTree = ""; };
+ 62492B5C22DE5D3E00537757 /* whitelist.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = whitelist.txt; sourceTree = ""; };
+ 62492B5E22DE5E2700537757 /* ImagesParameters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesParameters.swift; sourceTree = ""; };
+ 6252986222D87AD200082EC3 /* Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tree.swift; sourceTree = ""; };
+ 6252987522D87DF900082EC3 /* regen-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "regen-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 6252987722D87DF900082EC3 /* regen_tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = regen_tests.swift; sourceTree = ""; };
+ 6252987922D87DF900082EC3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ 6268E5A822D7BBD300D4585A /* Template.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Template.swift; sourceTree = ""; };
+ 6268E5A922D7BBD300D4585A /* IfTag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IfTag.swift; sourceTree = ""; };
+ 6268E5AA22D7BBD300D4585A /* Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; };
+ 6268E5AB22D7BBD300D4585A /* ForTag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForTag.swift; sourceTree = ""; };
+ 6268E5AC22D7BBD300D4585A /* Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extension.swift; sourceTree = ""; };
+ 6268E5AD22D7BBD300D4585A /* KeyPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPath.swift; sourceTree = ""; };
+ 6268E5AE22D7BBD300D4585A /* _SwiftSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _SwiftSupport.swift; sourceTree = ""; };
+ 6268E5AF22D7BBD300D4585A /* Loader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Loader.swift; sourceTree = ""; };
+ 6268E5B022D7BBD300D4585A /* Node.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; };
+ 6268E5B122D7BBD300D4585A /* Parser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = ""; };
+ 6268E5B222D7BBD400D4585A /* Filters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Filters.swift; sourceTree = ""; };
+ 6268E5B322D7BBD400D4585A /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; };
+ 6268E5B422D7BBD400D4585A /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; };
+ 6268E5B522D7BBD400D4585A /* Inheritence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Inheritence.swift; sourceTree = ""; };
+ 6268E5B622D7BBD400D4585A /* Tokenizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tokenizer.swift; sourceTree = ""; };
+ 6268E5B722D7BBD400D4585A /* Include.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Include.swift; sourceTree = ""; };
+ 6268E5B822D7BBD400D4585A /* Lexer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Lexer.swift; sourceTree = ""; };
+ 6268E5B922D7BBD400D4585A /* NowTag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NowTag.swift; sourceTree = ""; };
+ 6268E5BA22D7BBD400D4585A /* Variable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Variable.swift; sourceTree = ""; };
+ 6268E5BB22D7BBD400D4585A /* Context.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = ""; };
+ 6268E5BC22D7BBD400D4585A /* FilterTag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FilterTag.swift; sourceTree = ""; };
+ 6268E5D422D7BE6200D4585A /* PathKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PathKit.swift; sourceTree = ""; };
+ 62741CA922DC5DBF00B66956 /* ParametersLocalizationTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParametersLocalizationTemplate.swift; sourceTree = ""; };
+ 62793D3B222D6A140039D3F8 /* LocalizationTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTemplate.swift; sourceTree = ""; };
+ 627F4E9922DF551A0090E24F /* CommandLineParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandLineParameter.swift; sourceTree = ""; };
+ 627F4E9C22DF59EF0090E24F /* LocalizationParametersParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationParametersParser.swift; sourceTree = ""; };
+ 62A625DB22E87AA1007AEA56 /* StringClassNameTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringClassNameTests.swift; sourceTree = ""; };
+ 62A625DD22E87E8B007AEA56 /* ImagesParametersParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagesParametersParser.swift; sourceTree = ""; };
+ 62F5CC1122D8AB41002B28B5 /* ArgumentsParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArgumentsParserTests.swift; sourceTree = ""; };
+ 62F5CC1322D8C1AA002B28B5 /* LocalizationNamespace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationNamespace.swift; sourceTree = ""; };
992D521A1F8418EB00258395 /* ClassName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassName.swift; sourceTree = ""; };
994BF8311F8275B9006A62C3 /* ImageNodeItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageNodeItem.swift; sourceTree = ""; };
994BF8331F8275DB006A62C3 /* Property.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Property.swift; sourceTree = ""; };
994BF8351F8275FF006A62C3 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = ""; };
99667CD01F8C064B00D4A853 /* FileUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUtils.swift; sourceTree = ""; };
- 9977F4671F834C7E00D77D2C /* Tree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tree.swift; sourceTree = ""; };
99ABD9F31EC7185D00032E1B /* OperationTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationTimer.swift; sourceTree = ""; };
99AF6AF11EC5EB9600C91BA8 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; };
99B9E4931D40EBF900738100 /* regen */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = regen; sourceTree = BUILT_PRODUCTS_DIR; };
99B9E49D1D40EC1B00738100 /* ArgumentsParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArgumentsParser.swift; sourceTree = ""; };
- 99B9E49E1D40EC1B00738100 /* AssetsFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetsFinder.swift; sourceTree = ""; };
+ 99B9E49E1D40EC1B00738100 /* ImageesAssetsFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageesAssetsFinder.swift; sourceTree = ""; };
99B9E49F1D40EC1B00738100 /* Colors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = ""; };
99B9E4A01D40EC1B00738100 /* ImagesetParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagesetParser.swift; sourceTree = ""; };
- 99B9E4A11D40EC1B00738100 /* ImageFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageFinder.swift; sourceTree = ""; };
- 99B9E4A21D40EC1B00738100 /* ImageOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageOperation.swift; sourceTree = ""; };
+ 99B9E4A11D40EC1B00738100 /* ImagesFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagesFinder.swift; sourceTree = ""; };
+ 99B9E4A21D40EC1B00738100 /* ImagesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagesOperation.swift; sourceTree = ""; };
99B9E4A31D40EC1B00738100 /* ImagesClassGeneratorObjC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagesClassGeneratorObjC.swift; sourceTree = ""; };
99B9E4A41D40EC1B00738100 /* ImagesValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagesValidator.swift; sourceTree = ""; };
- 99B9E4A51D40EC1B00738100 /* LocalizationClassGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationClassGenerator.swift; sourceTree = ""; };
99B9E4A61D40EC1B00738100 /* LocalizationFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationFinder.swift; sourceTree = ""; };
99B9E4A71D40EC1B00738100 /* LocalizationOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationOperation.swift; sourceTree = ""; };
99B9E4A81D40EC1B00738100 /* LocalizationParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationParser.swift; sourceTree = ""; };
@@ -79,11 +198,16 @@
99B9E4AD1D40EC1B00738100 /* Version.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; };
99D74E1A1EC3919D003BFACE /* ImagesClassGeneratorSwift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagesClassGeneratorSwift.swift; sourceTree = ""; };
99D74E1C1EC391BE003BFACE /* ImagesClassGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImagesClassGenerator.swift; sourceTree = ""; };
- 99D74E331EC39AF8003BFACE /* LocalizationClassGeneratorObjc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationClassGeneratorObjc.swift; sourceTree = ""; };
- 99D74E351EC39B62003BFACE /* LocalizationClassGeneratorSwift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalizationClassGeneratorSwift.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 6252987222D87DF900082EC3 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
99B9E4901D40EBF900738100 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -94,23 +218,108 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
- 994BF8301F827590006A62C3 /* Model */ = {
+ 6252985F22D87AAA00082EC3 /* General Models */ = {
isa = PBXGroup;
children = (
- 994BF8311F8275B9006A62C3 /* ImageNodeItem.swift */,
- 994BF8331F8275DB006A62C3 /* Property.swift */,
- 994BF8351F8275FF006A62C3 /* Image.swift */,
+ 6252986222D87AD200082EC3 /* Tree.swift */,
+ 627F4E9922DF551A0090E24F /* CommandLineParameter.swift */,
);
- path = Model;
+ path = "General Models";
sourceTree = "";
};
- 997179161F7D6F1500FF8AB2 /* Data Structures */ = {
+ 6252986422D87B6500082EC3 /* Colors */ = {
isa = PBXGroup;
children = (
- 9977F4671F834C7E00D77D2C /* Tree.swift */,
+ 99B9E49F1D40EC1B00738100 /* Colors.swift */,
+ );
+ path = Colors;
+ sourceTree = "";
+ };
+ 6252987622D87DF900082EC3 /* regen-tests */ = {
+ isa = PBXGroup;
+ children = (
+ 6252987722D87DF900082EC3 /* regen_tests.swift */,
+ 6252987922D87DF900082EC3 /* Info.plist */,
+ 62F5CC1122D8AB41002B28B5 /* ArgumentsParserTests.swift */,
+ 62A625DB22E87AA1007AEA56 /* StringClassNameTests.swift */,
+ );
+ path = "regen-tests";
+ sourceTree = "";
+ };
+ 6268E5A622D7BB9800D4585A /* Dependencies */ = {
+ isa = PBXGroup;
+ children = (
+ 6252986422D87B6500082EC3 /* Colors */,
+ 6268E5D322D7BE5000D4585A /* PathKit */,
+ 6268E5A722D7BBCB00D4585A /* Stencil */,
+ );
+ path = Dependencies;
+ sourceTree = "";
+ };
+ 6268E5A722D7BBCB00D4585A /* Stencil */ = {
+ isa = PBXGroup;
+ children = (
+ 6268E5AE22D7BBD300D4585A /* _SwiftSupport.swift */,
+ 6268E5BB22D7BBD400D4585A /* Context.swift */,
+ 6268E5AA22D7BBD300D4585A /* Environment.swift */,
+ 6268E5B322D7BBD400D4585A /* Errors.swift */,
+ 6268E5B422D7BBD400D4585A /* Expression.swift */,
+ 6268E5AC22D7BBD300D4585A /* Extension.swift */,
+ 6268E5B222D7BBD400D4585A /* Filters.swift */,
+ 6268E5BC22D7BBD400D4585A /* FilterTag.swift */,
+ 6268E5AB22D7BBD300D4585A /* ForTag.swift */,
+ 6268E5A922D7BBD300D4585A /* IfTag.swift */,
+ 6268E5B722D7BBD400D4585A /* Include.swift */,
+ 6268E5B522D7BBD400D4585A /* Inheritence.swift */,
+ 6268E5AD22D7BBD300D4585A /* KeyPath.swift */,
+ 6268E5B822D7BBD400D4585A /* Lexer.swift */,
+ 6268E5AF22D7BBD300D4585A /* Loader.swift */,
+ 6268E5B022D7BBD300D4585A /* Node.swift */,
+ 6268E5B922D7BBD400D4585A /* NowTag.swift */,
+ 6268E5B122D7BBD300D4585A /* Parser.swift */,
+ 6268E5A822D7BBD300D4585A /* Template.swift */,
+ 6268E5B622D7BBD400D4585A /* Tokenizer.swift */,
+ 6268E5BA22D7BBD400D4585A /* Variable.swift */,
+ );
+ path = Stencil;
+ sourceTree = "";
+ };
+ 6268E5D322D7BE5000D4585A /* PathKit */ = {
+ isa = PBXGroup;
+ children = (
+ 6268E5D422D7BE6200D4585A /* PathKit.swift */,
+ );
+ path = PathKit;
+ sourceTree = "";
+ };
+ 62793D3C222D6A200039D3F8 /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ 62793D3B222D6A140039D3F8 /* LocalizationTemplate.swift */,
+ 62741CA922DC5DBF00B66956 /* ParametersLocalizationTemplate.swift */,
+ 622B8F2F22D9D85B001FC1E4 /* ImagesTemplate.swift */,
+ 62492B5C22DE5D3E00537757 /* whitelist.txt */,
+ );
+ path = Resources;
+ sourceTree = "";
+ };
+ 627F4E9B22DF552F0090E24F /* Models */ = {
+ isa = PBXGroup;
+ children = (
+ 6247EE3922DBB65D0096078D /* LocalizationParameters.swift */,
+ );
+ path = Models;
+ sourceTree = "";
+ };
+ 994BF8301F827590006A62C3 /* Model */ = {
+ isa = PBXGroup;
+ children = (
+ 62492B5E22DE5E2700537757 /* ImagesParameters.swift */,
+ 994BF8311F8275B9006A62C3 /* ImageNodeItem.swift */,
+ 994BF8331F8275DB006A62C3 /* Property.swift */,
+ 994BF8351F8275FF006A62C3 /* Image.swift */,
);
- name = "Data Structures";
- path = "../RegenFramework/Data Structures";
+ path = Model;
sourceTree = "";
};
997179181F7D6FDA00FF8AB2 /* Frameworks */ = {
@@ -124,6 +333,7 @@
isa = PBXGroup;
children = (
99B9E4951D40EBF900738100 /* regen */,
+ 6252987622D87DF900082EC3 /* regen-tests */,
99B9E4941D40EBF900738100 /* Products */,
997179181F7D6FDA00FF8AB2 /* Frameworks */,
);
@@ -133,6 +343,7 @@
isa = PBXGroup;
children = (
99B9E4931D40EBF900738100 /* regen */,
+ 6252987522D87DF900082EC3 /* regen-tests.xctest */,
);
name = Products;
sourceTree = "";
@@ -140,10 +351,12 @@
99B9E4951D40EBF900738100 /* regen */ = {
isa = PBXGroup;
children = (
- 997179161F7D6F1500FF8AB2 /* Data Structures */,
- 99BEB1FC1F78C36F002D9E05 /* Utilities */,
+ 6268E5A622D7BB9800D4585A /* Dependencies */,
+ 62793D3C222D6A200039D3F8 /* Resources */,
+ 6252985F22D87AAA00082EC3 /* General Models */,
99BEB1FA1F78C354002D9E05 /* Localization */,
99BEB1F91F78C342002D9E05 /* Images */,
+ 99BEB1FC1F78C36F002D9E05 /* Utilities */,
99B9E49D1D40EC1B00738100 /* ArgumentsParser.swift */,
99B9E4AA1D40EC1B00738100 /* OperationType.swift */,
99B9E4AC1D40EC1B00738100 /* Usage.swift */,
@@ -157,14 +370,18 @@
isa = PBXGroup;
children = (
994BF8301F827590006A62C3 /* Model */,
+ 622B8F2C22D9D821001FC1E4 /* ImagesNamespace.swift */,
99D74E1C1EC391BE003BFACE /* ImagesClassGenerator.swift */,
99B9E4A31D40EC1B00738100 /* ImagesClassGeneratorObjC.swift */,
99D74E1A1EC3919D003BFACE /* ImagesClassGeneratorSwift.swift */,
- 99B9E49E1D40EC1B00738100 /* AssetsFinder.swift */,
+ 99B9E49E1D40EC1B00738100 /* ImageesAssetsFinder.swift */,
99B9E4A01D40EC1B00738100 /* ImagesetParser.swift */,
- 99B9E4A11D40EC1B00738100 /* ImageFinder.swift */,
+ 99B9E4A11D40EC1B00738100 /* ImagesFinder.swift */,
99B9E4A41D40EC1B00738100 /* ImagesValidator.swift */,
- 99B9E4A21D40EC1B00738100 /* ImageOperation.swift */,
+ 99B9E4AB1D40EC1B00738100 /* PropertyName.swift */,
+ 992D521A1F8418EB00258395 /* ClassName.swift */,
+ 99B9E4A21D40EC1B00738100 /* ImagesOperation.swift */,
+ 62A625DD22E87E8B007AEA56 /* ImagesParametersParser.swift */,
);
path = Images;
sourceTree = "";
@@ -172,12 +389,12 @@
99BEB1FA1F78C354002D9E05 /* Localization */ = {
isa = PBXGroup;
children = (
- 99B9E4A51D40EC1B00738100 /* LocalizationClassGenerator.swift */,
- 99D74E331EC39AF8003BFACE /* LocalizationClassGeneratorObjc.swift */,
- 99D74E351EC39B62003BFACE /* LocalizationClassGeneratorSwift.swift */,
+ 627F4E9B22DF552F0090E24F /* Models */,
+ 62F5CC1322D8C1AA002B28B5 /* LocalizationNamespace.swift */,
99B9E4A61D40EC1B00738100 /* LocalizationFinder.swift */,
99B9E4A81D40EC1B00738100 /* LocalizationParser.swift */,
99B9E4A71D40EC1B00738100 /* LocalizationOperation.swift */,
+ 627F4E9C22DF59EF0090E24F /* LocalizationParametersParser.swift */,
);
path = Localization;
sourceTree = "";
@@ -185,11 +402,8 @@
99BEB1FC1F78C36F002D9E05 /* Utilities */ = {
isa = PBXGroup;
children = (
- 99B9E4AB1D40EC1B00738100 /* PropertyName.swift */,
- 99B9E49F1D40EC1B00738100 /* Colors.swift */,
99AF6AF11EC5EB9600C91BA8 /* Logger.swift */,
99ABD9F31EC7185D00032E1B /* OperationTimer.swift */,
- 992D521A1F8418EB00258395 /* ClassName.swift */,
99667CD01F8C064B00D4A853 /* FileUtils.swift */,
);
path = Utilities;
@@ -198,13 +412,30 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
+ 6252987422D87DF900082EC3 /* regen-tests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 6252987A22D87DF900082EC3 /* Build configuration list for PBXNativeTarget "regen-tests" */;
+ buildPhases = (
+ 6252987122D87DF900082EC3 /* Sources */,
+ 6252987222D87DF900082EC3 /* Frameworks */,
+ 6252987322D87DF900082EC3 /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "regen-tests";
+ productName = "regen-tests";
+ productReference = 6252987522D87DF900082EC3 /* regen-tests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
99B9E4921D40EBF900738100 /* regen */ = {
isa = PBXNativeTarget;
buildConfigurationList = 99B9E49A1D40EBF900738100 /* Build configuration list for PBXNativeTarget "regen" */;
buildPhases = (
99B9E48F1D40EBF900738100 /* Sources */,
99B9E4901D40EBF900738100 /* Frameworks */,
- 99B9E4911D40EBF900738100 /* CopyFiles */,
+ 99B9E4911D40EBF900738100 /* Copy Files */,
);
buildRules = (
);
@@ -221,10 +452,13 @@
99B9E48B1D40EBF900738100 /* Project object */ = {
isa = PBXProject;
attributes = {
- LastSwiftUpdateCheck = 0900;
+ LastSwiftUpdateCheck = 1020;
LastUpgradeCheck = 0900;
ORGANIZATIONNAME = "Ido Mizrachi";
TargetAttributes = {
+ 6252987422D87DF900082EC3 = {
+ CreatedOnToolsVersion = 10.2.1;
+ };
99B9E4921D40EBF900738100 = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 0900;
@@ -236,6 +470,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
+ English,
en,
);
mainGroup = 99B9E48A1D40EBF900738100;
@@ -244,42 +479,133 @@
projectRoot = "";
targets = (
99B9E4921D40EBF900738100 /* regen */,
+ 6252987422D87DF900082EC3 /* regen-tests */,
);
};
/* End PBXProject section */
+/* Begin PBXResourcesBuildPhase section */
+ 6252987322D87DF900082EC3 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
/* Begin PBXSourcesBuildPhase section */
+ 6252987122D87DF900082EC3 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 6236BBA622ECD7AC002615E9 /* _SwiftSupport.swift in Sources */,
+ 6236BBA722ECD7AC002615E9 /* Context.swift in Sources */,
+ 6236BBA822ECD7AC002615E9 /* Environment.swift in Sources */,
+ 6236BBA922ECD7AC002615E9 /* Errors.swift in Sources */,
+ 6236BBAA22ECD7AC002615E9 /* Expression.swift in Sources */,
+ 6236BBAB22ECD7AD002615E9 /* Extension.swift in Sources */,
+ 6236BBAC22ECD7AD002615E9 /* Filters.swift in Sources */,
+ 6236BBAD22ECD7AD002615E9 /* FilterTag.swift in Sources */,
+ 6236BBAE22ECD7AD002615E9 /* ForTag.swift in Sources */,
+ 6236BBAF22ECD7AD002615E9 /* IfTag.swift in Sources */,
+ 6236BBB022ECD7AD002615E9 /* Include.swift in Sources */,
+ 6236BBB122ECD7AD002615E9 /* Inheritence.swift in Sources */,
+ 6236BBB222ECD7AD002615E9 /* KeyPath.swift in Sources */,
+ 6236BBB322ECD7AD002615E9 /* Lexer.swift in Sources */,
+ 6236BBB422ECD7AD002615E9 /* Loader.swift in Sources */,
+ 6236BBB522ECD7AD002615E9 /* Node.swift in Sources */,
+ 6236BBB622ECD7AD002615E9 /* NowTag.swift in Sources */,
+ 6236BBB722ECD7AD002615E9 /* Parser.swift in Sources */,
+ 6236BBB822ECD7AD002615E9 /* Template.swift in Sources */,
+ 6236BBB922ECD7AD002615E9 /* Tokenizer.swift in Sources */,
+ 6236BBBA22ECD7AD002615E9 /* Variable.swift in Sources */,
+ 622B8F2E22D9D821001FC1E4 /* ImagesNamespace.swift in Sources */,
+ 624516F522E6336200E86FB1 /* CommandLineParameter.swift in Sources */,
+ 62F5CC1522D8D607002B28B5 /* LocalizationNamespace.swift in Sources */,
+ 624516F822E633D700E86FB1 /* Property.swift in Sources */,
+ 6252987822D87DF900082EC3 /* regen_tests.swift in Sources */,
+ 62F5CC1022D8AAA7002B28B5 /* OperationType.swift in Sources */,
+ 6236BBBB22ECE207002615E9 /* PathKit.swift in Sources */,
+ 62F5CC0F22D8A99F002B28B5 /* ArgumentsParser.swift in Sources */,
+ 62F5CC1222D8AB41002B28B5 /* ArgumentsParserTests.swift in Sources */,
+ 624516FD22E6353200E86FB1 /* ImagesValidator.swift in Sources */,
+ 624516FE22E6353B00E86FB1 /* ClassName.swift in Sources */,
+ 62A625DF22E87E8B007AEA56 /* ImagesParametersParser.swift in Sources */,
+ 62F5CC0C22D87ECB002B28B5 /* Colors.swift in Sources */,
+ 624516F622E6337A00E86FB1 /* ImageesAssetsFinder.swift in Sources */,
+ 624516FB22E633FF00E86FB1 /* ImageNodeItem.swift in Sources */,
+ 624516FA22E633F200E86FB1 /* Tree.swift in Sources */,
+ 624516F722E6339100E86FB1 /* Image.swift in Sources */,
+ 622B8F3122D9D919001FC1E4 /* ImagesetParser.swift in Sources */,
+ 624516F222E632F500E86FB1 /* LocalizationParameters.swift in Sources */,
+ 62F5CC0B22D87EB2002B28B5 /* Usage.swift in Sources */,
+ 62F5CC1622D8D621002B28B5 /* ImagesOperation.swift in Sources */,
+ 624516F322E632FF00E86FB1 /* ImagesParameters.swift in Sources */,
+ 624516FC22E6340D00E86FB1 /* ImagesFinder.swift in Sources */,
+ 624516F422E6334C00E86FB1 /* LocalizationParametersParser.swift in Sources */,
+ 62A625DC22E87AA1007AEA56 /* StringClassNameTests.swift in Sources */,
+ 624516F922E633E100E86FB1 /* PropertyName.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
99B9E48F1D40EBF900738100 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
99B9E4B41D40EC1B00738100 /* ImagesClassGeneratorObjC.swift in Sources */,
+ 6268E5C722D7BBD400D4585A /* Filters.swift in Sources */,
+ 6268E5CE22D7BBD400D4585A /* NowTag.swift in Sources */,
+ 6268E5BF22D7BBD400D4585A /* Environment.swift in Sources */,
99B9E4BA1D40EC1B00738100 /* main.swift in Sources */,
- 99B9E4AF1D40EC1B00738100 /* AssetsFinder.swift in Sources */,
- 99B9E4B31D40EC1B00738100 /* ImageOperation.swift in Sources */,
+ 99B9E4AF1D40EC1B00738100 /* ImageesAssetsFinder.swift in Sources */,
+ 6268E5C922D7BBD400D4585A /* Expression.swift in Sources */,
+ 99B9E4B31D40EC1B00738100 /* ImagesOperation.swift in Sources */,
99D74E1D1EC391BE003BFACE /* ImagesClassGenerator.swift in Sources */,
994BF8341F8275DB006A62C3 /* Property.swift in Sources */,
+ 6268E5D522D7BE6200D4585A /* PathKit.swift in Sources */,
+ 6268E5C522D7BBD400D4585A /* Node.swift in Sources */,
+ 6268E5C322D7BBD400D4585A /* _SwiftSupport.swift in Sources */,
994BF8361F8275FF006A62C3 /* Image.swift in Sources */,
99B9E4B11D40EC1B00738100 /* ImagesetParser.swift in Sources */,
+ 627F4E9D22DF59EF0090E24F /* LocalizationParametersParser.swift in Sources */,
994BF8321F8275B9006A62C3 /* ImageNodeItem.swift in Sources */,
- 99D74E341EC39AF8003BFACE /* LocalizationClassGeneratorObjc.swift in Sources */,
99D74E1B1EC3919D003BFACE /* ImagesClassGeneratorSwift.swift in Sources */,
- 99B9E4B61D40EC1B00738100 /* LocalizationClassGenerator.swift in Sources */,
+ 6268E5D022D7BBD400D4585A /* Context.swift in Sources */,
99AF6AF21EC5EB9600C91BA8 /* Logger.swift in Sources */,
99B9E4B91D40EC1B00738100 /* LocalizationParser.swift in Sources */,
+ 6268E5C622D7BBD400D4585A /* Parser.swift in Sources */,
+ 6268E5CC22D7BBD400D4585A /* Include.swift in Sources */,
99B9E4B51D40EC1B00738100 /* ImagesValidator.swift in Sources */,
+ 6268E5C222D7BBD400D4585A /* KeyPath.swift in Sources */,
+ 6268E5BD22D7BBD400D4585A /* Template.swift in Sources */,
99B9E4BE1D40EC1B00738100 /* Version.swift in Sources */,
+ 6268E5CA22D7BBD400D4585A /* Inheritence.swift in Sources */,
99B9E4B01D40EC1B00738100 /* Colors.swift in Sources */,
99B9E4B81D40EC1B00738100 /* LocalizationOperation.swift in Sources */,
- 99B9E4B21D40EC1B00738100 /* ImageFinder.swift in Sources */,
+ 6268E5C822D7BBD400D4585A /* Errors.swift in Sources */,
+ 99B9E4B21D40EC1B00738100 /* ImagesFinder.swift in Sources */,
+ 6268E5CF22D7BBD400D4585A /* Variable.swift in Sources */,
+ 6247EE3A22DBB65D0096078D /* LocalizationParameters.swift in Sources */,
+ 6268E5CB22D7BBD400D4585A /* Tokenizer.swift in Sources */,
99ABD9F41EC7185D00032E1B /* OperationTimer.swift in Sources */,
- 9977F4681F834C7E00D77D2C /* Tree.swift in Sources */,
+ 6252986322D87AD200082EC3 /* Tree.swift in Sources */,
+ 627F4E9A22DF551A0090E24F /* CommandLineParameter.swift in Sources */,
+ 6268E5C022D7BBD400D4585A /* ForTag.swift in Sources */,
99B9E4BD1D40EC1B00738100 /* Usage.swift in Sources */,
+ 622B8F2D22D9D821001FC1E4 /* ImagesNamespace.swift in Sources */,
99B9E4BC1D40EC1B00738100 /* PropertyName.swift in Sources */,
- 99D74E361EC39B62003BFACE /* LocalizationClassGeneratorSwift.swift in Sources */,
+ 6268E5D122D7BBD400D4585A /* FilterTag.swift in Sources */,
+ 6268E5C122D7BBD400D4585A /* Extension.swift in Sources */,
99667CD11F8C064B00D4A853 /* FileUtils.swift in Sources */,
+ 62A625DE22E87E8B007AEA56 /* ImagesParametersParser.swift in Sources */,
99B9E4BB1D40EC1B00738100 /* OperationType.swift in Sources */,
+ 6268E5CD22D7BBD400D4585A /* Lexer.swift in Sources */,
99B9E4B71D40EC1B00738100 /* LocalizationFinder.swift in Sources */,
+ 6268E5BE22D7BBD400D4585A /* IfTag.swift in Sources */,
+ 62492B5F22DE5E2700537757 /* ImagesParameters.swift in Sources */,
+ 62F5CC1422D8C1AA002B28B5 /* LocalizationNamespace.swift in Sources */,
+ 6268E5C422D7BBD400D4585A /* Loader.swift in Sources */,
99B9E4AE1D40EC1B00738100 /* ArgumentsParser.swift in Sources */,
992D521B1F8418EB00258395 /* ClassName.swift in Sources */,
);
@@ -288,6 +614,54 @@
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
+ 6252987B22D87DF900082EC3 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ COMBINE_HIDPI_IMAGES = YES;
+ DEVELOPMENT_TEAM = "";
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = "regen-tests/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.idomizrachi.regen-tests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ };
+ name = Debug;
+ };
+ 6252987C22D87DF900082EC3 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ COMBINE_HIDPI_IMAGES = YES;
+ DEVELOPMENT_TEAM = "";
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ INFOPLIST_FILE = "regen-tests/Info.plist";
+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
+ MACOSX_DEPLOYMENT_TARGET = 10.14;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.idomizrachi.regen-tests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ };
+ name = Release;
+ };
99B9E4981D40EBF900738100 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -314,7 +688,6 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -339,7 +712,7 @@
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
- SWIFT_VERSION = 4.0;
+ SWIFT_VERSION = 5.0;
};
name = Debug;
};
@@ -369,7 +742,6 @@
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
- CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
@@ -387,7 +759,7 @@
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
- SWIFT_VERSION = 4.0;
+ SWIFT_VERSION = 5.0;
};
name = Release;
};
@@ -395,10 +767,11 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
+ DEVELOPMENT_TEAM = "";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_VERSION = 4.0;
};
name = Debug;
};
@@ -406,15 +779,25 @@
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
+ DEVELOPMENT_TEAM = "";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_VERSION = 4.0;
+ PROVISIONING_PROFILE_SPECIFIER = "";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 6252987A22D87DF900082EC3 /* Build configuration list for PBXNativeTarget "regen-tests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 6252987B22D87DF900082EC3 /* Debug */,
+ 6252987C22D87DF900082EC3 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
99B9E48E1D40EBF900738100 /* Build configuration list for PBXProject "regen" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/regen.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/regen.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/regen.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/regen.xcodeproj/xcshareddata/xcschemes/regen.xcscheme b/regen.xcodeproj/xcshareddata/xcschemes/regen.xcscheme
new file mode 100644
index 0000000..78219d9
--- /dev/null
+++ b/regen.xcodeproj/xcshareddata/xcschemes/regen.xcscheme
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/regen.xcodeproj/xcuserdata/idomizrachi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/regen.xcodeproj/xcuserdata/idomizrachi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
new file mode 100644
index 0000000..fe2b454
--- /dev/null
+++ b/regen.xcodeproj/xcuserdata/idomizrachi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -0,0 +1,5 @@
+
+
+
diff --git a/regen.xcodeproj/xcuserdata/idomizrachi.xcuserdatad/xcschemes/xcschememanagement.plist b/regen.xcodeproj/xcuserdata/idomizrachi.xcuserdatad/xcschemes/xcschememanagement.plist
new file mode 100644
index 0000000..dd6e997
--- /dev/null
+++ b/regen.xcodeproj/xcuserdata/idomizrachi.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ SchemeUserState
+
+ regen.xcscheme
+
+ orderHint
+ 0
+
+ regen.xcscheme_^#shared#^_
+
+ orderHint
+ 0
+
+
+ SuppressBuildableAutocreation
+
+ 6252986822D87DA800082EC3
+
+ primary
+
+
+ 99B9E4921D40EBF900738100
+
+ primary
+
+
+
+
+
diff --git a/regen/ArgumentsParser.swift b/regen/ArgumentsParser.swift
index 1ec9cf5..59ab079 100644
--- a/regen/ArgumentsParser.swift
+++ b/regen/ArgumentsParser.swift
@@ -7,123 +7,70 @@
import Foundation
-enum Language {
- case ObjC
- case Swift
+protocol CanBeInitializedWithString {
+ init?(_ description: String)
}
+extension Int: CanBeInitializedWithString {}
+extension String: CanBeInitializedWithString {}
+
class ArgumentsParser {
-
- static let imagesType = "images"
- static let localizationType = "localization"
-
let arguments : [String]
-
- var output : String?
- var language: Language = .ObjC
- var verbose:Bool = false
- var color:Bool = true
-
+ let operationType: OperationType
+
init(arguments : [String]) {
self.arguments = arguments
- parseOutput()
- parseLanguage()
- parseVerbose()
- parseColor()
- }
-
- func operationType() -> OperationType {
- if isVersionOperationType() {
- return .version
- }
- if isImagesOperationType() {
- return .images
- }
- if isLocalizationOperationType() {
- return .localization
- }
- return .usage
+ self.operationType = ArgumentsParser.parseOperationType(arguments: arguments)
}
-
- func isVersionOperationType() -> Bool {
- //The first argument contains the executable file path
- if arguments.contains("--version") && arguments.count == 2 {
- return true
- }
- return false
- }
-
- func scanType() -> String? {
- guard let indexOfScanType = arguments.index(of: "--scanType") else {
- return nil
- }
- if indexOfScanType+1 < arguments.count {
- let scanType = arguments[indexOfScanType+1]
- return scanType.lowercased()
- }
- return nil
- }
-
-
- func isImagesOperationType() -> Bool {
- if let scanType = scanType() {
- if scanType == "images" {
- return true
+
+ private static func parseOperationType(arguments: [String]) -> OperationType {
+ let operationTypeKey = parseOperationTypeKey(arguments)
+ switch operationTypeKey {
+ case .localization:
+ guard let parameters = parseLocalizationParameters(arguments) else {
+ return .usage
}
- }
- return false
- }
-
- func isLocalizationOperationType() -> Bool {
- if let scanType = scanType() {
- if scanType == "localization" {
- return true
+ return .localization(parameters: parameters)
+ case .images:
+ guard let parameters = parseImagesParameters(arguments) else {
+ return .usage
}
+ return .images(parameters: parameters)
+ case .version:
+ return .version
+ case .usage:
+ return .usage
}
- return false
}
-
- func parseOutput() {
- guard let indexOfOutput = arguments.index(of: "--output") else {
- return
- }
- if indexOfOutput+1 < arguments.count {
- self.output = arguments[indexOfOutput+1]
+
+ private static func parseOperationTypeKey(_ arguments: [String]) -> OperationType.Keys {
+ guard let firstArgument = arguments.first else {
+ return .usage
}
+ return OperationType.Keys(rawValue: firstArgument) ?? .usage
}
-
- func parseLanguage() {
- guard let indexOfLanguage = arguments.index(of: "--language") else {
- return
- }
- if indexOfLanguage+1 < arguments.count {
- let language = arguments[indexOfLanguage+1].lowercased()
- if (language == "swift") {
- self.language = .Swift
- } else {
- self.language = .ObjC
- }
- }
+
+ private static func parseLocalizationParameters(_ arguments: [String]) -> Localization.Parameters? {
+ let parser = LocalizationParametersParser(arguments: arguments)
+ return parser.parse()
}
-
- func parseVerbose() {
- if arguments.index(of: "--verbose") != nil {
- self.verbose = true
- } else if arguments.index(of: "-v") != nil {
- self.verbose = true
- } else {
- self.verbose = false
- }
+
+ private static func parseImagesParameters(_ arguments: [String]) -> Images.Parameters? {
+ let parser = ImagesParametersParser(arguments: arguments)
+ return parser.parse()
}
-
- func parseColor() {
- if arguments.index(of: "--nocolor") != nil {
- self.color = false
- } else {
- self.color = true
+
+// private static func parseAssetsFile(arguments: [String]) -> String? {
+// let assetsFile: String? = tryParse("--assets-file", from: arguments)
+// return assetsFile
+// }
+
+ private static func isVersionOperation(_ arguments: [String]) -> Bool {
+ guard let firstArgument = arguments.first else {
+ return false
}
+ return firstArgument.lowercased() == OperationType.Keys.version.rawValue
}
-
}
diff --git a/regen/Utilities/Colors.swift b/regen/Dependencies/Colors/Colors.swift
similarity index 98%
rename from regen/Utilities/Colors.swift
rename to regen/Dependencies/Colors/Colors.swift
index 07827c8..ed20aac 100644
--- a/regen/Utilities/Colors.swift
+++ b/regen/Dependencies/Colors/Colors.swift
@@ -4,6 +4,7 @@
//
// Created by Chad Scira on 3/3/15.
//
+// https://github.com/icodeforlove/Colors
import Foundation
@@ -114,7 +115,7 @@ func parseExistingANSI(_ string: String) -> [ANSIGroup] {
for match in matches {
var parts = matchesForRegexInText("\\u001B\\[([^m]*)m(.+?)\\u001B\\[0m", text: match),
- codes = parts[1].characters.split {$0 == ";"}.map { String($0) },
+ codes = parts[1].split {$0 == ";"}.map { String($0) },
string = parts[2]
results.append(ANSIGroup(codes: codes.filter { Int($0) != nil }.map { Int($0)! }, string: string))
diff --git a/regen/Dependencies/PathKit/PathKit.swift b/regen/Dependencies/PathKit/PathKit.swift
new file mode 100755
index 0000000..7fe7612
--- /dev/null
+++ b/regen/Dependencies/PathKit/PathKit.swift
@@ -0,0 +1,795 @@
+// PathKit - Effortless path operations
+
+#if os(Linux)
+import Glibc
+
+let system_glob = Glibc.glob
+#else
+import Darwin
+
+let system_glob = Darwin.glob
+#endif
+
+import Foundation
+
+
+/// Represents a filesystem path.
+public struct Path {
+ /// The character used by the OS to separate two path elements
+ public static let separator = "/"
+
+ /// The underlying string representation
+ internal var path: String
+
+ internal static var fileManager = FileManager.default
+
+ internal var fileSystemInfo: FileSystemInfo = DefaultFileSystemInfo()
+
+ // MARK: Init
+
+ public init() {
+ self.path = ""
+ }
+
+ /// Create a Path from a given String
+ public init(_ path: String) {
+ self.path = path
+ }
+
+ /// Create a Path by joining multiple path components together
+ public init(components: S) where S.Iterator.Element == String {
+ if components.isEmpty {
+ path = "."
+ } else if components.first == Path.separator && components.count > 1 {
+ let p = components.joined(separator: Path.separator)
+ path = p.substring(from: p.index(after: p.startIndex))
+ } else {
+ path = components.joined(separator: Path.separator)
+ }
+ }
+}
+
+
+// MARK: StringLiteralConvertible
+
+extension Path : ExpressibleByStringLiteral {
+ public typealias ExtendedGraphemeClusterLiteralType = StringLiteralType
+ public typealias UnicodeScalarLiteralType = StringLiteralType
+
+ public init(extendedGraphemeClusterLiteral path: StringLiteralType) {
+ self.init(stringLiteral: path)
+ }
+
+ public init(unicodeScalarLiteral path: StringLiteralType) {
+ self.init(stringLiteral: path)
+ }
+
+ public init(stringLiteral value: StringLiteralType) {
+ self.path = value
+ }
+}
+
+
+// MARK: CustomStringConvertible
+
+extension Path : CustomStringConvertible {
+ public var description: String {
+ return self.path
+ }
+}
+
+
+// MARK: Conversion
+
+extension Path {
+ public var string: String {
+ return self.path
+ }
+
+ public var url: URL {
+ return URL(fileURLWithPath: path)
+ }
+}
+
+
+// MARK: Hashable
+
+extension Path : Hashable {
+ public var hashValue: Int {
+ return path.hashValue
+ }
+}
+
+
+// MARK: Path Info
+
+extension Path {
+ /// Test whether a path is absolute.
+ ///
+ /// - Returns: `true` iff the path begings with a slash
+ ///
+ public var isAbsolute: Bool {
+ return path.hasPrefix(Path.separator)
+ }
+
+ /// Test whether a path is relative.
+ ///
+ /// - Returns: `true` iff a path is relative (not absolute)
+ ///
+ public var isRelative: Bool {
+ return !isAbsolute
+ }
+
+ /// Concatenates relative paths to the current directory and derives the normalized path
+ ///
+ /// - Returns: the absolute path in the actual filesystem
+ ///
+ public func absolute() -> Path {
+ if isAbsolute {
+ return normalize()
+ }
+
+ let expandedPath = Path(NSString(string: self.path).expandingTildeInPath)
+ if expandedPath.isAbsolute {
+ return expandedPath.normalize()
+ }
+
+ return (Path.current + self).normalize()
+ }
+
+ /// Normalizes the path, this cleans up redundant ".." and ".", double slashes
+ /// and resolves "~".
+ ///
+ /// - Returns: a new path made by removing extraneous path components from the underlying String
+ /// representation.
+ ///
+ public func normalize() -> Path {
+ return Path(NSString(string: self.path).standardizingPath)
+ }
+
+ /// De-normalizes the path, by replacing the current user home directory with "~".
+ ///
+ /// - Returns: a new path made by removing extraneous path components from the underlying String
+ /// representation.
+ ///
+ public func abbreviate() -> Path {
+ let rangeOptions: String.CompareOptions = fileSystemInfo.isFSCaseSensitiveAt(path: self) ?
+ [.anchored] : [.anchored, .caseInsensitive]
+ let home = Path.home.string
+ guard let homeRange = self.path.range(of: home, options: rangeOptions) else { return self }
+ let withoutHome = Path(self.path.replacingCharacters(in: homeRange, with: ""))
+
+ if withoutHome.path.isEmpty || withoutHome.path == Path.separator {
+ return Path("~")
+ } else if withoutHome.isAbsolute {
+ return Path("~" + withoutHome.path)
+ } else {
+ return Path("~") + withoutHome.path
+ }
+ }
+
+ /// Returns the path of the item pointed to by a symbolic link.
+ ///
+ /// - Returns: the path of directory or file to which the symbolic link refers
+ ///
+ public func symlinkDestination() throws -> Path {
+ let symlinkDestination = try Path.fileManager.destinationOfSymbolicLink(atPath: path)
+ let symlinkPath = Path(symlinkDestination)
+ if symlinkPath.isRelative {
+ return self + ".." + symlinkPath
+ } else {
+ return symlinkPath
+ }
+ }
+}
+
+internal protocol FileSystemInfo {
+ func isFSCaseSensitiveAt(path: Path) -> Bool
+}
+
+internal struct DefaultFileSystemInfo: FileSystemInfo {
+ func isFSCaseSensitiveAt(path: Path) -> Bool {
+ #if os(Linux)
+ // URL resourceValues(forKeys:) is not supported on non-darwin platforms...
+ // But we can (fairly?) safely assume for now that the Linux FS is case sensitive.
+ // TODO: refactor when/if resourceValues is available, or look into using something
+ // like stat or pathconf to determine if the mountpoint is case sensitive.
+ return true
+ #else
+ var isCaseSensitive = false
+ // Calling resourceValues will fail if the path does not exist on the filesystem, which
+ // makes sense, but means we can only guarantee the return value is correct if the
+ // path actually exists.
+ if let resourceValues = try? path.url.resourceValues(forKeys: [.volumeSupportsCaseSensitiveNamesKey]) {
+ isCaseSensitive = resourceValues.volumeSupportsCaseSensitiveNames ?? isCaseSensitive
+ }
+ return isCaseSensitive
+ #endif
+ }
+}
+
+// MARK: Path Components
+
+extension Path {
+ /// The last path component
+ ///
+ /// - Returns: the last path component
+ ///
+ public var lastComponent: String {
+ return NSString(string: path).lastPathComponent
+ }
+
+ /// The last path component without file extension
+ ///
+ /// - Note: This returns "." for ".." on Linux, and ".." on Apple platforms.
+ ///
+ /// - Returns: the last path component without file extension
+ ///
+ public var lastComponentWithoutExtension: String {
+ return NSString(string: lastComponent).deletingPathExtension
+ }
+
+ /// Splits the string representation on the directory separator.
+ /// Absolute paths remain the leading slash as first component.
+ ///
+ /// - Returns: all path components
+ ///
+ public var components: [String] {
+ return NSString(string: path).pathComponents
+ }
+
+ /// The file extension behind the last dot of the last component.
+ ///
+ /// - Returns: the file extension
+ ///
+ public var `extension`: String? {
+ let pathExtension = NSString(string: path).pathExtension
+ if pathExtension.isEmpty {
+ return nil
+ }
+
+ return pathExtension
+ }
+}
+
+
+// MARK: File Info
+
+extension Path {
+ /// Test whether a file or directory exists at a specified path
+ ///
+ /// - Returns: `false` iff the path doesn't exist on disk or its existence could not be
+ /// determined
+ ///
+ public var exists: Bool {
+ return Path.fileManager.fileExists(atPath: self.path)
+ }
+
+ /// Test whether a path is a directory.
+ ///
+ /// - Returns: `true` if the path is a directory or a symbolic link that points to a directory;
+ /// `false` if the path is not a directory or the path doesn't exist on disk or its existence
+ /// could not be determined
+ ///
+ public var isDirectory: Bool {
+ var directory = ObjCBool(false)
+ guard Path.fileManager.fileExists(atPath: normalize().path, isDirectory: &directory) else {
+ return false
+ }
+#if os(Linux)
+ return directory
+#else
+ return directory.boolValue
+#endif
+ }
+
+ /// Test whether a path is a regular file.
+ ///
+ /// - Returns: `true` if the path is neither a directory nor a symbolic link that points to a
+ /// directory; `false` if the path is a directory or a symbolic link that points to a
+ /// directory or the path doesn't exist on disk or its existence
+ /// could not be determined
+ ///
+ public var isFile: Bool {
+ var directory = ObjCBool(false)
+ guard Path.fileManager.fileExists(atPath: normalize().path, isDirectory: &directory) else {
+ return false
+ }
+#if os(Linux)
+ return !directory
+#else
+ return !directory.boolValue
+#endif
+ }
+
+ /// Test whether a path is a symbolic link.
+ ///
+ /// - Returns: `true` if the path is a symbolic link; `false` if the path doesn't exist on disk
+ /// or its existence could not be determined
+ ///
+ public var isSymlink: Bool {
+ do {
+ let _ = try Path.fileManager.destinationOfSymbolicLink(atPath: path)
+ return true
+ } catch {
+ return false
+ }
+ }
+
+ /// Test whether a path is readable
+ ///
+ /// - Returns: `true` if the current process has read privileges for the file at path;
+ /// otherwise `false` if the process does not have read privileges or the existence of the
+ /// file could not be determined.
+ ///
+ public var isReadable: Bool {
+ return Path.fileManager.isReadableFile(atPath: self.path)
+ }
+
+ /// Test whether a path is writeable
+ ///
+ /// - Returns: `true` if the current process has write privileges for the file at path;
+ /// otherwise `false` if the process does not have write privileges or the existence of the
+ /// file could not be determined.
+ ///
+ public var isWritable: Bool {
+ return Path.fileManager.isWritableFile(atPath: self.path)
+ }
+
+ /// Test whether a path is executable
+ ///
+ /// - Returns: `true` if the current process has execute privileges for the file at path;
+ /// otherwise `false` if the process does not have execute privileges or the existence of the
+ /// file could not be determined.
+ ///
+ public var isExecutable: Bool {
+ return Path.fileManager.isExecutableFile(atPath: self.path)
+ }
+
+ /// Test whether a path is deletable
+ ///
+ /// - Returns: `true` if the current process has delete privileges for the file at path;
+ /// otherwise `false` if the process does not have delete privileges or the existence of the
+ /// file could not be determined.
+ ///
+ public var isDeletable: Bool {
+ return Path.fileManager.isDeletableFile(atPath: self.path)
+ }
+}
+
+
+// MARK: File Manipulation
+
+extension Path {
+ /// Create the directory.
+ ///
+ /// - Note: This method fails if any of the intermediate parent directories does not exist.
+ /// This method also fails if any of the intermediate path elements corresponds to a file and
+ /// not a directory.
+ ///
+ public func mkdir() throws -> () {
+ try Path.fileManager.createDirectory(atPath: self.path, withIntermediateDirectories: false, attributes: nil)
+ }
+
+ /// Create the directory and any intermediate parent directories that do not exist.
+ ///
+ /// - Note: This method fails if any of the intermediate path elements corresponds to a file and
+ /// not a directory.
+ ///
+ public func mkpath() throws -> () {
+ try Path.fileManager.createDirectory(atPath: self.path, withIntermediateDirectories: true, attributes: nil)
+ }
+
+ /// Delete the file or directory.
+ ///
+ /// - Note: If the path specifies a directory, the contents of that directory are recursively
+ /// removed.
+ ///
+ public func delete() throws -> () {
+ try Path.fileManager.removeItem(atPath: self.path)
+ }
+
+ /// Move the file or directory to a new location synchronously.
+ ///
+ /// - Parameter destination: The new path. This path must include the name of the file or
+ /// directory in its new location.
+ ///
+ public func move(_ destination: Path) throws -> () {
+ try Path.fileManager.moveItem(atPath: self.path, toPath: destination.path)
+ }
+
+ /// Copy the file or directory to a new location synchronously.
+ ///
+ /// - Parameter destination: The new path. This path must include the name of the file or
+ /// directory in its new location.
+ ///
+ public func copy(_ destination: Path) throws -> () {
+ try Path.fileManager.copyItem(atPath: self.path, toPath: destination.path)
+ }
+
+ /// Creates a hard link at a new destination.
+ ///
+ /// - Parameter destination: The location where the link will be created.
+ ///
+ public func link(_ destination: Path) throws -> () {
+ try Path.fileManager.linkItem(atPath: self.path, toPath: destination.path)
+ }
+
+ /// Creates a symbolic link at a new destination.
+ ///
+ /// - Parameter destintation: The location where the link will be created.
+ ///
+ public func symlink(_ destination: Path) throws -> () {
+ try Path.fileManager.createSymbolicLink(atPath: self.path, withDestinationPath: destination.path)
+ }
+}
+
+
+// MARK: Current Directory
+
+extension Path {
+ /// The current working directory of the process
+ ///
+ /// - Returns: the current working directory of the process
+ ///
+ public static var current: Path {
+ get {
+ return self.init(Path.fileManager.currentDirectoryPath)
+ }
+ set {
+ _ = Path.fileManager.changeCurrentDirectoryPath(newValue.description)
+ }
+ }
+
+ /// Changes the current working directory of the process to the path during the execution of the
+ /// given block.
+ ///
+ /// - Note: The original working directory is restored when the block returns or throws.
+ /// - Parameter closure: A closure to be executed while the current directory is configured to
+ /// the path.
+ ///
+ public func chdir(closure: () throws -> ()) rethrows {
+ let previous = Path.current
+ Path.current = self
+ defer { Path.current = previous }
+ try closure()
+ }
+}
+
+
+// MARK: Temporary
+
+extension Path {
+ /// - Returns: the path to either the user’s or application’s home directory,
+ /// depending on the platform.
+ ///
+ public static var home: Path {
+ return Path(NSHomeDirectory())
+ }
+
+ /// - Returns: the path of the temporary directory for the current user.
+ ///
+ public static var temporary: Path {
+ return Path(NSTemporaryDirectory())
+ }
+
+ /// - Returns: the path of a temporary directory unique for the process.
+ /// - Note: Based on `NSProcessInfo.globallyUniqueString`.
+ ///
+ public static func processUniqueTemporary() throws -> Path {
+ let path = temporary + ProcessInfo.processInfo.globallyUniqueString
+ if !path.exists {
+ try path.mkdir()
+ }
+ return path
+ }
+
+ /// - Returns: the path of a temporary directory unique for each call.
+ /// - Note: Based on `NSUUID`.
+ ///
+ public static func uniqueTemporary() throws -> Path {
+ let path = try processUniqueTemporary() + UUID().uuidString
+ try path.mkdir()
+ return path
+ }
+}
+
+
+// MARK: Contents
+
+extension Path {
+ /// Reads the file.
+ ///
+ /// - Returns: the contents of the file at the specified path.
+ ///
+ public func read() throws -> Data {
+ return try Data(contentsOf: self.url, options: NSData.ReadingOptions(rawValue: 0))
+ }
+
+ /// Reads the file contents and encoded its bytes to string applying the given encoding.
+ ///
+ /// - Parameter encoding: the encoding which should be used to decode the data.
+ /// (by default: `NSUTF8StringEncoding`)
+ ///
+ /// - Returns: the contents of the file at the specified path as string.
+ ///
+ public func read(_ encoding: String.Encoding = String.Encoding.utf8) throws -> String {
+ return try NSString(contentsOfFile: path, encoding: encoding.rawValue).substring(from: 0) as String
+ }
+
+ /// Write a file.
+ ///
+ /// - Note: Works atomically: the data is written to a backup file, and then — assuming no
+ /// errors occur — the backup file is renamed to the name specified by path.
+ ///
+ /// - Parameter data: the contents to write to file.
+ ///
+ public func write(_ data: Data) throws {
+ try data.write(to: normalize().url, options: .atomic)
+ }
+
+ /// Reads the file.
+ ///
+ /// - Note: Works atomically: the data is written to a backup file, and then — assuming no
+ /// errors occur — the backup file is renamed to the name specified by path.
+ ///
+ /// - Parameter string: the string to write to file.
+ ///
+ /// - Parameter encoding: the encoding which should be used to represent the string as bytes.
+ /// (by default: `NSUTF8StringEncoding`)
+ ///
+ /// - Returns: the contents of the file at the specified path as string.
+ ///
+ public func write(_ string: String, encoding: String.Encoding = String.Encoding.utf8) throws {
+ try string.write(toFile: normalize().path, atomically: true, encoding: encoding)
+ }
+}
+
+
+// MARK: Traversing
+
+extension Path {
+ /// Get the parent directory
+ ///
+ /// - Returns: the normalized path of the parent directory
+ ///
+ public func parent() -> Path {
+ return self + ".."
+ }
+
+ /// Performs a shallow enumeration in a directory
+ ///
+ /// - Returns: paths to all files, directories and symbolic links contained in the directory
+ ///
+ public func children() throws -> [Path] {
+ return try Path.fileManager.contentsOfDirectory(atPath: path).map {
+ self + Path($0)
+ }
+ }
+
+ /// Performs a deep enumeration in a directory
+ ///
+ /// - Returns: paths to all files, directories and symbolic links contained in the directory or
+ /// any subdirectory.
+ ///
+ public func recursiveChildren() throws -> [Path] {
+ return try Path.fileManager.subpathsOfDirectory(atPath: path).map {
+ self + Path($0)
+ }
+ }
+}
+
+
+// MARK: Globbing
+
+extension Path {
+ public static func glob(_ pattern: String) -> [Path] {
+ var gt = glob_t()
+ let cPattern = strdup(pattern)
+ defer {
+ globfree(>)
+ free(cPattern)
+ }
+
+ let flags = GLOB_TILDE | GLOB_BRACE | GLOB_MARK
+ if system_glob(cPattern, flags, nil, >) == 0 {
+#if os(Linux)
+ let matchc = gt.gl_pathc
+#else
+ let matchc = gt.gl_matchc
+#endif
+ return (0.. [Path] {
+ return Path.glob((self + pattern).description)
+ }
+}
+
+
+// MARK: SequenceType
+
+extension Path : Sequence {
+ public struct DirectoryEnumerationOptions : OptionSet {
+ public let rawValue: UInt
+ public init(rawValue: UInt) {
+ self.rawValue = rawValue
+ }
+
+ public static var skipsSubdirectoryDescendants = DirectoryEnumerationOptions(rawValue: FileManager.DirectoryEnumerationOptions.skipsSubdirectoryDescendants.rawValue)
+ public static var skipsPackageDescendants = DirectoryEnumerationOptions(rawValue: FileManager.DirectoryEnumerationOptions.skipsPackageDescendants.rawValue)
+ public static var skipsHiddenFiles = DirectoryEnumerationOptions(rawValue: FileManager.DirectoryEnumerationOptions.skipsHiddenFiles.rawValue)
+ }
+
+ /// Represents a path sequence with specific enumeration options
+ public struct PathSequence : Sequence {
+ private var path: Path
+ private var options: DirectoryEnumerationOptions
+ init(path: Path, options: DirectoryEnumerationOptions) {
+ self.path = path
+ self.options = options
+ }
+
+ public func makeIterator() -> DirectoryEnumerator {
+ return DirectoryEnumerator(path: path, options: options)
+ }
+ }
+
+ /// Enumerates the contents of a directory, returning the paths of all files and directories
+ /// contained within that directory. These paths are relative to the directory.
+ public struct DirectoryEnumerator : IteratorProtocol {
+ public typealias Element = Path
+
+ let path: Path
+ let directoryEnumerator: FileManager.DirectoryEnumerator?
+
+ init(path: Path, options mask: DirectoryEnumerationOptions = []) {
+ let options = FileManager.DirectoryEnumerationOptions(rawValue: mask.rawValue)
+ self.path = path
+ self.directoryEnumerator = Path.fileManager.enumerator(at: path.url, includingPropertiesForKeys: nil, options: options)
+ }
+
+ public func next() -> Path? {
+ let next = directoryEnumerator?.nextObject()
+
+ if let next = next as? URL {
+ return Path(next.path)
+ }
+ return nil
+ }
+
+ /// Skip recursion into the most recently obtained subdirectory.
+ public func skipDescendants() {
+ directoryEnumerator?.skipDescendants()
+ }
+ }
+
+ /// Perform a deep enumeration of a directory.
+ ///
+ /// - Returns: a directory enumerator that can be used to perform a deep enumeration of the
+ /// directory.
+ ///
+ public func makeIterator() -> DirectoryEnumerator {
+ return DirectoryEnumerator(path: self)
+ }
+
+ /// Perform a deep enumeration of a directory.
+ ///
+ /// - Parameter options: FileManager directory enumerator options.
+ ///
+ /// - Returns: a path sequence that can be used to perform a deep enumeration of the
+ /// directory.
+ ///
+ public func iterateChildren(options: DirectoryEnumerationOptions = []) -> PathSequence {
+ return PathSequence(path: self, options: options)
+ }
+}
+
+
+// MARK: Equatable
+
+extension Path : Equatable {}
+
+/// Determines if two paths are identical
+///
+/// - Note: The comparison is string-based. Be aware that two different paths (foo.txt and
+/// ./foo.txt) can refer to the same file.
+///
+public func ==(lhs: Path, rhs: Path) -> Bool {
+ return lhs.path == rhs.path
+}
+
+
+// MARK: Pattern Matching
+
+/// Implements pattern-matching for paths.
+///
+/// - Returns: `true` iff one of the following conditions is true:
+/// - the paths are equal (based on `Path`'s `Equatable` implementation)
+/// - the paths can be normalized to equal Paths.
+///
+public func ~=(lhs: Path, rhs: Path) -> Bool {
+ return lhs == rhs
+ || lhs.normalize() == rhs.normalize()
+}
+
+
+// MARK: Comparable
+
+extension Path : Comparable {}
+
+/// Defines a strict total order over Paths based on their underlying string representation.
+public func <(lhs: Path, rhs: Path) -> Bool {
+ return lhs.path < rhs.path
+}
+
+
+// MARK: Operators
+
+/// Appends a Path fragment to another Path to produce a new Path
+public func +(lhs: Path, rhs: Path) -> Path {
+ return lhs.path + rhs.path
+}
+
+/// Appends a String fragment to another Path to produce a new Path
+public func +(lhs: Path, rhs: String) -> Path {
+ return lhs.path + rhs
+}
+
+/// Appends a String fragment to another String to produce a new Path
+private func +(lhs: String, rhs: String) -> Path {
+ if rhs.hasPrefix(Path.separator) {
+ // Absolute paths replace relative paths
+ return Path(rhs)
+ } else {
+ var lSlice = NSString(string: lhs).pathComponents.fullSlice
+ var rSlice = NSString(string: rhs).pathComponents.fullSlice
+
+ // Get rid of trailing "/" at the left side
+ if lSlice.count > 1 && lSlice.last == Path.separator {
+ lSlice.removeLast()
+ }
+
+ // Advance after the first relevant "."
+ lSlice = lSlice.filter { $0 != "." }.fullSlice
+ rSlice = rSlice.filter { $0 != "." }.fullSlice
+
+ // Eats up trailing components of the left and leading ".." of the right side
+ while lSlice.last != ".." && !lSlice.isEmpty && rSlice.first == ".." {
+ if lSlice.count > 1 || lSlice.first != Path.separator {
+ // A leading "/" is never popped
+ lSlice.removeLast()
+ }
+ if !rSlice.isEmpty {
+ rSlice.removeFirst()
+ }
+
+ switch (lSlice.isEmpty, rSlice.isEmpty) {
+ case (true, _):
+ break
+ case (_, true):
+ break
+ default:
+ continue
+ }
+ }
+
+ return Path(components: lSlice + rSlice)
+ }
+}
+
+extension Array {
+ var fullSlice: ArraySlice {
+ return self[self.indices.suffix(from: 0)]
+ }
+}
diff --git a/regen/Dependencies/Stencil/Context.swift b/regen/Dependencies/Stencil/Context.swift
new file mode 100755
index 0000000..007cf68
--- /dev/null
+++ b/regen/Dependencies/Stencil/Context.swift
@@ -0,0 +1,68 @@
+/// A container for template variables.
+public class Context {
+ var dictionaries: [[String: Any?]]
+
+ public let environment: Environment
+
+ init(dictionary: [String: Any] = [:], environment: Environment? = nil) {
+ if !dictionary.isEmpty {
+ dictionaries = [dictionary]
+ } else {
+ dictionaries = []
+ }
+
+ self.environment = environment ?? Environment()
+ }
+
+ public subscript(key: String) -> Any? {
+ /// Retrieves a variable's value, starting at the current context and going upwards
+ get {
+ for dictionary in Array(dictionaries.reversed()) {
+ if let value = dictionary[key] {
+ return value
+ }
+ }
+
+ return nil
+ }
+
+ /// Set a variable in the current context, deleting the variable if it's nil
+ set(value) {
+ if var dictionary = dictionaries.popLast() {
+ dictionary[key] = value
+ dictionaries.append(dictionary)
+ }
+ }
+ }
+
+ /// Push a new level into the Context
+ fileprivate func push(_ dictionary: [String: Any] = [:]) {
+ dictionaries.append(dictionary)
+ }
+
+ /// Pop the last level off of the Context
+ fileprivate func pop() -> [String: Any?]? {
+ return dictionaries.popLast()
+ }
+
+ /// Push a new level onto the context for the duration of the execution of the given closure
+ public func push(dictionary: [String: Any] = [:], closure: (() throws -> Result)) rethrows -> Result {
+ push(dictionary)
+ defer { _ = pop() }
+ return try closure()
+ }
+
+ public func flatten() -> [String: Any] {
+ var accumulator: [String: Any] = [:]
+
+ for dictionary in dictionaries {
+ for (key, value) in dictionary {
+ if let value = value {
+ accumulator.updateValue(value, forKey: key)
+ }
+ }
+ }
+
+ return accumulator
+ }
+}
diff --git a/regen/Dependencies/Stencil/Environment.swift b/regen/Dependencies/Stencil/Environment.swift
new file mode 100755
index 0000000..0c2c72e
--- /dev/null
+++ b/regen/Dependencies/Stencil/Environment.swift
@@ -0,0 +1,48 @@
+public struct Environment {
+ public let templateClass: Template.Type
+ public var extensions: [Extension]
+
+ public var loader: Loader?
+
+ public init(loader: Loader? = nil,
+ extensions: [Extension] = [],
+ templateClass: Template.Type = Template.self) {
+
+ self.templateClass = templateClass
+ self.loader = loader
+ self.extensions = extensions + [DefaultExtension()]
+ }
+
+ public func loadTemplate(name: String) throws -> Template {
+ if let loader = loader {
+ return try loader.loadTemplate(name: name, environment: self)
+ } else {
+ throw TemplateDoesNotExist(templateNames: [name], loader: nil)
+ }
+ }
+
+ public func loadTemplate(names: [String]) throws -> Template {
+ if let loader = loader {
+ return try loader.loadTemplate(names: names, environment: self)
+ } else {
+ throw TemplateDoesNotExist(templateNames: names, loader: nil)
+ }
+ }
+
+ public func renderTemplate(name: String, context: [String: Any] = [:]) throws -> String {
+ let template = try loadTemplate(name: name)
+ return try render(template: template, context: context)
+ }
+
+ public func renderTemplate(string: String, context: [String: Any] = [:]) throws -> String {
+ let template = templateClass.init(templateString: string, environment: self)
+ return try render(template: template, context: context)
+ }
+
+ func render(template: Template, context: [String: Any]) throws -> String {
+ // update template environment as it can be created from string literal with default environment
+ template.environment = self
+ return try template.render(context)
+ }
+
+}
diff --git a/regen/Dependencies/Stencil/Errors.swift b/regen/Dependencies/Stencil/Errors.swift
new file mode 100755
index 0000000..9c1b584
--- /dev/null
+++ b/regen/Dependencies/Stencil/Errors.swift
@@ -0,0 +1,83 @@
+public class TemplateDoesNotExist: Error, CustomStringConvertible {
+ let templateNames: [String]
+ let loader: Loader?
+
+ public init(templateNames: [String], loader: Loader? = nil) {
+ self.templateNames = templateNames
+ self.loader = loader
+ }
+
+ public var description: String {
+ let templates = templateNames.joined(separator: ", ")
+
+ if let loader = loader {
+ return "Template named `\(templates)` does not exist in loader \(loader)"
+ }
+
+ return "Template named `\(templates)` does not exist. No loaders found"
+ }
+}
+
+public struct TemplateSyntaxError: Error, Equatable, CustomStringConvertible {
+ public let reason: String
+ public var description: String { return reason }
+ public internal(set) var token: Token?
+ public internal(set) var stackTrace: [Token]
+ public var templateName: String? { return token?.sourceMap.filename }
+ var allTokens: [Token] {
+ return stackTrace + (token.map { [$0] } ?? [])
+ }
+
+ public init(reason: String, token: Token? = nil, stackTrace: [Token] = []) {
+ self.reason = reason
+ self.stackTrace = stackTrace
+ self.token = token
+ }
+
+ public init(_ description: String) {
+ self.init(reason: description)
+ }
+}
+
+extension Error {
+ func withToken(_ token: Token?) -> Error {
+ if var error = self as? TemplateSyntaxError {
+ error.token = error.token ?? token
+ return error
+ } else {
+ return TemplateSyntaxError(reason: "\(self)", token: token)
+ }
+ }
+}
+
+public protocol ErrorReporter: AnyObject {
+ func renderError(_ error: Error) -> String
+}
+
+open class SimpleErrorReporter: ErrorReporter {
+
+ open func renderError(_ error: Error) -> String {
+ guard let templateError = error as? TemplateSyntaxError else { return error.localizedDescription }
+
+ func describe(token: Token) -> String {
+ let templateName = token.sourceMap.filename ?? ""
+ let location = token.sourceMap.location
+ let highlight = """
+ \(String(Array(repeating: " ", count: location.lineOffset)))\
+ ^\(String(Array(repeating: "~", count: max(token.contents.count - 1, 0))))
+ """
+
+ return """
+ \(templateName)\(location.lineNumber):\(location.lineOffset): error: \(templateError.reason)
+ \(location.content)
+ \(highlight)
+ """
+ }
+
+ var descriptions = templateError.stackTrace.reduce([]) { $0 + [describe(token: $1)] }
+ let description = templateError.token.map(describe(token:)) ?? templateError.reason
+ descriptions.append(description)
+ return descriptions.joined(separator: "\n")
+ }
+
+}
diff --git a/regen/Dependencies/Stencil/Expression.swift b/regen/Dependencies/Stencil/Expression.swift
new file mode 100755
index 0000000..045b34c
--- /dev/null
+++ b/regen/Dependencies/Stencil/Expression.swift
@@ -0,0 +1,322 @@
+public protocol Expression: CustomStringConvertible {
+ func evaluate(context: Context) throws -> Bool
+}
+
+protocol InfixOperator: Expression {
+ init(lhs: Expression, rhs: Expression)
+}
+
+protocol PrefixOperator: Expression {
+ init(expression: Expression)
+}
+
+final class StaticExpression: Expression, CustomStringConvertible {
+ let value: Bool
+
+ init(value: Bool) {
+ self.value = value
+ }
+
+ func evaluate(context: Context) throws -> Bool {
+ return value
+ }
+
+ var description: String {
+ return "\(value)"
+ }
+}
+
+final class VariableExpression: Expression, CustomStringConvertible {
+ let variable: Resolvable
+
+ init(variable: Resolvable) {
+ self.variable = variable
+ }
+
+ var description: String {
+ return "(variable: \(variable))"
+ }
+
+ /// Resolves a variable in the given context as boolean
+ func resolve(context: Context, variable: Resolvable) throws -> Bool {
+ let result = try variable.resolve(context)
+ var truthy = false
+
+ if let result = result as? [Any] {
+ truthy = !result.isEmpty
+ } else if let result = result as? [String: Any] {
+ truthy = !result.isEmpty
+ } else if let result = result as? Bool {
+ truthy = result
+ } else if let result = result as? String {
+ truthy = !result.isEmpty
+ } else if let value = result, let result = toNumber(value: value) {
+ truthy = result > 0
+ } else if result != nil {
+ truthy = true
+ }
+
+ return truthy
+ }
+
+ func evaluate(context: Context) throws -> Bool {
+ return try resolve(context: context, variable: variable)
+ }
+}
+
+final class NotExpression: Expression, PrefixOperator, CustomStringConvertible {
+ let expression: Expression
+
+ init(expression: Expression) {
+ self.expression = expression
+ }
+
+ var description: String {
+ return "not \(expression)"
+ }
+
+ func evaluate(context: Context) throws -> Bool {
+ return try !expression.evaluate(context: context)
+ }
+}
+
+final class InExpression: Expression, InfixOperator, CustomStringConvertible {
+ let lhs: Expression
+ let rhs: Expression
+
+ init(lhs: Expression, rhs: Expression) {
+ self.lhs = lhs
+ self.rhs = rhs
+ }
+
+ var description: String {
+ return "(\(lhs) in \(rhs))"
+ }
+
+ func evaluate(context: Context) throws -> Bool {
+ if let lhs = lhs as? VariableExpression, let rhs = rhs as? VariableExpression {
+ let lhsValue = try lhs.variable.resolve(context)
+ let rhsValue = try rhs.variable.resolve(context)
+
+ if let lhs = lhsValue as? AnyHashable, let rhs = rhsValue as? [AnyHashable] {
+ return rhs.contains(lhs)
+ } else if let lhs = lhsValue as? Int, let rhs = rhsValue as? CountableClosedRange {
+ return rhs.contains(lhs)
+ } else if let lhs = lhsValue as? Int, let rhs = rhsValue as? CountableRange {
+ return rhs.contains(lhs)
+ } else if let lhs = lhsValue as? String, let rhs = rhsValue as? String {
+ return rhs.contains(lhs)
+ } else if lhsValue == nil && rhsValue == nil {
+ return true
+ }
+ }
+
+ return false
+ }
+
+}
+
+final class OrExpression: Expression, InfixOperator, CustomStringConvertible {
+ let lhs: Expression
+ let rhs: Expression
+
+ init(lhs: Expression, rhs: Expression) {
+ self.lhs = lhs
+ self.rhs = rhs
+ }
+
+ var description: String {
+ return "(\(lhs) or \(rhs))"
+ }
+
+ func evaluate(context: Context) throws -> Bool {
+ let lhs = try self.lhs.evaluate(context: context)
+ if lhs {
+ return lhs
+ }
+
+ return try rhs.evaluate(context: context)
+ }
+}
+
+final class AndExpression: Expression, InfixOperator, CustomStringConvertible {
+ let lhs: Expression
+ let rhs: Expression
+
+ init(lhs: Expression, rhs: Expression) {
+ self.lhs = lhs
+ self.rhs = rhs
+ }
+
+ var description: String {
+ return "(\(lhs) and \(rhs))"
+ }
+
+ func evaluate(context: Context) throws -> Bool {
+ let lhs = try self.lhs.evaluate(context: context)
+ if !lhs {
+ return lhs
+ }
+
+ return try rhs.evaluate(context: context)
+ }
+}
+
+class EqualityExpression: Expression, InfixOperator, CustomStringConvertible {
+ let lhs: Expression
+ let rhs: Expression
+
+ required init(lhs: Expression, rhs: Expression) {
+ self.lhs = lhs
+ self.rhs = rhs
+ }
+
+ var description: String {
+ return "(\(lhs) == \(rhs))"
+ }
+
+ func evaluate(context: Context) throws -> Bool {
+ if let lhs = lhs as? VariableExpression, let rhs = rhs as? VariableExpression {
+ let lhsValue = try lhs.variable.resolve(context)
+ let rhsValue = try rhs.variable.resolve(context)
+
+ if let lhs = lhsValue, let rhs = rhsValue {
+ if let lhs = toNumber(value: lhs), let rhs = toNumber(value: rhs) {
+ return lhs == rhs
+ } else if let lhs = lhsValue as? String, let rhs = rhsValue as? String {
+ return lhs == rhs
+ } else if let lhs = lhsValue as? Bool, let rhs = rhsValue as? Bool {
+ return lhs == rhs
+ }
+ } else if lhsValue == nil && rhsValue == nil {
+ return true
+ }
+ }
+
+ return false
+ }
+}
+
+class NumericExpression: Expression, InfixOperator, CustomStringConvertible {
+ let lhs: Expression
+ let rhs: Expression
+
+ required init(lhs: Expression, rhs: Expression) {
+ self.lhs = lhs
+ self.rhs = rhs
+ }
+
+ var description: String {
+ return "(\(lhs) \(symbol) \(rhs))"
+ }
+
+ func evaluate(context: Context) throws -> Bool {
+ if let lhs = lhs as? VariableExpression, let rhs = rhs as? VariableExpression {
+ let lhsValue = try lhs.variable.resolve(context)
+ let rhsValue = try rhs.variable.resolve(context)
+
+ if let lhs = lhsValue, let rhs = rhsValue {
+ if let lhs = toNumber(value: lhs), let rhs = toNumber(value: rhs) {
+ return compare(lhs: lhs, rhs: rhs)
+ }
+ }
+ }
+
+ return false
+ }
+
+ var symbol: String {
+ return ""
+ }
+
+ func compare(lhs: Number, rhs: Number) -> Bool {
+ return false
+ }
+}
+
+class MoreThanExpression: NumericExpression {
+ override var symbol: String {
+ return ">"
+ }
+
+ override func compare(lhs: Number, rhs: Number) -> Bool {
+ return lhs > rhs
+ }
+}
+
+class MoreThanEqualExpression: NumericExpression {
+ override var symbol: String {
+ return ">="
+ }
+
+ override func compare(lhs: Number, rhs: Number) -> Bool {
+ return lhs >= rhs
+ }
+}
+
+class LessThanExpression: NumericExpression {
+ override var symbol: String {
+ return "<"
+ }
+
+ override func compare(lhs: Number, rhs: Number) -> Bool {
+ return lhs < rhs
+ }
+}
+
+class LessThanEqualExpression: NumericExpression {
+ override var symbol: String {
+ return "<="
+ }
+
+ override func compare(lhs: Number, rhs: Number) -> Bool {
+ return lhs <= rhs
+ }
+}
+
+class InequalityExpression: EqualityExpression {
+ override var description: String {
+ return "(\(lhs) != \(rhs))"
+ }
+
+ override func evaluate(context: Context) throws -> Bool {
+ return try !super.evaluate(context: context)
+ }
+}
+
+// swiftlint:disable:next cyclomatic_complexity
+func toNumber(value: Any) -> Number? {
+ if let value = value as? Float {
+ return Number(value)
+ } else if let value = value as? Double {
+ return Number(value)
+ } else if let value = value as? UInt {
+ return Number(value)
+ } else if let value = value as? Int {
+ return Number(value)
+ } else if let value = value as? Int8 {
+ return Number(value)
+ } else if let value = value as? Int16 {
+ return Number(value)
+ } else if let value = value as? Int32 {
+ return Number(value)
+ } else if let value = value as? Int64 {
+ return Number(value)
+ } else if let value = value as? UInt8 {
+ return Number(value)
+ } else if let value = value as? UInt16 {
+ return Number(value)
+ } else if let value = value as? UInt32 {
+ return Number(value)
+ } else if let value = value as? UInt64 {
+ return Number(value)
+ } else if let value = value as? Number {
+ return value
+ } else if let value = value as? Float64 {
+ return Number(value)
+ } else if let value = value as? Float32 {
+ return Number(value)
+ }
+
+ return nil
+}
diff --git a/regen/Dependencies/Stencil/Extension.swift b/regen/Dependencies/Stencil/Extension.swift
new file mode 100755
index 0000000..a91b4ab
--- /dev/null
+++ b/regen/Dependencies/Stencil/Extension.swift
@@ -0,0 +1,99 @@
+open class Extension {
+ typealias TagParser = (TokenParser, Token) throws -> NodeType
+ var tags = [String: TagParser]()
+
+ var filters = [String: Filter]()
+
+ public init() {
+ }
+
+ /// Registers a new template tag
+ public func registerTag(_ name: String, parser: @escaping (TokenParser, Token) throws -> NodeType) {
+ tags[name] = parser
+ }
+
+ /// Registers a simple template tag with a name and a handler
+ public func registerSimpleTag(_ name: String, handler: @escaping (Context) throws -> String) {
+ registerTag(name) { _, token in
+ SimpleNode(token: token, handler: handler)
+ }
+ }
+
+ /// Registers boolean filter with it's negative counterpart
+ // swiftlint:disable:next discouraged_optional_boolean
+ public func registerFilter(name: String, negativeFilterName: String, filter: @escaping (Any?) throws -> Bool?) {
+ filters[name] = .simple(filter)
+ filters[negativeFilterName] = .simple {
+ guard let result = try filter($0) else { return nil }
+ return !result
+ }
+ }
+
+ /// Registers a template filter with the given name
+ public func registerFilter(_ name: String, filter: @escaping (Any?) throws -> Any?) {
+ filters[name] = .simple(filter)
+ }
+
+ /// Registers a template filter with the given name
+ public func registerFilter(_ name: String, filter: @escaping (Any?, [Any?]) throws -> Any?) {
+ filters[name] = .arguments({ value, args, _ in try filter(value, args) })
+ }
+
+ /// Registers a template filter with the given name
+ public func registerFilter(_ name: String, filter: @escaping (Any?, [Any?], Context) throws -> Any?) {
+ filters[name] = .arguments(filter)
+ }
+}
+
+class DefaultExtension: Extension {
+ override init() {
+ super.init()
+ registerDefaultTags()
+ registerDefaultFilters()
+ }
+
+ fileprivate func registerDefaultTags() {
+ registerTag("for", parser: ForNode.parse)
+ registerTag("if", parser: IfNode.parse)
+ registerTag("ifnot", parser: IfNode.parse_ifnot)
+ #if !os(Linux)
+ registerTag("now", parser: NowNode.parse)
+ #endif
+ registerTag("include", parser: IncludeNode.parse)
+ registerTag("extends", parser: ExtendsNode.parse)
+ registerTag("block", parser: BlockNode.parse)
+ registerTag("filter", parser: FilterNode.parse)
+ }
+
+ fileprivate func registerDefaultFilters() {
+ registerFilter("default", filter: defaultFilter)
+ registerFilter("capitalize", filter: capitalise)
+ registerFilter("uppercase", filter: uppercase)
+ registerFilter("lowercase", filter: lowercase)
+ registerFilter("join", filter: joinFilter)
+ registerFilter("split", filter: splitFilter)
+ registerFilter("indent", filter: indentFilter)
+ registerFilter("filter", filter: filterFilter)
+ }
+}
+
+protocol FilterType {
+ func invoke(value: Any?, arguments: [Any?], context: Context) throws -> Any?
+}
+
+enum Filter: FilterType {
+ case simple(((Any?) throws -> Any?))
+ case arguments(((Any?, [Any?], Context) throws -> Any?))
+
+ func invoke(value: Any?, arguments: [Any?], context: Context) throws -> Any? {
+ switch self {
+ case let .simple(filter):
+ if !arguments.isEmpty {
+ throw TemplateSyntaxError("Can't invoke filter with an argument")
+ }
+ return try filter(value)
+ case let .arguments(filter):
+ return try filter(value, arguments, context)
+ }
+ }
+}
diff --git a/regen/Dependencies/Stencil/FilterTag.swift b/regen/Dependencies/Stencil/FilterTag.swift
new file mode 100755
index 0000000..e623b53
--- /dev/null
+++ b/regen/Dependencies/Stencil/FilterTag.swift
@@ -0,0 +1,36 @@
+class FilterNode: NodeType {
+ let resolvable: Resolvable
+ let nodes: [NodeType]
+ let token: Token?
+
+ class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
+ let bits = token.components
+
+ guard bits.count == 2 else {
+ throw TemplateSyntaxError("'filter' tag takes one argument, the filter expression")
+ }
+
+ let blocks = try parser.parse(until(["endfilter"]))
+
+ guard parser.nextToken() != nil else {
+ throw TemplateSyntaxError("`endfilter` was not found.")
+ }
+
+ let resolvable = try parser.compileFilter("filter_value|\(bits[1])", containedIn: token)
+ return FilterNode(nodes: blocks, resolvable: resolvable, token: token)
+ }
+
+ init(nodes: [NodeType], resolvable: Resolvable, token: Token) {
+ self.nodes = nodes
+ self.resolvable = resolvable
+ self.token = token
+ }
+
+ func render(_ context: Context) throws -> String {
+ let value = try renderNodes(nodes, context)
+
+ return try context.push(dictionary: ["filter_value": value]) {
+ try VariableNode(variable: resolvable, token: token).render(context)
+ }
+ }
+}
diff --git a/regen/Dependencies/Stencil/Filters.swift b/regen/Dependencies/Stencil/Filters.swift
new file mode 100755
index 0000000..a456299
--- /dev/null
+++ b/regen/Dependencies/Stencil/Filters.swift
@@ -0,0 +1,129 @@
+func capitalise(_ value: Any?) -> Any? {
+ if let array = value as? [Any?] {
+ return array.map { stringify($0).capitalized }
+ } else {
+ return stringify(value).capitalized
+ }
+}
+
+func uppercase(_ value: Any?) -> Any? {
+ if let array = value as? [Any?] {
+ return array.map { stringify($0).uppercased() }
+ } else {
+ return stringify(value).uppercased()
+ }
+}
+
+func lowercase(_ value: Any?) -> Any? {
+ if let array = value as? [Any?] {
+ return array.map { stringify($0).lowercased() }
+ } else {
+ return stringify(value).lowercased()
+ }
+}
+
+func defaultFilter(value: Any?, arguments: [Any?]) -> Any? {
+ // value can be optional wrapping nil, so this way we check for underlying value
+ if let value = value, String(describing: value) != "nil" {
+ return value
+ }
+
+ for argument in arguments {
+ if let argument = argument {
+ return argument
+ }
+ }
+
+ return nil
+}
+
+func joinFilter(value: Any?, arguments: [Any?]) throws -> Any? {
+ guard arguments.count < 2 else {
+ throw TemplateSyntaxError("'join' filter takes at most one argument")
+ }
+
+ let separator = stringify(arguments.first ?? "")
+
+ if let value = value as? [Any] {
+ return value
+ .map(stringify)
+ .joined(separator: separator)
+ }
+
+ return value
+}
+
+func splitFilter(value: Any?, arguments: [Any?]) throws -> Any? {
+ guard arguments.count < 2 else {
+ throw TemplateSyntaxError("'split' filter takes at most one argument")
+ }
+
+ let separator = stringify(arguments.first ?? " ")
+ if let value = value as? String {
+ return value.components(separatedBy: separator)
+ }
+
+ return value
+}
+
+func indentFilter(value: Any?, arguments: [Any?]) throws -> Any? {
+ guard arguments.count <= 3 else {
+ throw TemplateSyntaxError("'indent' filter can take at most 3 arguments")
+ }
+
+ var indentWidth = 4
+ if !arguments.isEmpty {
+ guard let value = arguments[0] as? Int else {
+ throw TemplateSyntaxError("""
+ 'indent' filter width argument must be an Integer (\(String(describing: arguments[0])))
+ """)
+ }
+ indentWidth = value
+ }
+
+ var indentationChar = " "
+ if arguments.count > 1 {
+ guard let value = arguments[1] as? String else {
+ throw TemplateSyntaxError("""
+ 'indent' filter indentation argument must be a String (\(String(describing: arguments[1]))
+ """)
+ }
+ indentationChar = value
+ }
+
+ var indentFirst = false
+ if arguments.count > 2 {
+ guard let value = arguments[2] as? Bool else {
+ throw TemplateSyntaxError("'indent' filter indentFirst argument must be a Bool")
+ }
+ indentFirst = value
+ }
+
+ let indentation = [String](repeating: indentationChar, count: indentWidth).joined()
+ return indent(stringify(value), indentation: indentation, indentFirst: indentFirst)
+}
+
+func indent(_ content: String, indentation: String, indentFirst: Bool) -> String {
+ guard !indentation.isEmpty else { return content }
+
+ var lines = content.components(separatedBy: .newlines)
+ let firstLine = (indentFirst ? indentation : "") + lines.removeFirst()
+ let result = lines.reduce([firstLine]) { result, line in
+ result + [(line.isEmpty ? "" : "\(indentation)\(line)")]
+ }
+ return result.joined(separator: "\n")
+}
+
+func filterFilter(value: Any?, arguments: [Any?], context: Context) throws -> Any? {
+ guard let value = value else { return nil }
+ guard arguments.count == 1 else {
+ throw TemplateSyntaxError("'filter' filter takes one argument")
+ }
+
+ let attribute = stringify(arguments[0])
+
+ let expr = try context.environment.compileFilter("$0|\(attribute)")
+ return try context.push(dictionary: ["$0": value]) {
+ try expr.resolve(context)
+ }
+}
diff --git a/regen/Dependencies/Stencil/ForTag.swift b/regen/Dependencies/Stencil/ForTag.swift
new file mode 100755
index 0000000..f727324
--- /dev/null
+++ b/regen/Dependencies/Stencil/ForTag.swift
@@ -0,0 +1,176 @@
+import Foundation
+
+class ForNode: NodeType {
+ let resolvable: Resolvable
+ let loopVariables: [String]
+ let nodes: [NodeType]
+ let emptyNodes: [NodeType]
+ let `where`: Expression?
+ let token: Token?
+
+ class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
+ let components = token.components
+
+ func hasToken(_ token: String, at index: Int) -> Bool {
+ return components.count > (index + 1) && components[index] == token
+ }
+
+ func endsOrHasToken(_ token: String, at index: Int) -> Bool {
+ return components.count == index || hasToken(token, at: index)
+ }
+
+ guard hasToken("in", at: 2) && endsOrHasToken("where", at: 4) else {
+ throw TemplateSyntaxError("'for' statements should use the syntax: `for in [where ]`.")
+ }
+
+ let loopVariables = components[1]
+ .split(separator: ",")
+ .map(String.init)
+ .map { $0.trim(character: " ") }
+
+ let resolvable = try parser.compileResolvable(components[3], containedIn: token)
+
+ let `where` = hasToken("where", at: 4)
+ ? try parser.compileExpression(components: Array(components.suffix(from: 5)), token: token)
+ : nil
+
+ let forNodes = try parser.parse(until(["endfor", "empty"]))
+
+ guard let token = parser.nextToken() else {
+ throw TemplateSyntaxError("`endfor` was not found.")
+ }
+
+ var emptyNodes = [NodeType]()
+ if token.contents == "empty" {
+ emptyNodes = try parser.parse(until(["endfor"]))
+ _ = parser.nextToken()
+ }
+
+ return ForNode(
+ resolvable: resolvable,
+ loopVariables: loopVariables,
+ nodes: forNodes,
+ emptyNodes: emptyNodes,
+ where: `where`,
+ token: token
+ )
+ }
+
+ init(
+ resolvable: Resolvable,
+ loopVariables: [String],
+ nodes: [NodeType],
+ emptyNodes: [NodeType],
+ where: Expression? = nil,
+ token: Token? = nil
+ ) {
+ self.resolvable = resolvable
+ self.loopVariables = loopVariables
+ self.nodes = nodes
+ self.emptyNodes = emptyNodes
+ self.where = `where`
+ self.token = token
+ }
+
+ func render(_ context: Context) throws -> String {
+ var values = try resolve(context)
+
+ if let `where` = self.where {
+ values = try values.filter { item -> Bool in
+ try push(value: item, context: context) {
+ try `where`.evaluate(context: context)
+ }
+ }
+ }
+
+ if !values.isEmpty {
+ let count = values.count
+
+ return try zip(0..., values)
+ .map { index, item in
+ let forContext: [String: Any] = [
+ "first": index == 0,
+ "last": index == (count - 1),
+ "counter": index + 1,
+ "counter0": index,
+ "length": count
+ ]
+
+ return try context.push(dictionary: ["forloop": forContext]) {
+ try push(value: item, context: context) {
+ try renderNodes(nodes, context)
+ }
+ }
+ }
+ .joined()
+ }
+
+ return try context.push {
+ try renderNodes(emptyNodes, context)
+ }
+ }
+
+ private func push(value: Any, context: Context, closure: () throws -> (Result)) throws -> Result {
+ if loopVariables.isEmpty {
+ return try context.push {
+ try closure()
+ }
+ }
+
+ let valueMirror = Mirror(reflecting: value)
+ if case .tuple? = valueMirror.displayStyle {
+ if loopVariables.count > Int(valueMirror.children.count) {
+ throw TemplateSyntaxError("Tuple '\(value)' has less values than loop variables")
+ }
+ var variablesContext = [String: Any]()
+ valueMirror.children.prefix(loopVariables.count).enumerated().forEach { offset, element in
+ if loopVariables[offset] != "_" {
+ variablesContext[loopVariables[offset]] = element.value
+ }
+ }
+
+ return try context.push(dictionary: variablesContext) {
+ try closure()
+ }
+ }
+
+ return try context.push(dictionary: [loopVariables.first ?? "": value]) {
+ try closure()
+ }
+ }
+
+ private func resolve(_ context: Context) throws -> [Any] {
+ let resolved = try resolvable.resolve(context)
+
+ var values: [Any]
+ if let dictionary = resolved as? [String: Any], !dictionary.isEmpty {
+ values = dictionary.sorted { $0.key < $1.key }
+ } else if let array = resolved as? [Any] {
+ values = array
+ } else if let range = resolved as? CountableClosedRange {
+ values = Array(range)
+ } else if let range = resolved as? CountableRange {
+ values = Array(range)
+ } else if let resolved = resolved {
+ let mirror = Mirror(reflecting: resolved)
+ switch mirror.displayStyle {
+ case .struct?, .tuple?:
+ values = Array(mirror.children)
+ case .class?:
+ var children = Array(mirror.children)
+ var currentMirror: Mirror? = mirror
+ while let superclassMirror = currentMirror?.superclassMirror {
+ children.append(contentsOf: superclassMirror.children)
+ currentMirror = superclassMirror
+ }
+ values = Array(children)
+ default:
+ values = []
+ }
+ } else {
+ values = []
+ }
+
+ return values
+ }
+}
diff --git a/regen/Dependencies/Stencil/IfTag.swift b/regen/Dependencies/Stencil/IfTag.swift
new file mode 100755
index 0000000..061914a
--- /dev/null
+++ b/regen/Dependencies/Stencil/IfTag.swift
@@ -0,0 +1,313 @@
+enum Operator {
+ case infix(String, Int, InfixOperator.Type)
+ case prefix(String, Int, PrefixOperator.Type)
+
+ var name: String {
+ switch self {
+ case .infix(let name, _, _):
+ return name
+ case .prefix(let name, _, _):
+ return name
+ }
+ }
+}
+
+let operators: [Operator] = [
+ .infix("in", 5, InExpression.self),
+ .infix("or", 6, OrExpression.self),
+ .infix("and", 7, AndExpression.self),
+ .prefix("not", 8, NotExpression.self),
+ .infix("==", 10, EqualityExpression.self),
+ .infix("!=", 10, InequalityExpression.self),
+ .infix(">", 10, MoreThanExpression.self),
+ .infix(">=", 10, MoreThanEqualExpression.self),
+ .infix("<", 10, LessThanExpression.self),
+ .infix("<=", 10, LessThanEqualExpression.self)
+]
+
+func findOperator(name: String) -> Operator? {
+ for `operator` in operators where `operator`.name == name {
+ return `operator`
+ }
+
+ return nil
+}
+
+indirect enum IfToken {
+ case infix(name: String, bindingPower: Int, operatorType: InfixOperator.Type)
+ case prefix(name: String, bindingPower: Int, operatorType: PrefixOperator.Type)
+ case variable(Resolvable)
+ case subExpression(Expression)
+ case end
+
+ var bindingPower: Int {
+ switch self {
+ case .infix(_, let bindingPower, _):
+ return bindingPower
+ case .prefix(_, let bindingPower, _):
+ return bindingPower
+ case .variable:
+ return 0
+ case .subExpression:
+ return 0
+ case .end:
+ return 0
+ }
+ }
+
+ func nullDenotation(parser: IfExpressionParser) throws -> Expression {
+ switch self {
+ case .infix(let name, _, _):
+ throw TemplateSyntaxError("'if' expression error: infix operator '\(name)' doesn't have a left hand side")
+ case .prefix(_, let bindingPower, let operatorType):
+ let expression = try parser.expression(bindingPower: bindingPower)
+ return operatorType.init(expression: expression)
+ case .variable(let variable):
+ return VariableExpression(variable: variable)
+ case .subExpression(let expression):
+ return expression
+ case .end:
+ throw TemplateSyntaxError("'if' expression error: end")
+ }
+ }
+
+ func leftDenotation(left: Expression, parser: IfExpressionParser) throws -> Expression {
+ switch self {
+ case .infix(_, let bindingPower, let operatorType):
+ let right = try parser.expression(bindingPower: bindingPower)
+ return operatorType.init(lhs: left, rhs: right)
+ case .prefix(let name, _, _):
+ throw TemplateSyntaxError("'if' expression error: prefix operator '\(name)' was called with a left hand side")
+ case .variable(let variable):
+ throw TemplateSyntaxError("'if' expression error: variable '\(variable)' was called with a left hand side")
+ case .subExpression:
+ throw TemplateSyntaxError("'if' expression error: sub expression was called with a left hand side")
+ case .end:
+ throw TemplateSyntaxError("'if' expression error: end")
+ }
+ }
+
+ var isEnd: Bool {
+ switch self {
+ case .end:
+ return true
+ default:
+ return false
+ }
+ }
+}
+
+final class IfExpressionParser {
+ let tokens: [IfToken]
+ var position: Int = 0
+
+ private init(tokens: [IfToken]) {
+ self.tokens = tokens
+ }
+
+ static func parser(components: [String], environment: Environment, token: Token) throws -> IfExpressionParser {
+ return try IfExpressionParser(components: ArraySlice(components), environment: environment, token: token)
+ }
+
+ private init(components: ArraySlice, environment: Environment, token: Token) throws {
+ var parsedComponents = Set()
+ var bracketsBalance = 0
+ self.tokens = try zip(components.indices, components).compactMap { index, component in
+ guard !parsedComponents.contains(index) else { return nil }
+
+ if component == "(" {
+ bracketsBalance += 1
+ let (expression, parsedCount) = try IfExpressionParser.subExpression(
+ from: components.suffix(from: index + 1),
+ environment: environment,
+ token: token
+ )
+ parsedComponents.formUnion(Set(index...(index + parsedCount)))
+ return .subExpression(expression)
+ } else if component == ")" {
+ bracketsBalance -= 1
+ if bracketsBalance < 0 {
+ throw TemplateSyntaxError("'if' expression error: missing opening bracket")
+ }
+ parsedComponents.insert(index)
+ return nil
+ } else {
+ parsedComponents.insert(index)
+ if let `operator` = findOperator(name: component) {
+ switch `operator` {
+ case .infix(let name, let bindingPower, let operatorType):
+ return .infix(name: name, bindingPower: bindingPower, operatorType: operatorType)
+ case .prefix(let name, let bindingPower, let operatorType):
+ return .prefix(name: name, bindingPower: bindingPower, operatorType: operatorType)
+ }
+ }
+ return .variable(try environment.compileResolvable(component, containedIn: token))
+ }
+ }
+ }
+
+ private static func subExpression(
+ from components: ArraySlice,
+ environment: Environment,
+ token: Token
+ ) throws -> (Expression, Int) {
+ var bracketsBalance = 1
+ let subComponents = components.prefix {
+ if $0 == "(" {
+ bracketsBalance += 1
+ } else if $0 == ")" {
+ bracketsBalance -= 1
+ }
+ return bracketsBalance != 0
+ }
+ if bracketsBalance > 0 {
+ throw TemplateSyntaxError("'if' expression error: missing closing bracket")
+ }
+
+ let expressionParser = try IfExpressionParser(components: subComponents, environment: environment, token: token)
+ let expression = try expressionParser.parse()
+ return (expression, subComponents.count)
+ }
+
+ var currentToken: IfToken {
+ if tokens.count > position {
+ return tokens[position]
+ }
+
+ return .end
+ }
+
+ var nextToken: IfToken {
+ position += 1
+ return currentToken
+ }
+
+ func parse() throws -> Expression {
+ let expression = try self.expression()
+
+ if !currentToken.isEnd {
+ throw TemplateSyntaxError("'if' expression error: dangling token")
+ }
+
+ return expression
+ }
+
+ func expression(bindingPower: Int = 0) throws -> Expression {
+ var token = currentToken
+ position += 1
+
+ var left = try token.nullDenotation(parser: self)
+
+ while bindingPower < currentToken.bindingPower {
+ token = currentToken
+ position += 1
+ left = try token.leftDenotation(left: left, parser: self)
+ }
+
+ return left
+ }
+}
+
+/// Represents an if condition and the associated nodes when the condition
+/// evaluates
+final class IfCondition {
+ let expression: Expression?
+ let nodes: [NodeType]
+
+ init(expression: Expression?, nodes: [NodeType]) {
+ self.expression = expression
+ self.nodes = nodes
+ }
+
+ func render(_ context: Context) throws -> String {
+ return try context.push {
+ try renderNodes(nodes, context)
+ }
+ }
+}
+
+class IfNode: NodeType {
+ let conditions: [IfCondition]
+ let token: Token?
+
+ class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
+ var components = token.components
+ components.removeFirst()
+
+ let expression = try parser.compileExpression(components: components, token: token)
+ let nodes = try parser.parse(until(["endif", "elif", "else"]))
+ var conditions: [IfCondition] = [
+ IfCondition(expression: expression, nodes: nodes)
+ ]
+
+ var nextToken = parser.nextToken()
+ while let current = nextToken, current.contents.hasPrefix("elif") {
+ var components = current.components
+ components.removeFirst()
+ let expression = try parser.compileExpression(components: components, token: current)
+
+ let nodes = try parser.parse(until(["endif", "elif", "else"]))
+ nextToken = parser.nextToken()
+ conditions.append(IfCondition(expression: expression, nodes: nodes))
+ }
+
+ if let current = nextToken, current.contents == "else" {
+ conditions.append(IfCondition(expression: nil, nodes: try parser.parse(until(["endif"]))))
+ nextToken = parser.nextToken()
+ }
+
+ guard let current = nextToken, current.contents == "endif" else {
+ throw TemplateSyntaxError("`endif` was not found.")
+ }
+
+ return IfNode(conditions: conditions, token: token)
+ }
+
+ class func parse_ifnot(_ parser: TokenParser, token: Token) throws -> NodeType {
+ var components = token.components
+ guard components.count == 2 else {
+ throw TemplateSyntaxError("'ifnot' statements should use the following syntax 'ifnot condition'.")
+ }
+ components.removeFirst()
+ var trueNodes = [NodeType]()
+ var falseNodes = [NodeType]()
+
+ let expression = try parser.compileExpression(components: components, token: token)
+ falseNodes = try parser.parse(until(["endif", "else"]))
+
+ guard let token = parser.nextToken() else {
+ throw TemplateSyntaxError("`endif` was not found.")
+ }
+
+ if token.contents == "else" {
+ trueNodes = try parser.parse(until(["endif"]))
+ _ = parser.nextToken()
+ }
+
+ return IfNode(conditions: [
+ IfCondition(expression: expression, nodes: trueNodes),
+ IfCondition(expression: nil, nodes: falseNodes)
+ ], token: token)
+ }
+
+ init(conditions: [IfCondition], token: Token? = nil) {
+ self.conditions = conditions
+ self.token = token
+ }
+
+ func render(_ context: Context) throws -> String {
+ for condition in conditions {
+ if let expression = condition.expression {
+ let truthy = try expression.evaluate(context: context)
+
+ if truthy {
+ return try condition.render(context)
+ }
+ } else {
+ return try condition.render(context)
+ }
+ }
+
+ return ""
+ }
+}
diff --git a/regen/Dependencies/Stencil/Include.swift b/regen/Dependencies/Stencil/Include.swift
new file mode 100755
index 0000000..5f5224e
--- /dev/null
+++ b/regen/Dependencies/Stencil/Include.swift
@@ -0,0 +1,46 @@
+class IncludeNode: NodeType {
+ let templateName: Variable
+ let includeContext: String?
+ let token: Token?
+
+ class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
+ let bits = token.components
+
+ guard bits.count == 2 || bits.count == 3 else {
+ throw TemplateSyntaxError("""
+ 'include' tag requires one argument, the template file to be included. \
+ A second optional argument can be used to specify the context that will \
+ be passed to the included file
+ """)
+ }
+
+ return IncludeNode(templateName: Variable(bits[1]), includeContext: bits.count == 3 ? bits[2] : nil, token: token)
+ }
+
+ init(templateName: Variable, includeContext: String? = nil, token: Token) {
+ self.templateName = templateName
+ self.includeContext = includeContext
+ self.token = token
+ }
+
+ func render(_ context: Context) throws -> String {
+ guard let templateName = try self.templateName.resolve(context) as? String else {
+ throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string")
+ }
+
+ let template = try context.environment.loadTemplate(name: templateName)
+
+ do {
+ let subContext = includeContext.flatMap { context[$0] as? [String: Any] } ?? [:]
+ return try context.push(dictionary: subContext) {
+ try template.render(context)
+ }
+ } catch {
+ if let error = error as? TemplateSyntaxError {
+ throw TemplateSyntaxError(reason: error.reason, stackTrace: error.allTokens)
+ } else {
+ throw error
+ }
+ }
+ }
+}
diff --git a/regen/Dependencies/Stencil/Inheritence.swift b/regen/Dependencies/Stencil/Inheritence.swift
new file mode 100755
index 0000000..611d28c
--- /dev/null
+++ b/regen/Dependencies/Stencil/Inheritence.swift
@@ -0,0 +1,189 @@
+class BlockContext {
+ class var contextKey: String { return "block_context" }
+
+ // contains mapping of block names to their nodes and templates where they are defined
+ var blocks: [String: [BlockNode]]
+
+ init(blocks: [String: BlockNode]) {
+ self.blocks = [:]
+ blocks.forEach { self.blocks[$0.key] = [$0.value] }
+ }
+
+ func push(_ block: BlockNode, forKey blockName: String) {
+ if var blocks = blocks[blockName] {
+ blocks.append(block)
+ self.blocks[blockName] = blocks
+ } else {
+ self.blocks[blockName] = [block]
+ }
+ }
+
+ func pop(_ blockName: String) -> BlockNode? {
+ if var blocks = blocks[blockName] {
+ let block = blocks.removeFirst()
+ if blocks.isEmpty {
+ self.blocks.removeValue(forKey: blockName)
+ } else {
+ self.blocks[blockName] = blocks
+ }
+ return block
+ } else {
+ return nil
+ }
+ }
+}
+
+extension Collection {
+ func any(_ closure: (Iterator.Element) -> Bool) -> Iterator.Element? {
+ for element in self {
+ if closure(element) {
+ return element
+ }
+ }
+
+ return nil
+ }
+}
+
+class ExtendsNode: NodeType {
+ let templateName: Variable
+ let blocks: [String: BlockNode]
+ let token: Token?
+
+ class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
+ let bits = token.components
+
+ guard bits.count == 2 else {
+ throw TemplateSyntaxError("'extends' takes one argument, the template file to be extended")
+ }
+
+ let parsedNodes = try parser.parse()
+ guard (parsedNodes.any { $0 is ExtendsNode }) == nil else {
+ throw TemplateSyntaxError("'extends' cannot appear more than once in the same template")
+ }
+
+ let blockNodes = parsedNodes.compactMap { $0 as? BlockNode }
+
+ let nodes = blockNodes.reduce([String: BlockNode]()) { accumulator, node -> [String: BlockNode] in
+ var dict = accumulator
+ dict[node.name] = node
+ return dict
+ }
+
+ return ExtendsNode(templateName: Variable(bits[1]), blocks: nodes, token: token)
+ }
+
+ init(templateName: Variable, blocks: [String: BlockNode], token: Token) {
+ self.templateName = templateName
+ self.blocks = blocks
+ self.token = token
+ }
+
+ func render(_ context: Context) throws -> String {
+ guard let templateName = try self.templateName.resolve(context) as? String else {
+ throw TemplateSyntaxError("'\(self.templateName)' could not be resolved as a string")
+ }
+
+ let baseTemplate = try context.environment.loadTemplate(name: templateName)
+
+ let blockContext: BlockContext
+ if let currentBlockContext = context[BlockContext.contextKey] as? BlockContext {
+ blockContext = currentBlockContext
+ for (name, block) in blocks {
+ blockContext.push(block, forKey: name)
+ }
+ } else {
+ blockContext = BlockContext(blocks: blocks)
+ }
+
+ do {
+ // pushes base template and renders it's content
+ // block_context contains all blocks from child templates
+ return try context.push(dictionary: [BlockContext.contextKey: blockContext]) {
+ try baseTemplate.render(context)
+ }
+ } catch {
+ // if error template is already set (see catch in BlockNode)
+ // and it happend in the same template as current template
+ // there is no need to wrap it in another error
+ if let error = error as? TemplateSyntaxError, error.templateName != token?.sourceMap.filename {
+ throw TemplateSyntaxError(reason: error.reason, stackTrace: error.allTokens)
+ } else {
+ throw error
+ }
+ }
+ }
+}
+
+class BlockNode: NodeType {
+ let name: String
+ let nodes: [NodeType]
+ let token: Token?
+
+ class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
+ let bits = token.components
+
+ guard bits.count == 2 else {
+ throw TemplateSyntaxError("'block' tag takes one argument, the block name")
+ }
+
+ let blockName = bits[1]
+ let nodes = try parser.parse(until(["endblock"]))
+ _ = parser.nextToken()
+ return BlockNode(name: blockName, nodes: nodes, token: token)
+ }
+
+ init(name: String, nodes: [NodeType], token: Token) {
+ self.name = name
+ self.nodes = nodes
+ self.token = token
+ }
+
+ func render(_ context: Context) throws -> String {
+ if let blockContext = context[BlockContext.contextKey] as? BlockContext, let child = blockContext.pop(name) {
+ let childContext = try self.childContext(child, blockContext: blockContext, context: context)
+ // render extension node
+ do {
+ return try context.push(dictionary: childContext) {
+ try child.render(context)
+ }
+ } catch {
+ throw error.withToken(child.token)
+ }
+ }
+
+ return try renderNodes(nodes, context)
+ }
+
+ // child node is a block node from child template that extends this node (has the same name)
+ func childContext(_ child: BlockNode, blockContext: BlockContext, context: Context) throws -> [String: Any] {
+ var childContext: [String: Any] = [BlockContext.contextKey: blockContext]
+
+ if let blockSuperNode = child.nodes.first(where: {
+ if let token = $0.token, case .variable = token.kind, token.contents == "block.super" {
+ return true
+ } else {
+ return false
+ }
+ }) {
+ do {
+ // render base node so that its content can be used as part of child node that extends it
+ childContext["block"] = ["super": try self.render(context)]
+ } catch {
+ if let error = error as? TemplateSyntaxError {
+ throw TemplateSyntaxError(
+ reason: error.reason,
+ token: blockSuperNode.token,
+ stackTrace: error.allTokens)
+ } else {
+ throw TemplateSyntaxError(
+ reason: "\(error)",
+ token: blockSuperNode.token,
+ stackTrace: [])
+ }
+ }
+ }
+ return childContext
+ }
+
+}
diff --git a/regen/Dependencies/Stencil/KeyPath.swift b/regen/Dependencies/Stencil/KeyPath.swift
new file mode 100755
index 0000000..98767b7
--- /dev/null
+++ b/regen/Dependencies/Stencil/KeyPath.swift
@@ -0,0 +1,112 @@
+import Foundation
+
+/// A structure used to represent a template variable, and to resolve it in a given context.
+final class KeyPath {
+ private var components = [String]()
+ private var current = ""
+ private var partialComponents = [String]()
+ private var subscriptLevel = 0
+
+ let variable: String
+ let context: Context
+
+ // Split the keypath string and resolve references if possible
+ init(_ variable: String, in context: Context) {
+ self.variable = variable
+ self.context = context
+ }
+
+ func parse() throws -> [String] {
+ defer {
+ components = []
+ current = ""
+ partialComponents = []
+ subscriptLevel = 0
+ }
+
+ for character in variable {
+ switch character {
+ case "." where subscriptLevel == 0:
+ try foundSeparator()
+ case "[":
+ try openBracket()
+ case "]":
+ try closeBracket()
+ default:
+ try addCharacter(character)
+ }
+ }
+ try finish()
+
+ return components
+ }
+
+ private func foundSeparator() throws {
+ if !current.isEmpty {
+ partialComponents.append(current)
+ }
+
+ guard !partialComponents.isEmpty else {
+ throw TemplateSyntaxError("Unexpected '.' in variable '\(variable)'")
+ }
+
+ components += partialComponents
+ current = ""
+ partialComponents = []
+ }
+
+ // when opening the first bracket, we must have a partial component
+ private func openBracket() throws {
+ guard !partialComponents.isEmpty || !current.isEmpty else {
+ throw TemplateSyntaxError("Unexpected '[' in variable '\(variable)'")
+ }
+
+ if subscriptLevel > 0 {
+ current.append("[")
+ } else if !current.isEmpty {
+ partialComponents.append(current)
+ current = ""
+ }
+
+ subscriptLevel += 1
+ }
+
+ // for a closing bracket at root level, try to resolve the reference
+ private func closeBracket() throws {
+ guard subscriptLevel > 0 else {
+ throw TemplateSyntaxError("Unbalanced ']' in variable '\(variable)'")
+ }
+
+ if subscriptLevel > 1 {
+ current.append("]")
+ } else if !current.isEmpty,
+ let value = try Variable(current).resolve(context) {
+ partialComponents.append("\(value)")
+ current = ""
+ } else {
+ throw TemplateSyntaxError("Unable to resolve subscript '\(current)' in variable '\(variable)'")
+ }
+
+ subscriptLevel -= 1
+ }
+
+ private func addCharacter(_ character: Character) throws {
+ guard partialComponents.isEmpty || subscriptLevel > 0 else {
+ throw TemplateSyntaxError("Unexpected character '\(character)' in variable '\(variable)'")
+ }
+
+ current.append(character)
+ }
+
+ private func finish() throws {
+ // check if we have a last piece
+ if !current.isEmpty {
+ partialComponents.append(current)
+ }
+ components += partialComponents
+
+ guard subscriptLevel == 0 else {
+ throw TemplateSyntaxError("Unbalanced subscript brackets in variable '\(variable)'")
+ }
+ }
+}
diff --git a/regen/Dependencies/Stencil/Lexer.swift b/regen/Dependencies/Stencil/Lexer.swift
new file mode 100755
index 0000000..47465f5
--- /dev/null
+++ b/regen/Dependencies/Stencil/Lexer.swift
@@ -0,0 +1,231 @@
+import Foundation
+
+typealias Line = (content: String, number: UInt, range: Range)
+
+struct Lexer {
+ let templateName: String?
+ let templateString: String
+ let lines: [Line]
+
+ /// The potential token start characters. In a template these appear after a
+ /// `{` character, for example `{{`, `{%`, `{#`, ...
+ private static let tokenChars: [Unicode.Scalar] = ["{", "%", "#"]
+
+ /// The token end characters, corresponding to their token start characters.
+ /// For example, a variable token starts with `{{` and ends with `}}`
+ private static let tokenCharMap: [Unicode.Scalar: Unicode.Scalar] = [
+ "{": "}",
+ "%": "%",
+ "#": "#"
+ ]
+
+ init(templateName: String? = nil, templateString: String) {
+ self.templateName = templateName
+ self.templateString = templateString
+
+ self.lines = templateString.components(separatedBy: .newlines).enumerated().compactMap {
+ guard !$0.element.isEmpty,
+ let range = templateString.range(of: $0.element) else { return nil }
+ return (content: $0.element, number: UInt($0.offset + 1), range)
+ }
+ }
+
+ /// Create a token that will be passed on to the parser, with the given
+ /// content and a range. The content will be tested to see if it's a
+ /// `variable`, a `block` or a `comment`, otherwise it'll default to a simple
+ /// `text` token.
+ ///
+ /// - Parameters:
+ /// - string: The content string of the token
+ /// - range: The range within the template content, used for smart
+ /// error reporting
+ func createToken(string: String, at range: Range) -> Token {
+ func strip() -> String {
+ guard string.count > 4 else { return "" }
+ let trimmed = String(string.dropFirst(2).dropLast(2))
+ .components(separatedBy: "\n")
+ .filter { !$0.isEmpty }
+ .map { $0.trim(character: " ") }
+ .joined(separator: " ")
+ return trimmed
+ }
+
+ if string.hasPrefix("{{") || string.hasPrefix("{%") || string.hasPrefix("{#") {
+ let value = strip()
+ let range = templateString.range(of: value, range: range) ?? range
+ let location = rangeLocation(range)
+ let sourceMap = SourceMap(filename: templateName, location: location)
+
+ if string.hasPrefix("{{") {
+ return .variable(value: value, at: sourceMap)
+ } else if string.hasPrefix("{%") {
+ return .block(value: value, at: sourceMap)
+ } else if string.hasPrefix("{#") {
+ return .comment(value: value, at: sourceMap)
+ }
+ }
+
+ let location = rangeLocation(range)
+ let sourceMap = SourceMap(filename: templateName, location: location)
+ return .text(value: string, at: sourceMap)
+ }
+
+ /// Transforms the template into a list of tokens, that will eventually be
+ /// passed on to the parser.
+ ///
+ /// - Returns: The list of tokens (see `createToken(string: at:)`).
+ func tokenize() -> [Token] {
+ var tokens: [Token] = []
+
+ let scanner = Scanner(templateString)
+ while !scanner.isEmpty {
+ if let (char, text) = scanner.scanForTokenStart(Lexer.tokenChars) {
+ if !text.isEmpty {
+ tokens.append(createToken(string: text, at: scanner.range))
+ }
+
+ guard let end = Lexer.tokenCharMap[char] else { continue }
+ let result = scanner.scanForTokenEnd(end)
+ tokens.append(createToken(string: result, at: scanner.range))
+ } else {
+ tokens.append(createToken(string: scanner.content, at: scanner.range))
+ scanner.content = ""
+ }
+ }
+
+ return tokens
+ }
+
+ /// Finds the line matching the given range (for a token)
+ ///
+ /// - Parameter range: The range to search for.
+ /// - Returns: The content for that line, the line number and offset within
+ /// the line.
+ func rangeLocation(_ range: Range) -> ContentLocation {
+ guard let line = self.lines.first(where: { $0.range.contains(range.lowerBound) }) else {
+ return ("", 0, 0)
+ }
+ let offset = templateString.distance(from: line.range.lowerBound, to: range.lowerBound)
+ return (line.content, line.number, offset)
+ }
+
+}
+
+class Scanner {
+ let originalContent: String
+ var content: String
+ var range: Range
+
+ /// The start delimiter for a token.
+ private static let tokenStartDelimiter: Unicode.Scalar = "{"
+ /// And the corresponding end delimiter for a token.
+ private static let tokenEndDelimiter: Unicode.Scalar = "}"
+
+ init(_ content: String) {
+ self.originalContent = content
+ self.content = content
+ range = content.startIndex.. String {
+ var foundChar = false
+
+ for (index, char) in content.unicodeScalars.enumerated() {
+ if foundChar && char == Scanner.tokenEndDelimiter {
+ let result = String(content.prefix(index + 1))
+ content = String(content.dropFirst(index + 1))
+ range = range.upperBound.. (Unicode.Scalar, String)? {
+ var foundBrace = false
+
+ range = range.upperBound.. String.Index? {
+ var index = startIndex
+
+ while index != endIndex {
+ if character != self[index] {
+ return index
+ }
+ index = self.index(after: index)
+ }
+
+ return nil
+ }
+
+ func findLastNot(character: Character) -> String.Index? {
+ var index = self.index(before: endIndex)
+
+ while index != startIndex {
+ if character != self[index] {
+ return self.index(after: index)
+ }
+ index = self.index(before: index)
+ }
+
+ return nil
+ }
+
+ func trim(character: Character) -> String {
+ let first = findFirstNot(character: character) ?? startIndex
+ let last = findLastNot(character: character) ?? endIndex
+ return String(self[first.. Template
+ func loadTemplate(names: [String], environment: Environment) throws -> Template
+}
+
+extension Loader {
+ public func loadTemplate(names: [String], environment: Environment) throws -> Template {
+ for name in names {
+ do {
+ return try loadTemplate(name: name, environment: environment)
+ } catch is TemplateDoesNotExist {
+ continue
+ } catch {
+ throw error
+ }
+ }
+
+ throw TemplateDoesNotExist(templateNames: names, loader: self)
+ }
+}
+
+// A class for loading a template from disk
+public class FileSystemLoader: Loader, CustomStringConvertible {
+ public let paths: [Path]
+
+ public init(paths: [Path]) {
+ self.paths = paths
+ }
+
+ public init(bundle: [Bundle]) {
+ self.paths = bundle.map {
+ Path($0.bundlePath)
+ }
+ }
+
+ public var description: String {
+ return "FileSystemLoader(\(paths))"
+ }
+
+ public func loadTemplate(name: String, environment: Environment) throws -> Template {
+ for path in paths {
+ let templatePath = try path.safeJoin(path: Path(name))
+
+ if !templatePath.exists {
+ continue
+ }
+
+ let content: String = try templatePath.read()
+ return environment.templateClass.init(templateString: content, environment: environment, name: name)
+ }
+
+ throw TemplateDoesNotExist(templateNames: [name], loader: self)
+ }
+
+ public func loadTemplate(names: [String], environment: Environment) throws -> Template {
+ for path in paths {
+ for templateName in names {
+ let templatePath = try path.safeJoin(path: Path(templateName))
+
+ if templatePath.exists {
+ let content: String = try templatePath.read()
+ return environment.templateClass.init(templateString: content, environment: environment, name: templateName)
+ }
+ }
+ }
+
+ throw TemplateDoesNotExist(templateNames: names, loader: self)
+ }
+}
+
+public class DictionaryLoader: Loader {
+ public let templates: [String: String]
+
+ public init(templates: [String: String]) {
+ self.templates = templates
+ }
+
+ public func loadTemplate(name: String, environment: Environment) throws -> Template {
+ if let content = templates[name] {
+ return environment.templateClass.init(templateString: content, environment: environment, name: name)
+ }
+
+ throw TemplateDoesNotExist(templateNames: [name], loader: self)
+ }
+
+ public func loadTemplate(names: [String], environment: Environment) throws -> Template {
+ for name in names {
+ if let content = templates[name] {
+ return environment.templateClass.init(templateString: content, environment: environment, name: name)
+ }
+ }
+
+ throw TemplateDoesNotExist(templateNames: names, loader: self)
+ }
+}
+
+extension Path {
+ func safeJoin(path: Path) throws -> Path {
+ let newPath = self + path
+
+ if !newPath.absolute().description.hasPrefix(absolute().description) {
+ throw SuspiciousFileOperation(basePath: self, path: newPath)
+ }
+
+ return newPath
+ }
+}
+
+class SuspiciousFileOperation: Error {
+ let basePath: Path
+ let path: Path
+
+ init(basePath: Path, path: Path) {
+ self.basePath = basePath
+ self.path = path
+ }
+
+ var description: String {
+ return "Path `\(path)` is located outside of base path `\(basePath)`"
+ }
+}
diff --git a/regen/Dependencies/Stencil/Node.swift b/regen/Dependencies/Stencil/Node.swift
new file mode 100755
index 0000000..3386211
--- /dev/null
+++ b/regen/Dependencies/Stencil/Node.swift
@@ -0,0 +1,149 @@
+import Foundation
+
+public protocol NodeType {
+ /// Render the node in the given context
+ func render(_ context: Context) throws -> String
+
+ /// Reference to this node's token
+ var token: Token? { get }
+}
+
+/// Render the collection of nodes in the given context
+public func renderNodes(_ nodes: [NodeType], _ context: Context) throws -> String {
+ return try nodes
+ .map {
+ do {
+ return try $0.render(context)
+ } catch {
+ throw error.withToken($0.token)
+ }
+ }
+ .joined()
+}
+
+public class SimpleNode: NodeType {
+ public let handler: (Context) throws -> String
+ public let token: Token?
+
+ public init(token: Token, handler: @escaping (Context) throws -> String) {
+ self.token = token
+ self.handler = handler
+ }
+
+ public func render(_ context: Context) throws -> String {
+ return try handler(context)
+ }
+}
+
+public class TextNode: NodeType {
+ public let text: String
+ public let token: Token?
+
+ public init(text: String) {
+ self.text = text
+ self.token = nil
+ }
+
+ public func render(_ context: Context) throws -> String {
+ return self.text
+ }
+}
+
+public protocol Resolvable {
+ func resolve(_ context: Context) throws -> Any?
+}
+
+public class VariableNode: NodeType {
+ public let variable: Resolvable
+ public var token: Token?
+ let condition: Expression?
+ let elseExpression: Resolvable?
+
+ class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
+ var components = token.components
+
+ func hasToken(_ token: String, at index: Int) -> Bool {
+ return components.count > (index + 1) && components[index] == token
+ }
+
+ let condition: Expression?
+ let elseExpression: Resolvable?
+
+ if hasToken("if", at: 1) {
+ let components = components.suffix(from: 2)
+ if let elseIndex = components.firstIndex(of: "else") {
+ condition = try parser.compileExpression(components: Array(components.prefix(upTo: elseIndex)), token: token)
+ let elseToken = components.suffix(from: elseIndex.advanced(by: 1)).joined(separator: " ")
+ elseExpression = try parser.compileResolvable(elseToken, containedIn: token)
+ } else {
+ condition = try parser.compileExpression(components: Array(components), token: token)
+ elseExpression = nil
+ }
+ } else {
+ condition = nil
+ elseExpression = nil
+ }
+
+ guard let resolvable = components.first else {
+ throw TemplateSyntaxError(reason: "Missing variable name", token: token)
+ }
+ let filter = try parser.compileResolvable(resolvable, containedIn: token)
+ return VariableNode(variable: filter, token: token, condition: condition, elseExpression: elseExpression)
+ }
+
+ public init(variable: Resolvable, token: Token? = nil) {
+ self.variable = variable
+ self.token = token
+ self.condition = nil
+ self.elseExpression = nil
+ }
+
+ init(variable: Resolvable, token: Token? = nil, condition: Expression?, elseExpression: Resolvable?) {
+ self.variable = variable
+ self.token = token
+ self.condition = condition
+ self.elseExpression = elseExpression
+ }
+
+ public init(variable: String, token: Token? = nil) {
+ self.variable = Variable(variable)
+ self.token = token
+ self.condition = nil
+ self.elseExpression = nil
+ }
+
+ public func render(_ context: Context) throws -> String {
+ if let condition = self.condition, try condition.evaluate(context: context) == false {
+ return try elseExpression?.resolve(context).map(stringify) ?? ""
+ }
+
+ let result = try variable.resolve(context)
+ return stringify(result)
+ }
+}
+
+func stringify(_ result: Any?) -> String {
+ if let result = result as? String {
+ return result
+ } else if let array = result as? [Any?] {
+ return unwrap(array).description
+ } else if let result = result as? CustomStringConvertible {
+ return result.description
+ } else if let result = result as? NSObject {
+ return result.description
+ }
+
+ return ""
+}
+
+func unwrap(_ array: [Any?]) -> [Any] {
+ return array.map { (item: Any?) -> Any in
+ if let item = item {
+ if let items = item as? [Any?] {
+ return unwrap(items)
+ } else {
+ return item
+ }
+ } else { return item as Any }
+ }
+}
diff --git a/regen/Dependencies/Stencil/NowTag.swift b/regen/Dependencies/Stencil/NowTag.swift
new file mode 100755
index 0000000..bad6627
--- /dev/null
+++ b/regen/Dependencies/Stencil/NowTag.swift
@@ -0,0 +1,44 @@
+#if !os(Linux)
+import Foundation
+
+class NowNode: NodeType {
+ let format: Variable
+ let token: Token?
+
+ class func parse(_ parser: TokenParser, token: Token) throws -> NodeType {
+ var format: Variable?
+
+ let components = token.components
+ guard components.count <= 2 else {
+ throw TemplateSyntaxError("'now' tags may only have one argument: the format string.")
+ }
+ if components.count == 2 {
+ format = Variable(components[1])
+ }
+
+ return NowNode(format: format, token: token)
+ }
+
+ init(format: Variable?, token: Token? = nil) {
+ self.format = format ?? Variable("\"yyyy-MM-dd 'at' HH:mm\"")
+ self.token = token
+ }
+
+ func render(_ context: Context) throws -> String {
+ let date = Date()
+ let format = try self.format.resolve(context)
+
+ var formatter: DateFormatter
+ if let format = format as? DateFormatter {
+ formatter = format
+ } else if let format = format as? String {
+ formatter = DateFormatter()
+ formatter.dateFormat = format
+ } else {
+ return ""
+ }
+
+ return formatter.string(from: date)
+ }
+}
+#endif
diff --git a/regen/Dependencies/Stencil/Parser.swift b/regen/Dependencies/Stencil/Parser.swift
new file mode 100755
index 0000000..404b8e2
--- /dev/null
+++ b/regen/Dependencies/Stencil/Parser.swift
@@ -0,0 +1,224 @@
+public func until(_ tags: [String]) -> ((TokenParser, Token) -> Bool) {
+ return { parser, token in
+ if let name = token.components.first {
+ for tag in tags where name == tag {
+ return true
+ }
+ }
+
+ return false
+ }
+}
+
+/// A class for parsing an array of tokens and converts them into a collection of Node's
+public class TokenParser {
+ public typealias TagParser = (TokenParser, Token) throws -> NodeType
+
+ fileprivate var tokens: [Token]
+ fileprivate let environment: Environment
+
+ public init(tokens: [Token], environment: Environment) {
+ self.tokens = tokens
+ self.environment = environment
+ }
+
+ /// Parse the given tokens into nodes
+ public func parse() throws -> [NodeType] {
+ return try parse(nil)
+ }
+
+ public func parse(_ parseUntil: ((_ parser: TokenParser, _ token: Token) -> (Bool))?) throws -> [NodeType] {
+ var nodes = [NodeType]()
+
+ while !tokens.isEmpty {
+ guard let token = nextToken() else { break }
+
+ switch token.kind {
+ case .text:
+ nodes.append(TextNode(text: token.contents))
+ case .variable:
+ try nodes.append(VariableNode.parse(self, token: token))
+ case .block:
+ if let parseUntil = parseUntil, parseUntil(self, token) {
+ prependToken(token)
+ return nodes
+ }
+
+ if let tag = token.components.first {
+ do {
+ let parser = try environment.findTag(name: tag)
+ let node = try parser(self, token)
+ nodes.append(node)
+ } catch {
+ throw error.withToken(token)
+ }
+ }
+ case .comment:
+ continue
+ }
+ }
+
+ return nodes
+ }
+
+ public func nextToken() -> Token? {
+ if !tokens.isEmpty {
+ return tokens.remove(at: 0)
+ }
+
+ return nil
+ }
+
+ public func prependToken(_ token: Token) {
+ tokens.insert(token, at: 0)
+ }
+
+ /// Create filter expression from a string contained in provided token
+ public func compileFilter(_ filterToken: String, containedIn token: Token) throws -> Resolvable {
+ return try environment.compileFilter(filterToken, containedIn: token)
+ }
+
+ /// Create boolean expression from components contained in provided token
+ public func compileExpression(components: [String], token: Token) throws -> Expression {
+ return try environment.compileExpression(components: components, containedIn: token)
+ }
+
+ /// Create resolvable (i.e. range variable or filter expression) from a string contained in provided token
+ public func compileResolvable(_ token: String, containedIn containingToken: Token) throws -> Resolvable {
+ return try environment.compileResolvable(token, containedIn: containingToken)
+ }
+
+}
+
+extension Environment {
+ func findTag(name: String) throws -> Extension.TagParser {
+ for ext in extensions {
+ if let filter = ext.tags[name] {
+ return filter
+ }
+ }
+
+ throw TemplateSyntaxError("Unknown template tag '\(name)'")
+ }
+
+ func findFilter(_ name: String) throws -> FilterType {
+ for ext in extensions {
+ if let filter = ext.filters[name] {
+ return filter
+ }
+ }
+
+ let suggestedFilters = self.suggestedFilters(for: name)
+ if suggestedFilters.isEmpty {
+ throw TemplateSyntaxError("Unknown filter '\(name)'.")
+ } else {
+ throw TemplateSyntaxError("""
+ Unknown filter '\(name)'. \
+ Found similar filters: \(suggestedFilters.map { "'\($0)'" }.joined(separator: ", ")).
+ """)
+ }
+ }
+
+ private func suggestedFilters(for name: String) -> [String] {
+ let allFilters = extensions.flatMap { $0.filters.keys }
+
+ let filtersWithDistance = allFilters
+ .map { (filterName: $0, distance: $0.levenshteinDistance(name)) }
+ // do not suggest filters which names are shorter than the distance
+ .filter { $0.filterName.count > $0.distance }
+ guard let minDistance = filtersWithDistance.min(by: { $0.distance < $1.distance })?.distance else {
+ return []
+ }
+ // suggest all filters with the same distance
+ return filtersWithDistance.filter { $0.distance == minDistance }.map { $0.filterName }
+ }
+
+ /// Create filter expression from a string
+ public func compileFilter(_ token: String) throws -> Resolvable {
+ return try FilterExpression(token: token, environment: self)
+ }
+
+ /// Create filter expression from a string contained in provided token
+ public func compileFilter(_ filterToken: String, containedIn containingToken: Token) throws -> Resolvable {
+ do {
+ return try FilterExpression(token: filterToken, environment: self)
+ } catch {
+ guard var syntaxError = error as? TemplateSyntaxError, syntaxError.token == nil else {
+ throw error
+ }
+ // find offset of filter in the containing token so that only filter is highligted, not the whole token
+ if let filterTokenRange = containingToken.contents.range(of: filterToken) {
+ var location = containingToken.sourceMap.location
+ location.lineOffset += containingToken.contents.distance(
+ from: containingToken.contents.startIndex,
+ to: filterTokenRange.lowerBound
+ )
+ syntaxError.token = .variable(
+ value: filterToken,
+ at: SourceMap(filename: containingToken.sourceMap.filename, location: location)
+ )
+ } else {
+ syntaxError.token = containingToken
+ }
+ throw syntaxError
+ }
+ }
+
+ /// Create resolvable (i.e. range variable or filter expression) from a string
+ public func compileResolvable(_ token: String) throws -> Resolvable {
+ return try RangeVariable(token, environment: self)
+ ?? compileFilter(token)
+ }
+
+ /// Create resolvable (i.e. range variable or filter expression) from a string contained in provided token
+ public func compileResolvable(_ token: String, containedIn containingToken: Token) throws -> Resolvable {
+ return try RangeVariable(token, environment: self, containedIn: containingToken)
+ ?? compileFilter(token, containedIn: containingToken)
+ }
+
+ /// Create boolean expression from components contained in provided token
+ public func compileExpression(components: [String], containedIn token: Token) throws -> Expression {
+ return try IfExpressionParser.parser(components: components, environment: self, token: token).parse()
+ }
+
+}
+
+// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows
+extension String {
+ subscript(_ index: Int) -> Character {
+ return self[self.index(self.startIndex, offsetBy: index)]
+ }
+
+ func levenshteinDistance(_ target: String) -> Int {
+ // create two work vectors of integer distances
+ var last, current: [Int]
+
+ // initialize v0 (the previous row of distances)
+ // this row is A[0][i]: edit distance for an empty s
+ // the distance is just the number of characters to delete from t
+ last = [Int](0...target.count)
+ current = [Int](repeating: 0, count: target.count + 1)
+
+ for selfIndex in 0.. String {
+ let context = context
+ let parser = TokenParser(tokens: tokens, environment: context.environment)
+ let nodes = try parser.parse()
+ return try renderNodes(nodes, context)
+ }
+
+ // swiftlint:disable discouraged_optional_collection
+ /// Render the given template
+ open func render(_ dictionary: [String: Any]? = nil) throws -> String {
+ return try render(Context(dictionary: dictionary ?? [:], environment: environment))
+ }
+}
diff --git a/regen/Dependencies/Stencil/Tokenizer.swift b/regen/Dependencies/Stencil/Tokenizer.swift
new file mode 100755
index 0000000..30f3117
--- /dev/null
+++ b/regen/Dependencies/Stencil/Tokenizer.swift
@@ -0,0 +1,130 @@
+import Foundation
+
+extension String {
+ /// Split a string by a separator leaving quoted phrases together
+ func smartSplit(separator: Character = " ") -> [String] {
+ var word = ""
+ var components: [String] = []
+ var separate: Character = separator
+ var singleQuoteCount = 0
+ var doubleQuoteCount = 0
+
+ for character in self {
+ if character == "'" {
+ singleQuoteCount += 1
+ } else if character == "\"" {
+ doubleQuoteCount += 1
+ }
+
+ if character == separate {
+ if separate != separator {
+ word.append(separate)
+ } else if (singleQuoteCount % 2 == 0 || doubleQuoteCount % 2 == 0) && !word.isEmpty {
+ appendWord(word, to: &components)
+ word = ""
+ }
+
+ separate = separator
+ } else {
+ if separate == separator && (character == "'" || character == "\"") {
+ separate = character
+ }
+ word.append(character)
+ }
+ }
+
+ if !word.isEmpty {
+ appendWord(word, to: &components)
+ }
+
+ return components
+ }
+
+ private func appendWord(_ word: String, to components: inout [String]) {
+ let specialCharacters = ",|:"
+
+ if !components.isEmpty {
+ if let precedingChar = components.last?.last, specialCharacters.contains(precedingChar) {
+ components[components.count - 1] += word
+ } else if specialCharacters.contains(word) {
+ components[components.count - 1] += word
+ } else if word != "(" && word.first == "(" || word != ")" && word.first == ")" {
+ components.append(String(word.prefix(1)))
+ appendWord(String(word.dropFirst()), to: &components)
+ } else if word != "(" && word.last == "(" || word != ")" && word.last == ")" {
+ appendWord(String(word.dropLast()), to: &components)
+ components.append(String(word.suffix(1)))
+ } else {
+ components.append(word)
+ }
+ } else {
+ components.append(word)
+ }
+ }
+}
+
+public struct SourceMap: Equatable {
+ public let filename: String?
+ public let location: ContentLocation
+
+ init(filename: String? = nil, location: ContentLocation = ("", 0, 0)) {
+ self.filename = filename
+ self.location = location
+ }
+
+ static let unknown = SourceMap()
+
+ public static func == (lhs: SourceMap, rhs: SourceMap) -> Bool {
+ return lhs.filename == rhs.filename && lhs.location == rhs.location
+ }
+}
+
+public class Token: Equatable {
+ public enum Kind: Equatable {
+ /// A token representing a piece of text.
+ case text
+ /// A token representing a variable.
+ case variable
+ /// A token representing a comment.
+ case comment
+ /// A token representing a template block.
+ case block
+ }
+
+ public let contents: String
+ public let kind: Kind
+ public let sourceMap: SourceMap
+
+ /// Returns the underlying value as an array seperated by spaces
+ public private(set) lazy var components: [String] = self.contents.smartSplit()
+
+ init(contents: String, kind: Kind, sourceMap: SourceMap) {
+ self.contents = contents
+ self.kind = kind
+ self.sourceMap = sourceMap
+ }
+
+ /// A token representing a piece of text.
+ public static func text(value: String, at sourceMap: SourceMap) -> Token {
+ return Token(contents: value, kind: .text, sourceMap: sourceMap)
+ }
+
+ /// A token representing a variable.
+ public static func variable(value: String, at sourceMap: SourceMap) -> Token {
+ return Token(contents: value, kind: .variable, sourceMap: sourceMap)
+ }
+
+ /// A token representing a comment.
+ public static func comment(value: String, at sourceMap: SourceMap) -> Token {
+ return Token(contents: value, kind: .comment, sourceMap: sourceMap)
+ }
+
+ /// A token representing a template block.
+ public static func block(value: String, at sourceMap: SourceMap) -> Token {
+ return Token(contents: value, kind: .block, sourceMap: sourceMap)
+ }
+
+ public static func == (lhs: Token, rhs: Token) -> Bool {
+ return lhs.contents == rhs.contents && lhs.kind == rhs.kind && lhs.sourceMap == rhs.sourceMap
+ }
+}
diff --git a/regen/Dependencies/Stencil/Variable.swift b/regen/Dependencies/Stencil/Variable.swift
new file mode 100755
index 0000000..ee03028
--- /dev/null
+++ b/regen/Dependencies/Stencil/Variable.swift
@@ -0,0 +1,280 @@
+import Foundation
+
+typealias Number = Float
+
+class FilterExpression: Resolvable {
+ let filters: [(FilterType, [Variable])]
+ let variable: Variable
+
+ init(token: String, environment: Environment) throws {
+ let bits = token.smartSplit(separator: "|").map { String($0).trim(character: " ") }
+ if bits.isEmpty {
+ throw TemplateSyntaxError("Variable tags must include at least 1 argument")
+ }
+
+ variable = Variable(bits[0])
+ let filterBits = bits[bits.indices.suffix(from: 1)]
+
+ do {
+ filters = try filterBits.map {
+ let (name, arguments) = parseFilterComponents(token: $0)
+ let filter = try environment.findFilter(name)
+ return (filter, arguments)
+ }
+ } catch {
+ filters = []
+ throw error
+ }
+ }
+
+ func resolve(_ context: Context) throws -> Any? {
+ let result = try variable.resolve(context)
+
+ return try filters.reduce(result) { value, filter in
+ let arguments = try filter.1.map { try $0.resolve(context) }
+ return try filter.0.invoke(value: value, arguments: arguments, context: context)
+ }
+ }
+}
+
+/// A structure used to represent a template variable, and to resolve it in a given context.
+public struct Variable: Equatable, Resolvable {
+ public let variable: String
+
+ /// Create a variable with a string representing the variable
+ public init(_ variable: String) {
+ self.variable = variable
+ }
+
+ /// Resolve the variable in the given context
+ public func resolve(_ context: Context) throws -> Any? {
+ if (variable.hasPrefix("'") && variable.hasSuffix("'")) || (variable.hasPrefix("\"") && variable.hasSuffix("\"")) {
+ // String literal
+ return String(variable[variable.index(after: variable.startIndex) ..< variable.index(before: variable.endIndex)])
+ }
+
+ // Number literal
+ if let int = Int(variable) {
+ return int
+ }
+ if let number = Number(variable) {
+ return number
+ }
+ // Boolean literal
+ if let bool = Bool(variable) {
+ return bool
+ }
+
+ var current: Any? = context
+ for bit in try lookup(context) {
+ current = resolve(bit: bit, context: current)
+
+ if current == nil {
+ return nil
+ }
+ }
+
+ if let resolvable = current as? Resolvable {
+ current = try resolvable.resolve(context)
+ } else if let node = current as? NodeType {
+ current = try node.render(context)
+ }
+
+ return Variable.normalize(current)
+ }
+
+ // Split the lookup string and resolve references if possible
+ private func lookup(_ context: Context) throws -> [String] {
+ let keyPath = KeyPath(variable, in: context)
+ return try keyPath.parse()
+ }
+
+ // Try to resolve a partial keypath for the given context
+ private func resolve(bit: String, context: Any?) -> Any? {
+ let context = Variable.normalize(context)
+
+ if let context = context as? Context {
+ return context[bit]
+ } else if let dictionary = context as? [String: Any] {
+ return resolve(bit: bit, dictionary: dictionary)
+ } else if let array = context as? [Any] {
+ return resolve(bit: bit, collection: array)
+ } else if let string = context as? String {
+ return resolve(bit: bit, collection: string)
+ } else if let object = context as? NSObject { // NSKeyValueCoding
+ #if os(Linux)
+ return nil
+ #else
+ if object.responds(to: Selector(bit)) {
+ return object.value(forKey: bit)
+ }
+ #endif
+ } else if let value = context {
+ return Mirror(reflecting: value).getValue(for: bit)
+ }
+
+ return nil
+ }
+
+ // Try to resolve a partial keypath for the given dictionary
+ private func resolve(bit: String, dictionary: [String: Any]) -> Any? {
+ if bit == "count" {
+ return dictionary.count
+ } else {
+ return dictionary[bit]
+ }
+ }
+
+ // Try to resolve a partial keypath for the given collection
+ private func resolve(bit: String, collection: T) -> Any? {
+ if let index = Int(bit) {
+ if index >= 0 && index < collection.count {
+ return collection[collection.index(collection.startIndex, offsetBy: index)]
+ } else {
+ return nil
+ }
+ } else if bit == "first" {
+ return collection.first
+ } else if bit == "last" {
+ return collection[collection.index(collection.endIndex, offsetBy: -1)]
+ } else if bit == "count" {
+ return collection.count
+ } else {
+ return nil
+ }
+ }
+}
+
+/// A structure used to represet range of two integer values expressed as `from...to`.
+/// Values should be numbers (they will be converted to integers).
+/// Rendering this variable produces array from range `from...to`.
+/// If `from` is more than `to` array will contain values of reversed range.
+public struct RangeVariable: Resolvable {
+ public let from: Resolvable
+ // swiftlint:disable:next identifier_name
+ public let to: Resolvable
+
+ public init?(_ token: String, environment: Environment) throws {
+ let components = token.components(separatedBy: "...")
+ guard components.count == 2 else {
+ return nil
+ }
+
+ self.from = try environment.compileFilter(components[0])
+ self.to = try environment.compileFilter(components[1])
+ }
+
+ public init?(_ token: String, environment: Environment, containedIn containingToken: Token) throws {
+ let components = token.components(separatedBy: "...")
+ guard components.count == 2 else {
+ return nil
+ }
+
+ self.from = try environment.compileFilter(components[0], containedIn: containingToken)
+ self.to = try environment.compileFilter(components[1], containedIn: containingToken)
+ }
+
+ public func resolve(_ context: Context) throws -> Any? {
+ let lowerResolved = try from.resolve(context)
+ let upperResolved = try to.resolve(context)
+
+ guard let lower = lowerResolved.flatMap(toNumber(value:)).flatMap(Int.init) else {
+ throw TemplateSyntaxError("'from' value is not an Integer (\(lowerResolved ?? "nil"))")
+ }
+
+ guard let upper = upperResolved.flatMap(toNumber(value:)).flatMap(Int.init) else {
+ throw TemplateSyntaxError("'to' value is not an Integer (\(upperResolved ?? "nil") )")
+ }
+
+ let range = min(lower, upper)...max(lower, upper)
+ return lower > upper ? Array(range.reversed()) : Array(range)
+ }
+
+}
+
+extension Variable {
+ static func normalize(_ current: Any?) -> Any? {
+ if let current = current as? Normalizable {
+ return current.normalize()
+ }
+
+ return current
+ }
+}
+
+protocol Normalizable {
+ func normalize() -> Any?
+}
+
+extension Array: Normalizable {
+ func normalize() -> Any? {
+ return map { $0 as Any }
+ }
+}
+
+extension NSArray: Normalizable {
+ func normalize() -> Any? {
+ return map { $0 as Any }
+ }
+}
+
+extension Dictionary: Normalizable {
+ func normalize() -> Any? {
+ var dictionary: [String: Any] = [:]
+
+ for (key, value) in self {
+ if let key = key as? String {
+ dictionary[key] = Variable.normalize(value)
+ } else if let key = key as? CustomStringConvertible {
+ dictionary[key.description] = Variable.normalize(value)
+ }
+ }
+
+ return dictionary
+ }
+}
+
+func parseFilterComponents(token: String) -> (String, [Variable]) {
+ var components = token.smartSplit(separator: ":")
+ let name = components.removeFirst().trim(character: " ")
+ let variables = components
+ .joined(separator: ":")
+ .smartSplit(separator: ",")
+ .map { Variable($0.trim(character: " ")) }
+ return (name, variables)
+}
+
+extension Mirror {
+ func getValue(for key: String) -> Any? {
+ let result = descendant(key) ?? Int(key).flatMap { descendant($0) }
+ if result == nil {
+ // go through inheritance chain to reach superclass properties
+ return superclassMirror?.getValue(for: key)
+ } else if let result = result {
+ guard String(describing: result) != "nil" else {
+ // mirror returns non-nil value even for nil-containing properties
+ // so we have to check if its value is actually nil or not
+ return nil
+ }
+ if let result = (result as? AnyOptional)?.wrapped {
+ return result
+ } else {
+ return result
+ }
+ }
+ return result
+ }
+}
+
+protocol AnyOptional {
+ var wrapped: Any? { get }
+}
+
+extension Optional: AnyOptional {
+ var wrapped: Any? {
+ switch self {
+ case let .some(value): return value
+ case .none: return nil
+ }
+ }
+}
diff --git a/regen/Dependencies/Stencil/_SwiftSupport.swift b/regen/Dependencies/Stencil/_SwiftSupport.swift
new file mode 100755
index 0000000..4519fbd
--- /dev/null
+++ b/regen/Dependencies/Stencil/_SwiftSupport.swift
@@ -0,0 +1,46 @@
+import Foundation
+
+#if !swift(>=4.1)
+ public extension Sequence {
+ func compactMap(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
+ return try flatMap(transform)
+ }
+ }
+#endif
+
+#if !swift(>=4.1)
+ public extension Collection {
+ func index(_ index: Self.Index, offsetBy offset: Int) -> Self.Index {
+ let indexDistance = Self.IndexDistance(offset)
+ return self.index(index, offsetBy: indexDistance)
+ }
+ }
+#endif
+
+#if !swift(>=4.1)
+public extension TemplateSyntaxError {
+ public static func == (lhs: TemplateSyntaxError, rhs: TemplateSyntaxError) -> Bool {
+ return lhs.reason == rhs.reason &&
+ lhs.description == rhs.description &&
+ lhs.token == rhs.token &&
+ lhs.stackTrace == rhs.stackTrace &&
+ lhs.templateName == rhs.templateName
+ }
+}
+#endif
+
+#if !swift(>=4.1)
+public extension Variable {
+ public static func == (lhs: Variable, rhs: Variable) -> Bool {
+ return lhs.variable == rhs.variable
+ }
+}
+#endif
+
+#if !swift(>=4.2)
+extension ArraySlice where Element: Equatable {
+ func firstIndex(of element: Element) -> Int? {
+ return index(of: element)
+ }
+}
+#endif
diff --git a/regen/General Models/CommandLineParameter.swift b/regen/General Models/CommandLineParameter.swift
new file mode 100644
index 0000000..6c60629
--- /dev/null
+++ b/regen/General Models/CommandLineParameter.swift
@@ -0,0 +1,31 @@
+//
+// CommandLineParameter.swift
+// regen
+//
+// Created by Ido Mizrachi on 17/07/2019.
+// Copyright © 2019 Ido Mizrachi. All rights reserved.
+//
+
+import Foundation
+
+enum CommandLineParameter: String {
+ case searchPath = "--search-path"
+ case template = "--template"
+ case outputFilename = "--output-filename"
+ case outputClassName = "--output-class-name"
+ case baseLanguageCode = "--base-language-code"
+ case whitelist = "--whitelist-filename"
+ case parameterStartRegex = "--parameter-start-regex"
+ case parameterEndRegex = "--parameter-end-regex"
+ case parameterStartOffset = "--parameter-start-offset"
+ case parameterEndOffset = "--parameter-end-offset"
+ case assetsFile = "--assets"
+}
+
+func tryParse(_ parameter: CommandLineParameter, from arguments: [String]) -> T? {
+ if let index = arguments.firstIndex(of: parameter.rawValue), index+1 < arguments.count {
+ return T(arguments[index+1])
+ } else {
+ return nil
+ }
+}
diff --git a/RegenFramework/Data Structures/Tree.swift b/regen/General Models/Tree.swift
similarity index 100%
rename from RegenFramework/Data Structures/Tree.swift
rename to regen/General Models/Tree.swift
diff --git a/regen/Images/AssetsFinder.swift b/regen/Images/AssetsFinder.swift
deleted file mode 100644
index a1ebdfd..0000000
--- a/regen/Images/AssetsFinder.swift
+++ /dev/null
@@ -1,36 +0,0 @@
-//
-// AssetsFinder.swift
-// Regen
-//
-// Created by Ido Mizrachi on 7/8/16.
-//
-
-import Cocoa
-
-class AssetsFinder {
- public static let assetsSuffix = ".xcassets"
-
- let fileManager : FileManager
-
- init(fileManager: FileManager) {
- self.fileManager = fileManager
- }
-
- func findAssetsFiles(in path : String) -> [String] {
- var assets : [String] = []
- Logger.debug("\tSearching image assets files: started")
- let enumerator = fileManager.enumerator(atPath: path)
- while let element = enumerator?.nextObject() as? String {
- if isAsset(element) {
- assets.append(path + "/" + element)
- }
- }
- Logger.debug("\tSearching image assets files: finished (\(assets.count) asset\\s found)")
- return assets
- }
-
- func isAsset(_ element : String) -> Bool {
- return element.hasSuffix(AssetsFinder.assetsSuffix)
- }
-
-}
diff --git a/regen/Utilities/ClassName.swift b/regen/Images/ClassName.swift
similarity index 85%
rename from regen/Utilities/ClassName.swift
rename to regen/Images/ClassName.swift
index 1d48c32..22e3ed7 100644
--- a/regen/Utilities/ClassName.swift
+++ b/regen/Images/ClassName.swift
@@ -9,7 +9,7 @@
import Cocoa
extension String {
- func className() -> String {
+ func className() -> String {
return prefix(1).uppercased() + String(dropFirst())
}
}
diff --git a/regen/Images/ImageFinder.swift b/regen/Images/ImageFinder.swift
deleted file mode 100644
index f8ac0f8..0000000
--- a/regen/Images/ImageFinder.swift
+++ /dev/null
@@ -1,39 +0,0 @@
-//
-// ImageFinder.swift
-// Regen
-//
-// Created by Ido Mizrachi on 7/8/16.
-//
-
-import Cocoa
-
-class ImageFinder {
- static let imageSuffix = ".imageset"
-
- let fileManager : FileManager
-
- init(fileManager: FileManager) {
- self.fileManager = fileManager
- }
-
- func findImages(in assets : String) -> [String] {
- var images : [String] = []
- var searchPath = assets
- if !assets.hasSuffix("/") {
- searchPath = searchPath + "/"
- }
- Logger.debug("\tSearching for images: started")
- let enumaretor = fileManager.enumerator(atPath: searchPath)
- while let element = enumaretor?.nextObject() as? String {
- if isImage(element) {
- images.append(searchPath + element)
- }
- }
- Logger.debug("\tSearching for images: finished (\(images.count) image\\s found)")
- return images
- }
-
- func isImage(_ element : String) -> Bool {
- return element.hasSuffix(ImageFinder.imageSuffix)
- }
-}
diff --git a/regen/Images/ImageOperation.swift b/regen/Images/ImageOperation.swift
deleted file mode 100644
index 11350db..0000000
--- a/regen/Images/ImageOperation.swift
+++ /dev/null
@@ -1,94 +0,0 @@
-//
-// ImageOperation.swift
-// Regen
-//
-// Created by Ido Mizrachi on 7/15/16.
-//
-
-import Foundation
-
-class ImageOperation {
- let fileManager : FileManager
- let language: Language
-
- init(fileManager : FileManager, language: Language) {
- self.fileManager = fileManager
- self.language = language
- }
-
- func run(_ searchPath : String, output : String) {
- Logger.info("Images scan: started")
- let assetsFinder = AssetsFinder(fileManager: fileManager)
- let imageFinder = ImageFinder(fileManager: fileManager)
- let imagesetParser = ImagesetParser()
- var images : [Image] = []
-
- let xcassetsFiles = assetsFinder.findAssetsFiles(in: searchPath)
- for assetsFile in xcassetsFiles {
- let imageFiles = imageFinder.findImages(in: assetsFile)
- for imageFile in imageFiles {
- if let image = imagesetParser.parseImage(imageFile) {
- images.append(image)
- }
- }
- }
-
- let imagesTree = foldersTree(images: images)
-
- let validator = ImagesValidator()
- let validationIssues = validator.validate(images)
- if validationIssues.count == 0 {
- let generator: ImagesClassGenerator
- if self.language == .ObjC {
- generator = ImagesClassGeneratorObjC()
- } else {
- generator = ImagesClassGeneratorSwift()
- }
- generator.generateClass(fromImagesTree: imagesTree, generatedFile: output)
- } else {
- Logger.error("Issues found:")
- for validationIssue in validationIssues {
- let firstImage = validationIssue.firstImage
- let secondImage = validationIssue.secondImage
- Logger.error("\timage \(firstImage) conflicts with \(secondImage) as property \(validationIssue.property)")
- }
- exit(EXIT_FAILURE)
- }
- Logger.info("Images scan: Finished")
- }
-
- func foldersTree(images: [Image]) -> Tree {
- let root = ImageNodeItem(folder: "", folderClass: "")
- let tree: Tree = Tree(item: root)
- for image in images {
- if image.folders.count == 0 {
- tree.item.images.append(image.file)
- } else {
- var node: TreeNode = tree
- var nextNode: TreeNode? = nil
- for folder in image.folders {
- var found = false
- for child in node.children {
- if child.item.folder == folder.propertyName {
- found = true
- nextNode = child
- break
- }
- }
- if found == false {
- let uuid = UUID().uuidString
- let folderClass = folder.propertyName.className() + "_" + String(uuid[.. = TreeNode(item: ImageNodeItem(folder: folder.propertyName, folderClass: folderClass))
- node.addChild(folderNode)
- node = folderNode
- } else {
- node = nextNode!
- }
- }
- node.item.images.append(image.file)
- }
- }
-
- return tree
- }
-}
diff --git a/regen/Images/ImageesAssetsFinder.swift b/regen/Images/ImageesAssetsFinder.swift
new file mode 100644
index 0000000..5b3e840
--- /dev/null
+++ b/regen/Images/ImageesAssetsFinder.swift
@@ -0,0 +1,36 @@
+//
+// AssetsFinder.swift
+// Regen
+//
+// Created by Ido Mizrachi on 7/8/16.
+//
+
+import Cocoa
+
+extension Images {
+ class AssetsFinder {
+ public static let assetsSuffix = ".xcassets"
+
+ init() {
+ }
+
+ func findAssetsFiles(in path : String) -> [String] {
+ var assets : [String] = []
+// Logger.debug("\tSearching image assets files: started")
+ let enumerator = FileManager.default.enumerator(atPath: path)
+ while let element = enumerator?.nextObject() as? String {
+ if isAsset(element) {
+ assets.append(path + "/" + element)
+ }
+ }
+// Logger.debug("\tSearching image assets files: finished (\(assets.count) asset\\s found)")
+ return assets
+ }
+
+ func isAsset(_ element : String) -> Bool {
+ return element.hasSuffix(AssetsFinder.assetsSuffix)
+ }
+
+ }
+
+}
diff --git a/regen/Images/ImagesFinder.swift b/regen/Images/ImagesFinder.swift
new file mode 100644
index 0000000..724e97b
--- /dev/null
+++ b/regen/Images/ImagesFinder.swift
@@ -0,0 +1,37 @@
+//
+// ImageFinder.swift
+// Regen
+//
+// Created by Ido Mizrachi on 7/8/16.
+//
+
+import Cocoa
+
+extension Images {
+ class ImagesetsFinder {
+
+ init() {
+ }
+
+ func find(in assets : String) -> [String] {
+ var images : [String] = []
+ var searchPath = assets
+ if !assets.hasSuffix("/") {
+ searchPath = searchPath + "/"
+ }
+// Logger.debug("\tSearching for images: started")
+ let enumaretor = FileManager.default.enumerator(atPath: searchPath)
+ while let element = enumaretor?.nextObject() as? String {
+ if isImageset(element) {
+ images.append(searchPath + element)
+ }
+ }
+// Logger.debug("\tSearching for images: finished (\(images.count) image\\s found)")
+ return images
+ }
+
+ func isImageset(_ element : String) -> Bool {
+ return element.hasSuffix(Images.imagesetSuffix)
+ }
+ }
+}
diff --git a/regen/Images/ImagesNamespace.swift b/regen/Images/ImagesNamespace.swift
new file mode 100644
index 0000000..9168c9c
--- /dev/null
+++ b/regen/Images/ImagesNamespace.swift
@@ -0,0 +1,16 @@
+//
+// ImagesNamespace.swift
+// regen
+//
+// Created by Ido Mizrachi on 13/07/2019.
+// Copyright © 2019 Ido Mizrachi. All rights reserved.
+//
+
+import Foundation
+
+struct Images {
+}
+
+extension Images {
+ static let imagesetSuffix = ".imageset"
+}
diff --git a/regen/Images/ImagesOperation.swift b/regen/Images/ImagesOperation.swift
new file mode 100644
index 0000000..503bf27
--- /dev/null
+++ b/regen/Images/ImagesOperation.swift
@@ -0,0 +1,184 @@
+//
+// ImagesOperation.swift
+// Regen
+//
+// Created by Ido Mizrachi on 7/15/16.
+//
+
+import Foundation
+
+extension Images {
+ class Operation {
+
+ let parameters: Parameters
+
+ init(parameters: Parameters) {
+ self.parameters = parameters
+ }
+
+ func run() {
+ let imagesetsFinder = ImagesetsFinder()
+ let imagesetParser = ImagesetParser()
+ var images : [Image] = []
+ let imagesetFiles = imagesetsFinder.find(in: parameters.assetsFile)
+ for imagesetFile in imagesetFiles {
+ if let image = imagesetParser.parse(imagesetFile) {
+ images.append(image)
+ }
+ }
+ let validator = Validator()
+ let issues = validator.validate(images)
+ guard issues.isEmpty else {
+ return
+ }
+ let imagesTree = foldersTree(images: images)
+
+ let result = generate(tree: imagesTree)
+ try! result.data(using: .utf8)!.write(to: URL(fileURLWithPath: parameters.outputFilename))
+// generate()
+//// if validationIssues.count == 0 {
+//// let generator: ImagesClassGenerator
+//// switch self.language {
+//// case .objc:
+//// generator = ImagesClassGeneratorObjC()
+//// case .swift:
+//// generator = ImagesClassGeneratorSwift()
+//// }
+//// generator.generateClass(fromImagesTree: imagesTree, generatedFile: output)
+//// } else {
+//// Logger.error("Issues found:")
+//// for validationIssue in validationIssues {
+//// let firstImage = validationIssue.firstImage
+//// let secondImage = validationIssue.secondImage
+//// Logger.error("\timage \(firstImage) conflicts with \(secondImage) as property \(validationIssue.property)")
+//// }
+//// exit(EXIT_FAILURE)
+//// }
+//// Logger.info("Images scan: Finished")
+ }
+
+ private func generate(root: Tree) {
+// let data = try! JSONEncoder().encode(imagesTree)
+// let array = try! JSONSerialization.jsonObject(with: data, options: []) as! [AnyHashable]
+//
+// let context: [String: AnyHashable] = ["className": parameters.outputClassName, "properties": array]
+//
+// let environment = Environment(loader: FileSystemLoader(paths: [""]))
+// let render = try! environment.renderTemplate(name: parameters.templateFile, context: context)
+// try! render.data(using: .utf8)!.write(to: URL(fileURLWithPath: parameters.outputFilename))
+ }
+
+ /**
+ public struct {{ className }} {
+ {% for folder in folders %} let {{ folder.propertyName }}: {{folder.className }} = {{folder.className }}()
+
+ {% for image in images %} let {{ image.propertyName }}: String = "{{ image.imageSetName }}"
+ }
+
+ */
+ private func generate(tree: Tree) -> String {
+ //flatten the tree
+ //generate code per section
+ //construct a single file
+ let folders: [[String: String]] = tree.children.map {
+ let folder: [String: String] = [
+ "propertyName": $0.item.folder.propertyName(),
+ "className": $0.item.folderClass
+ ]
+ return folder
+ }
+ let images: [[String: String]] = tree.item.images.map {
+ let image: [String: String] = [
+ "propertyName": $0.propertyName,
+ "imageSetName": $0.name
+
+ ]
+ return image
+ }
+ let context: [String: AnyHashable] = [
+ "className": parameters.outputClassName,
+ "folders": folders,
+ "images": images
+ ]
+ let environment = Environment(loader: FileSystemLoader(paths: [""]))
+ let render = try! environment.renderTemplate(name: parameters.templateFile, context: context)
+ let result = String(data: render.data(using: .utf8)!, encoding: .utf8)!
+ var childrenGenerated: String = ""
+ tree.children.forEach {
+ childrenGenerated += generateChild(tree: $0, parent: parameters.outputClassName) + "\n"
+ }
+
+ return childrenGenerated + "\n" + result
+ }
+
+ private func generateChild(tree: TreeNode, parent: String) -> String {
+ let folders: [[String: String]] = tree.children.map {
+ let folder: [String: String] = [
+ "propertyName": $0.item.folder.propertyName(),
+ "className": $0.item.folderClass
+ ]
+ return folder
+ }
+ let images: [[String: String]] = tree.item.images.map {
+ let image: [String: String] = [
+ "propertyName": $0.propertyName,
+ "imageSetName": $0.name
+
+ ]
+ return image
+ }
+ let context: [String: AnyHashable] = [
+ "parent": parent,
+ "className": tree.item.folderClass,
+ "folders": folders,
+ "images": images
+ ]
+ let environment = Environment(loader: FileSystemLoader(paths: [""]))
+ let render = try! environment.renderTemplate(name: parameters.templateFile, context: context)
+ let result = String(data: render.data(using: .utf8)!, encoding: .utf8)!
+
+ var childrenGenerated: String = ""
+ tree.children.forEach {
+ childrenGenerated += generateChild(tree: $0, parent: tree.item.folderClass)
+// childrenGenerated += "\n"
+ }
+ return childrenGenerated + /*"\n\n" +*/ result
+ }
+
+ func foldersTree(images: [Image]) -> Tree {
+ let root = ImageNodeItem(folder: "", folderClass: "")
+ let tree: Tree = Tree(item: root)
+ for image in images {
+ if image.folders.count == 0 {
+ tree.item.images.append(image.file)
+ } else {
+ var node: TreeNode = tree
+ var nextNode: TreeNode? = nil
+ for folder in image.folders {
+ var found = false
+ for child in node.children {
+ if child.item.folder == folder.propertyName {
+ found = true
+ nextNode = child
+ break
+ }
+ }
+ if found == false {
+ let folderClass = folder.propertyName.className()
+ let folderNode: TreeNode = TreeNode(item: ImageNodeItem(folder: folder.propertyName, folderClass: folderClass))
+ node.addChild(folderNode)
+ node = folderNode
+ } else {
+ node = nextNode!
+ }
+ }
+ node.item.images.append(image.file)
+ }
+ }
+
+ return tree
+ }
+ }
+}
+
+
diff --git a/regen/Images/ImagesParametersParser.swift b/regen/Images/ImagesParametersParser.swift
new file mode 100644
index 0000000..7fe9ea6
--- /dev/null
+++ b/regen/Images/ImagesParametersParser.swift
@@ -0,0 +1,29 @@
+//
+// ImagesParametersParser.swift
+// regen
+//
+// Created by Ido Mizrachi on 24/07/2019.
+// Copyright © 2019 Ido Mizrachi. All rights reserved.
+//
+
+import Foundation
+
+class ImagesParametersParser {
+ let arguments: [String]
+
+ init(arguments: [String]) {
+ self.arguments = arguments
+ }
+
+ func parse() -> Images.Parameters? {
+ guard let assetsFile: String = tryParse(.assetsFile, from: arguments) else {
+ return nil
+ }
+ guard let templateFile: String = tryParse(.template, from: arguments) else {
+ return nil
+ }
+ let outputFilename: String = tryParse(.outputFilename, from: arguments) ?? "Localization.swift"
+ let outputClassName: String = tryParse(.outputClassName, from: arguments) ?? "Localization"
+ return Images.Parameters(assetsFile: assetsFile, templateFile: templateFile, outputFilename: outputFilename, outputClassName: outputClassName)
+ }
+}
diff --git a/regen/Images/ImagesValidator.swift b/regen/Images/ImagesValidator.swift
index 9dbb0d6..6374966 100644
--- a/regen/Images/ImagesValidator.swift
+++ b/regen/Images/ImagesValidator.swift
@@ -13,24 +13,27 @@ struct ValidationIssue {
var property : String
}
-class ImagesValidator {
- func validate(_ images : [Image]) -> [ValidationIssue] {
- Logger.debug("\tImages validation: started")
- var issues : [ValidationIssue] = []
- guard images.count > 1 else {
- Logger.debug("\tImages validation: finished")
- return issues
- }
- for i in 0...images.count-2 {
- for j in i+1...images.count-1 {
- if images[i].file.propertyName == images[j].file.propertyName {
- if images[i].folders.last?.propertyName == images[j].folders.last?.propertyName {
- issues.append(ValidationIssue(firstImage: images[i].file.name, secondImage: images[j].file.name, property: images[i].file.name))
+extension Images {
+ class Validator {
+ func validate(_ images : [Image]) -> [ValidationIssue] {
+ // Logger.debug("\tImages validation: started")
+ var issues : [ValidationIssue] = []
+ guard images.count > 1 else {
+ // Logger.debug("\tImages validation: finished")
+ return issues
+ }
+ for i in 0...images.count-2 {
+ for j in i+1...images.count-1 {
+ if images[i].file.propertyName == images[j].file.propertyName {
+ if images[i].folders.last?.propertyName == images[j].folders.last?.propertyName {
+ issues.append(ValidationIssue(firstImage: images[i].file.name, secondImage: images[j].file.name, property: images[i].file.name))
+ }
}
}
}
+ // Logger.debug("\tImages validation: finished (\(issues.count) issue\\s found)")
+ return issues
}
- Logger.debug("\tImages validation: finished (\(issues.count) issue\\s found)")
- return issues
}
+
}
diff --git a/regen/Images/ImagesetParser.swift b/regen/Images/ImagesetParser.swift
index d2aa91f..2c1e7e0 100644
--- a/regen/Images/ImagesetParser.swift
+++ b/regen/Images/ImagesetParser.swift
@@ -7,96 +7,98 @@
import Cocoa
-class ImagesetParser {
- private static func removeAssetsPath(_ imageset: String) -> String? {
- guard let assetsPathRange = imageset.range(of: AssetsFinder.assetsSuffix) else {
- return nil
+extension Images {
+ class ImagesetParser {
+ private static func removeAssetsPath(_ imageset: String) -> String? {
+ guard let assetsPathRange = imageset.range(of: AssetsFinder.assetsSuffix) else {
+ return nil
+ }
+ return String(imageset[imageset.index(assetsPathRange.upperBound, offsetBy: 1)...])
}
- return String(imageset[imageset.index(assetsPathRange.upperBound, offsetBy: 1)...])
- }
-
- private static func removeImagesetSuffix(_ imageset: String) -> String? {
- if imageset.hasSuffix(ImageFinder.imageSuffix) {
- var mutableImageSet = imageset
- mutableImageSet.removeLast(ImageFinder.imageSuffix.count)
- return mutableImageSet
- } else {
- return nil
- }
- }
-
- func parseImage(_ imageset: String) -> Image? {
- guard let relativeImagename = ImagesetParser.removeAssetsPath(imageset) else {
- return nil
- }
- guard let relativeImagenameWithoutSuffix = ImagesetParser.removeImagesetSuffix(relativeImagename) else {
- return nil
- }
- var parts = relativeImagenameWithoutSuffix.split(separator: "/")
- guard parts.last != nil else {
- return nil
- }
- var image = Image()
- let filename = String(parts.last!)
- image.file = Property(name: filename, propertyName: filename.propertyName())
- parts.removeLast()
- for part in parts {
- let folder = String(part)
- image.folders.append(Property(name: folder, propertyName: folder.propertyName()))
- }
-
-
- /*
- var rangeOfFolder = imagename.range(of: "/")
- while rangeOfFolder != nil {
- let folder = imagename[.. = Range(uncheckedBounds: (imagename.startIndex, rangeOfFolder!.upperBound))
- imagename.removeSubrange(rangeFromStart)
- rangeOfFolder = imagename.range(of: "/")
- }*/
-// imagename.removeLast(ImageFinder.imageSuffix.count)
-// metadata.image = imagename
-// metadata.property = imagename.propertyName()
-
-
-
-// guard var imageName = imageAsset.components(separatedBy: "/").last else {
-// return metadata
-// }
-
- /*
- var mutableAsset = assets
- if !assets.hasSuffix("/") {
- mutableAsset = assets + "/"
- }
-
- var imageName = imageAsset
- let range = imageName.range(of: mutableAsset)
- if let range = range {
- imageName.removeSubrange(range)
+
+ private static func removeImagesetSuffix(_ imageset: String) -> String? {
+ if imageset.hasSuffix(Images.imagesetSuffix) {
+ var mutableImageSet = imageset
+ mutableImageSet.removeLast(Images.imagesetSuffix.count)
+ return mutableImageSet
+ } else {
+ return nil
+ }
}
-
- */
-
-
-// if imageName.contains("/") {
+
+ func parse(_ imageset: String) -> Image? {
+ guard let relativeImagename = ImagesetParser.removeAssetsPath(imageset) else {
+ return nil
+ }
+ guard let relativeImagenameWithoutSuffix = ImagesetParser.removeImagesetSuffix(relativeImagename) else {
+ return nil
+ }
+ var parts = relativeImagenameWithoutSuffix.split(separator: "/")
+ guard parts.last != nil else {
+ return nil
+ }
+ var image = Image()
+ let filename = String(parts.last!)
+ image.file = Property(name: filename, propertyName: filename.propertyName())
+ parts.removeLast()
+ for part in parts {
+ let folder = String(part)
+ image.folders.append(Property(name: folder, propertyName: folder.propertyName()))
+ }
+
+
+ /*
+ var rangeOfFolder = imagename.range(of: "/")
+ while rangeOfFolder != nil {
+ let folder = imagename[.. = Range(uncheckedBounds: (imagename.startIndex, rangeOfFolder!.upperBound))
+ imagename.removeSubrange(rangeFromStart)
+ rangeOfFolder = imagename.range(of: "/")
+ }*/
+ // imagename.removeLast(ImageFinder.imageSuffix.count)
+ // metadata.image = imagename
+ // metadata.property = imagename.propertyName()
+
+
+
+ // guard var imageName = imageAsset.components(separatedBy: "/").last else {
+ // return metadata
+ // }
+
/*
- metadata.path = String(imageName[.. Bool {
return lhs.folder == rhs.folder
}
@@ -29,5 +23,10 @@ public class ImageNodeItem: Hashable {
self.folder = folder
self.folderClass = folderClass
self.images = []
- }
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(folder)
+ }
+
}
diff --git a/regen/Images/Model/ImagesParameters.swift b/regen/Images/Model/ImagesParameters.swift
new file mode 100644
index 0000000..4c630f0
--- /dev/null
+++ b/regen/Images/Model/ImagesParameters.swift
@@ -0,0 +1,18 @@
+//
+// ImagesParameters.swift
+// regen
+//
+// Created by Ido Mizrachi on 16/07/2019.
+// Copyright © 2019 Ido Mizrachi. All rights reserved.
+//
+
+import Foundation
+
+extension Images {
+ struct Parameters {
+ let assetsFile: String
+ let templateFile: String
+ let outputFilename: String
+ let outputClassName: String
+ }
+}
diff --git a/regen/Utilities/PropertyName.swift b/regen/Images/PropertyName.swift
similarity index 93%
rename from regen/Utilities/PropertyName.swift
rename to regen/Images/PropertyName.swift
index 29bd173..2c22644 100644
--- a/regen/Utilities/PropertyName.swift
+++ b/regen/Images/PropertyName.swift
@@ -19,7 +19,7 @@ private extension String {
extension String {
func propertyName() -> String {
- if self.characters.count == 0 {
+ if self.isEmpty {
return self
}
var propertyName = ""
@@ -27,12 +27,12 @@ extension String {
var currentToken = String(self[self.index(self.startIndex, offsetBy: 0)])
var previousCharacterType = CharacterType.fromCharacter(self[self.index(self.startIndex, offsetBy: 0)])
var isFirstToken = true
- for i in 1.. [String] {
- let enumerator = fileManager.enumerator(atPath: path)
- var localizationFiles : [String] = []
- Logger.debug("\tSearching localization files: started")
- while let element = enumerator?.nextObject() as? String {
- if element.hasSuffix(LocalizationOperation.localizableStrings) {
- localizationFiles.append(path + "/" + element)
+extension Localization {
+ class Finder {
+
+ let searchPath: String
+ let stringsFilename: String
+
+ init(searchPath: String, stringsFilename: String) {
+ self.searchPath = searchPath
+ self.stringsFilename = stringsFilename
+ }
+
+ func findLocalizationFiles() -> [String] {
+ let enumerator = FileManager.default.enumerator(atPath: searchPath)
+ var localizationFiles : [String] = []
+ while let element = enumerator?.nextObject() as? String {
+ if element.hasSuffix(stringsFilename) {
+ localizationFiles.append(searchPath + "/" + element)
+ }
}
+ return localizationFiles
}
- Logger.debug("\tSearching localization files: finished (\(localizationFiles.count) file\\s found)")
- return localizationFiles
+
}
-
}
diff --git a/regen/Localization/LocalizationNamespace.swift b/regen/Localization/LocalizationNamespace.swift
new file mode 100644
index 0000000..2be1f9d
--- /dev/null
+++ b/regen/Localization/LocalizationNamespace.swift
@@ -0,0 +1,12 @@
+//
+// LocalizationNamespace.swift
+// regen
+//
+// Created by Ido Mizrachi on 12/07/2019.
+// Copyright © 2019 Ido Mizrachi. All rights reserved.
+//
+
+import Foundation
+
+struct Localization {
+}
diff --git a/regen/Localization/LocalizationOperation.swift b/regen/Localization/LocalizationOperation.swift
index 37c228b..e19afcc 100644
--- a/regen/Localization/LocalizationOperation.swift
+++ b/regen/Localization/LocalizationOperation.swift
@@ -7,41 +7,75 @@
import Foundation
-class LocalizationOperation{
-
- static let localizableStrings = "Localizable.strings"
-
- let fileManager : FileManager
- let language: Language
-
- init(fileManager : FileManager, language: Language) {
- self.fileManager = fileManager
- self.language = language
- }
-
- func run(_ searchPath : String, output : String) {
- Logger.info("Localization scan: started")
- let localizationFinder = LocalizationFinder(fileManager: fileManager)
- let files = localizationFinder.findLocalizationFiles(inPath: searchPath)
- let parser = LocalizationParser()
- var localizationEntries : [LocalizationEntry] = []
- Logger.debug("\tParse localization files: started")
- for file in files {
- //Scanning only the english file saves a few seconds
- if file.hasSuffix("en.lproj/Localizable.strings") {
- let parsedFile = parser.parseLocalizationFile(file)
- parser.appendEntries(parsedFile, to: &localizationEntries)
+extension Localization {
+ class Operation {
+
+ let parameters: Parameters
+
+ init(parameters: Parameters) {
+ self.parameters = parameters
+ }
+
+ func run() {
+ let finder = Finder(searchPath: parameters.searchPath, stringsFilename: "Localizable.strings")
+ let files = finder.findLocalizationFiles()
+ let whitelist = parseWhitelist()
+ let parser = Localization.Parser(parameterDetection: parameters.parameterDetection, whitelist: whitelist)
+ guard let baseLanguageFile = files.first(where: { $0.hasSuffix(parameters.baseLanguageCode + ".lproj/Localizable.strings")}) else {
+ //error
+ return
+ }
+ var localizationEntries: [Entry] = []
+ localizationEntries.append(contentsOf: parser.parseLocalizationFile(baseLanguageFile))
+ let duplicates = findDuplicates(localizationEntries: localizationEntries)
+ guard duplicates.isEmpty else {
+ duplicates.forEach { print($0) }
+ return
+ }
+ generate(localizationEntries: localizationEntries)
+
+ }
+
+ private func findDuplicates(localizationEntries: [Entry]) -> [String] {
+ var duplicatesReport: [String] = []
+ var scanner = localizationEntries
+ while !scanner.isEmpty {
+ let entry = scanner.removeFirst()
+ if let duplicate = scanner.first(where: { $0.property == entry.property }) {
+ duplicatesReport.append("Duplicate - key:\(entry.key) value:\(entry.value) property name:\(entry.property)")
+ duplicatesReport.append("With - key:\(duplicate.key) value:\(duplicate.value) property name:\(duplicate.property)")
+ }
}
+ return duplicatesReport
}
- //TODO: Add validator for duplicated properties
- Logger.debug("\tParse localization files: finished (\(localizationEntries.count) keys found)")
- let generator: LocalizationClassGenerator
- if language == .ObjC {
- generator = LocalizationClassGeneratorObjC()
- } else {
- generator = LocalizationClassGeneratorSwift()
+
+ private func generate(localizationEntries: [Entry]) {
+ let data = try! JSONEncoder().encode(localizationEntries)
+ let array = try! JSONSerialization.jsonObject(with: data, options: []) as! [AnyHashable]
+ let context: [String: AnyHashable] = ["className": parameters.outputClassName, "properties": array]
+
+ let environment = Environment(loader: FileSystemLoader(paths: [""]))
+ let render = try! environment.renderTemplate(name: parameters.templateFile, context: context)
+ try! render.data(using: .utf8)!.write(to: URL(fileURLWithPath: parameters.outputFilename))
+ }
+
+ private func parseWhitelist() -> [String]? {
+ guard let whitelistFile = parameters.whitelistFile else {
+ return nil
+ }
+ do {
+ let whitelistString = try String(contentsOfFile: whitelistFile)
+ let whitelistArray = whitelistString.split(separator: "\n").compactMap {
+ return String($0).trimmingCharacters(in: .whitespacesAndNewlines)
+ }
+ return whitelistArray
+ } catch {
+ print("Could not load whitelist from \(whitelistFile)")
+ return nil
+ }
}
- generator.generateClass(fromLocalizationEntries: localizationEntries, generatedFile: output)
- Logger.info("Localization scan: finished")
}
+
}
+
+
diff --git a/regen/Localization/LocalizationParametersParser.swift b/regen/Localization/LocalizationParametersParser.swift
new file mode 100644
index 0000000..e4a0c19
--- /dev/null
+++ b/regen/Localization/LocalizationParametersParser.swift
@@ -0,0 +1,41 @@
+//
+// LocalizationParametersParser.swift
+// regen
+//
+// Created by Ido Mizrachi on 17/07/2019.
+// Copyright © 2019 Ido Mizrachi. All rights reserved.
+//
+
+import Foundation
+
+class LocalizationParametersParser {
+ let arguments: [String]
+
+ init(arguments: [String]) {
+ self.arguments = arguments
+ }
+
+ func parse() -> Localization.Parameters? {
+ guard let searchPath: String = tryParse(.searchPath, from: arguments) else {
+ return nil
+ }
+ guard let templateFile: String = tryParse(.template, from: arguments) else {
+ return nil
+ }
+ let outputFilename: String = tryParse(.outputFilename, from: arguments) ?? "Localization.swift"
+ let outputClassName: String = tryParse(.outputClassName, from: arguments) ?? "Localization"
+ let baseLanguageCode: String = tryParse(.baseLanguageCode, from: arguments) ?? "en"
+ let whitelistFile: String? = tryParse(.whitelist, from: arguments)
+ let parameterDetection: Localization.ParameterDetection?
+ if
+ let startRegex: String = tryParse(.parameterStartRegex, from: arguments),
+ let endRegex: String = tryParse(.parameterEndRegex, from: arguments),
+ let startOffset: Int = tryParse(.parameterStartOffset, from: arguments),
+ let endOffset: Int = tryParse(.parameterEndOffset, from: arguments) {
+ parameterDetection = Localization.ParameterDetection(startRegex: startRegex, endRegex: endRegex, startOffset: startOffset, endOffset: endOffset)
+ } else {
+ parameterDetection = nil
+ }
+ return Localization.Parameters(searchPath: searchPath, templateFile: templateFile, outputFilename: outputFilename, outputClassName: outputClassName, baseLanguageCode: baseLanguageCode, parameterDetection: parameterDetection, whitelistFile: whitelistFile)
+ }
+}
diff --git a/regen/Localization/LocalizationParser.swift b/regen/Localization/LocalizationParser.swift
index 93cc844..00e8211 100644
--- a/regen/Localization/LocalizationParser.swift
+++ b/regen/Localization/LocalizationParser.swift
@@ -7,107 +7,119 @@
import Foundation
-struct LocalizationEntry {
- var path : String
- var key : String
- var value : String
- var property : String
-}
+extension Localization {
-class LocalizationParser {
-
- func parseLocalizationFile(_ file : String) -> [LocalizationEntry] {
- var localizationEntries : [LocalizationEntry] = []
- let content : String
- do {
- Logger.verbose("\t\tLoading localization file: started")
- content = try String(contentsOfFile: file)
- Logger.verbose("\t\tLoading localization file: finished")
- Logger.verbose("\t\tAnalyzing localization file: started \(file)")
- let lines = content.components(separatedBy: "\n")
- for line in lines {
- let keyValue = parseLocalizationLine(line)
- if let key = keyValue.0, let value = keyValue.1 {
- let property = key.propertyName()
- localizationEntries.append(LocalizationEntry(path: file, key: key, value: value, property: property))
- }
- }
- Logger.verbose("\t\tAnalyzing localization file: finished \(file)")
- } catch {
- Logger.error("\(error)")
- content = ""
- }
- return localizationEntries
+ struct KeyValue: Codable {
+ let key: String
+ let value: String
}
-
- func appendEntries(_ from : [LocalizationEntry], to : inout [LocalizationEntry]) {
- for entry in from {
- if LocalizationParser.contains(to, key: entry.key) {
- continue
- }
- to.append(entry)
- }
- }
-
- static func contains(_ entries : [LocalizationEntry], key : String) -> Bool {
- for entry in entries {
- if entry.key == key {
- return true
- }
- }
- return false
+
+ struct Entry: Codable {
+ let path : String
+ let key : String
+ let value : String
+ let property : String
+ let params: [KeyValue]
}
-
- func parseLocalizationLine(_ line: String) -> (String?, String?) {
- var key:String = ""
- var value:String = ""
- let trimmedLine = line.trimmingCharacters(in: CharacterSet.whitespaces)
- guard trimmedLine.hasPrefix("\"") else {
- return (nil, nil)
+
+ class Parser {
+ let parameterDetection: ParameterDetection?
+ let whitelist: [String]?
+
+ init(parameterDetection: ParameterDetection?, whitelist: [String]?) {
+ self.parameterDetection = parameterDetection
+ self.whitelist = whitelist
}
- var keyStarted = false
- var keyFinished = false
- var valueStarted = false
- var valueFinished = false
- var previousCharacter:Character = "\0"
- for character in line.characters {
- if character == "\"" {
- if previousCharacter != "\\" {
- if keyStarted == false && keyFinished == false {
- keyStarted = true
- continue
- }
- if keyStarted == true && keyFinished == false {
- keyFinished = true
- continue
- }
- if keyFinished == true && valueStarted == false {
- valueStarted = true
- continue
- }
- if valueStarted == true && valueFinished == false {
- valueFinished = true
- continue
+
+ func parseLocalizationFile(_ file : String) -> [Entry] {
+ var localizationEntries : [Entry] = []
+ let content : String
+ do {
+ content = try String(contentsOfFile: file)
+ let lines = content.components(separatedBy: "\n")
+ for line in lines {
+ let keyValue = parseLocalizationLine(line)
+ if let key = keyValue.0, let value = keyValue.1 {
+ if let whitelist = self.whitelist {
+ if !whitelist.contains(key) {
+ continue
+ }
+ }
+ let property = key.propertyName()
+ var params: [KeyValue] = []
+ if let parameterDetection = parameterDetection {
+ var range = value.startIndex.. (String?, String?) {
+ var key:String = ""
+ var value:String = ""
+ let trimmedLine = line.trimmingCharacters(in: CharacterSet.whitespaces)
+ guard trimmedLine.hasPrefix("\"") else {
+ return (nil, nil)
}
- if keyStarted == true && keyFinished == false {
- key += String(character)
+ var keyStarted = false
+ var keyFinished = false
+ var valueStarted = false
+ var valueFinished = false
+ var previousCharacter:Character = "\0"
+ line.forEach { character in
+ if character == "\"" {
+ if previousCharacter != "\\" {
+ if keyStarted == false && keyFinished == false {
+ keyStarted = true
+ return
+ }
+ if keyStarted == true && keyFinished == false {
+ keyFinished = true
+ return
+ }
+ if keyFinished == true && valueStarted == false {
+ valueStarted = true
+ return
+ }
+ if valueStarted == true && valueFinished == false {
+ valueFinished = true
+ return
+ }
+ }
+ }
+ if character == "#" && keyStarted == true && keyFinished == false {
+ keyFinished = true
+ return
+ }
+ if keyStarted == true && keyFinished == false {
+ key += String(character)
+ }
+ if valueStarted == true && valueFinished == false {
+ value += String(character)
+ }
+ previousCharacter = character
}
- if valueStarted == true && valueFinished == false {
- value += String(character)
+ if keyStarted == true && keyFinished == true && valueStarted == true && valueFinished == true {
+ return (key, value)
+ } else {
+ return (nil, nil)
}
- previousCharacter = character
- }
- if keyStarted == true && keyFinished == true && valueStarted == true && valueFinished == true {
- return (key, value)
- } else {
- return (nil, nil)
}
+
}
-
}
diff --git a/regen/Localization/Models/LocalizationParameters.swift b/regen/Localization/Models/LocalizationParameters.swift
new file mode 100644
index 0000000..02c187b
--- /dev/null
+++ b/regen/Localization/Models/LocalizationParameters.swift
@@ -0,0 +1,35 @@
+//
+// LocalizationParams.swift
+// regen
+//
+// Created by Ido Mizrachi on 14/07/2019.
+// Copyright © 2019 Ido Mizrachi. All rights reserved.
+//
+
+import Foundation
+
+extension Localization {
+ struct Parameters {
+ let searchPath: String
+ let templateFile: String
+ let outputFilename: String
+ let outputClassName: String
+ let baseLanguageCode: String
+ let parameterDetection: ParameterDetection?
+ let whitelistFile: String?
+ }
+
+ // This is useful if you localization strings contains custom parameters, for example:
+ // "Hello #{user}, you have #{unread_count} unread messages"
+ // In this case:
+ // the start regex will detect #{
+ // the end regex will detect }
+ // the start offset will be 2
+ // the end offset will be 1
+ struct ParameterDetection {
+ let startRegex: String
+ let endRegex: String
+ let startOffset: Int
+ let endOffset: Int
+ }
+}
diff --git a/regen/OperationType.swift b/regen/OperationType.swift
index 763ea75..1840385 100644
--- a/regen/OperationType.swift
+++ b/regen/OperationType.swift
@@ -8,8 +8,19 @@
import Foundation
enum OperationType {
+ // Info
case version
- case images
- case localization
case usage
+ // Actions
+ case images(parameters: Images.Parameters)
+ case localization(parameters: Localization.Parameters)
+}
+
+extension OperationType {
+ enum Keys: String {
+ case version = "--version"
+ case usage
+ case images
+ case localization
+ }
}
diff --git a/regen/Resources/ImagesTemplate-1.swift b/regen/Resources/ImagesTemplate-1.swift
new file mode 100644
index 0000000..f9eeb58
--- /dev/null
+++ b/regen/Resources/ImagesTemplate-1.swift
@@ -0,0 +1,8 @@
+public enum {{ className }} {
+ {% if root %}static let sharedInstance = {{ className }}()
+
+ {% endif %}{% for folder in folders %} let _{{ folder.propertyName }}: {{folder.className }} = {{folder.className }}()
+ {% endfor %}
+ {% for image in images %} let {{ image.propertyName }}: String = "{{ image.imageSetName }}"
+ {% endfor %}
+}
diff --git a/regen/Resources/ImagesTemplate.swift b/regen/Resources/ImagesTemplate.swift
new file mode 100644
index 0000000..829f677
--- /dev/null
+++ b/regen/Resources/ImagesTemplate.swift
@@ -0,0 +1,12 @@
+{% if parent %}
+extension {{ parent }} {
+ enum {{ className }} {
+ {% for image in images %} static let {{ image.propertyName }}: String = "{{ image.imageSetName }}"
+ {% endfor %}}
+}
+{% else %}
+enum {{ className }} {
+{% for image in images %} static let {{ image.propertyName }}: String = "{{ image.imageSetName }}"
+{% endfor %}}
+{% endif %}
+
diff --git a/regen/Resources/LocalizationTemplate.swift b/regen/Resources/LocalizationTemplate.swift
new file mode 100644
index 0000000..ac4738b
--- /dev/null
+++ b/regen/Resources/LocalizationTemplate.swift
@@ -0,0 +1,5 @@
+import Foundation
+
+public struct {{ className }} {
+{% for property in properties %} static let {{ property.property }} = NSLocalizedString("{{ property.key }}", comment: "{{ property.value }}")
+{% endfor %}}
diff --git a/regen/Resources/ParametersLocalizationTemplate.swift b/regen/Resources/ParametersLocalizationTemplate.swift
new file mode 100644
index 0000000..243f067
--- /dev/null
+++ b/regen/Resources/ParametersLocalizationTemplate.swift
@@ -0,0 +1,13 @@
+import Foundation
+
+public struct {{ className }} {
+{% for property in properties %}
+ static func {{ property.property }}({% for param in property.params %}{{ param.key }}: String{% if forloop.last == false %}, {% endif %}{% endfor %}) {
+ return localize("{{ property.key }}",
+ defaultValue: "{{ property.value }}",
+ parameters: {% if property.params.count == 0 %}nil{% else %} [{% for param in property.params %}"{{ param.value }}": {{ param.key }}{% if forloop.last == false %}, {% endif %}{% empty %}:{% endfor %}]{% endif %})
+ }
+{% endfor %}
+}
+
+
diff --git a/regen/Resources/whitelist.txt b/regen/Resources/whitelist.txt
new file mode 100644
index 0000000..51c8da9
--- /dev/null
+++ b/regen/Resources/whitelist.txt
@@ -0,0 +1,173 @@
+error_showing_qr_scanner_title
+welcome_to_title
+driver_app_name
+login_view_login_with_email_and_password
+login_version_label
+prompt_password
+prompt_email
+prompt_phone
+hint_verification_code
+forgot_password
+back_to_login_button_title
+next_button_title
+action_sign_in_short
+send_login_code_via_sms_button_title
+error
+OK
+login_view_select_merchant_header_title
+login_view_recover_password_screen_header
+alternative_login_phone
+alternative_login_qrcode
+login_view_enter_sms_verification_code
+choose_country_screen_title
+cancel
+recovery_email_sent_alert_title
+recovery_email_sent_alert_message
+change_phone_number
+wrong_credentials_message
+error_login_not_a_driver
+failed_to_login_message
+refresh_hint
+drawer_my_tasks
+drawer_future_tasks
+order_by_time
+order_by_priority
+order_by_distance
+unknown
+yesterday
+days_ago
+empty_task_list_message
+failedLoadingOrderErrorMessage
+waypoint_view_scheduled_to_title
+waypoint_view_promised_time_title
+eta_title
+contact
+title_order_with_id
+inventory_collect_label
+inventory_deliver_label
+inventory_quantity
+inventory_item_title
+inventory_quantity_title
+inventory_pickup_count_title
+inventory_dropoff_count_title
+no_internet_connection
+wifi_not_connected_to_internet
+gps_off_message_ios
+payments_history_screen_title
+payments_history_section_header_payments
+payments_history_section_header_blocks_and_orders
+payments_history_deliveries
+payments_history_deliveries_rate
+payments_history_total_paid_label
+payments_history_tab_daily
+payments_history_tab_weekly
+payments_history_tab_monthly
+payments_history_miles
+payments_history_miles_rate
+payments_history_kilometers
+payments_history_kilometers_rate
+payments_history_returned_orders
+payments_history_returned_orders_rate
+payments_history_hours
+payments_history_hours_rate
+payments_history_minutes
+payments_history_minutes_rate
+payments_history_tips_label
+payments_history_orders_offered_label
+payments_history_orders_accepted_label
+payments_history_delivery_blocks_label
+payments_history_try_again
+payments_history_error_title
+payments_history_error_subtitle
+order_completed_label
+waypoint_completed
+pending_button
+checkin_button
+checkout_button_on_route_mode
+checkout_button
+accept_button
+start_button
+start_button_pick_up
+start_button_drop_off
+checkout_button_pick_up
+checkout_button_drop_off
+error_accepting_task
+error_arriving_at_waypoint
+error_locally_starting_task
+error_checking_out_from_waypoint
+sidebar_app_subtitle
+sidebar_version_label
+sidebar_option_title_call_dispatch
+sidebar_option_title_work_schedule
+action_bar_inbox
+sidebar_option_title_add_an_order
+sidebar_option_title_scan_an_item
+sidebar_option_title_role
+sidebar_option_title_routes
+sidebar_option_title_order_history
+sidebar_option_title_options
+drawer_logout
+shift_status_label_on_shift_state_title
+shift_status_label_off_shift_state_title
+shortcut_end_shift
+shortcut_start_shift
+start_shift
+slide_to_start_shift
+side_menu_on_shift
+starting_shift
+another_device_error
+another_device_button
+stop_index_and_total_count
+incoming_text_title_no_params
+incoming_task_button_accept
+not_now
+got_it_text
+canceled_task_title
+single_order_pending_accept
+few_orders_pending_accept
+todays_orders
+sort_orders_by_time
+sort_orders_by_location
+sort_orders_by_priority
+address_type_commercial
+address_type_residential
+address_type_educational
+address_type_government
+address_type_medical
+address_type_industrial
+messaging_center_title
+retry_button_label_text
+failed_to_load_conversations
+conversation_list_driver_to_team_dispatchers_title
+conversation_view_failed_loading_messages
+failed_to_load_conversation
+conversation_view_start_shift_to_send_and_receive_message
+conversation_view_connection_label_text
+conversation_message_cell_sending_status
+conversation_message_cell_failed_sending_status
+label_vehicle
+vehicle_selection_title
+vehicle_fetch_failed
+vehicle_update_failed
+start_shift_form_title
+clear_signature_button_label
+action_add_signature
+drawer_feedback
+drawer_about
+action_add_photo
+action_add_note
+type_your_note
+feedback_write_note_view_placeholder
+action_add_form
+action_add_payment
+collect_cash
+action_edit_inventory
+action_reject_inventory
+cancel_task_title
+take_scan_button
+take_tip_button
+browser_task_action_default_title
+order_removed_by_dispatcher_alert_title
+dismiss
+orders_list_failed_loading_orders_message
+error_mandatory_actions_not_taken_dialog
diff --git a/regen/Usage.swift b/regen/Usage.swift
index 632246b..e83116b 100644
--- a/regen/Usage.swift
+++ b/regen/Usage.swift
@@ -7,40 +7,50 @@
import Foundation
+
+// Usage:
+// regen supports two type of code generation:
+// 1. Images - by scanning ".xcassets" packages
+// 2. Localization - by scanning string files
+//
+// Type any of the following for additiona help:
+// regen images --help
+// regen localization --help
+
class Usage {
- static func display(color: Bool) {
- let options = [ ["--version ", "Prints the current version"],
- ["--output FILE ", "Set the generated file name (without extension)"] ,
- ["--scanType TYPE ", "Use images or localization\n\t\timages - scans the projects .xcassets files\n\t\tlocalization - scans the projects Localizable.strings file"],
- ["--language LANGUAGE", "Use swift or objc\n\t\tSets the language of the generated filess"],
- ["--verbose or -v ", "Print detailed information while running"],
- ["--nocolor ", "Don't use colors in console output"]
- ]
- var usageHeader: String = "Usage:"
- if color {
- usageHeader = usageHeader.bold.underline
- }
-
- print(usageHeader)
- levelPrint("$ regen [options]")
- newLine()
-
- var optionsHeader: String = "Options"
- if color {
- optionsHeader = optionsHeader.bold.underline
- }
- print(optionsHeader)
- for (option) in options {
- levelPrint("\(option[0]) \t\t\t \(option[1])")
- }
- }
-
- private static func levelPrint(_ string : String) {
- print("\t" + string)
- }
-
- private static func newLine() {
- print("")
+
+ static func display() {
+ let usage = #"""
+ Usage:
+ $ regen localization [options]
+ or
+ $ regen images [options]
+ or
+
+ General:
+ --version Prints the current tool version
+
+ Localization Parameters:
+ --search-path The path of the .strings files, for example: /Users/me/dev/project
+ --template Stencil template file, for example: Template.txt
+ --output-filename The output filename, for example: Localization.swift, default value: Localization.swift
+ --output-class-name The generated class name, for example: LocalizableStrings, default value: Localization
+ --base-language-code The locate of the .strings file that will be scanned, default value: en
+ --whitelist-filename If specified, the file with a list of strings keys to include in the generated file
+ Handling Localization Parameters: "hello %d" / "Welcome #{user}"
+ --parameter-start-regex The regex for the beginning of the parameters, in the above cases a regex for % or #{
+ --parameter-end-regex The regex for the beginning of the parameters, in the above cases a regex for d or }
+ --parameter-start-offset The number of characters to skip when a parameter for example to extract only "user" from #{users} skip 2 characters from the start
+ --parameter-end-offset The number of characters to skip when a parameter for example to extract only "user" from #{users} skip 1 character from the end
+
+ Images Parameters:
+ --assets The .xcassets file path
+ --template Stencil template file, for example: Template.txt
+ --output-filename The output filename, for example: Localization.swift, default value: Localization.swift
+ --output-class-name The generated class name, for example: LocalizableStrings, default value: Localization
+ """#
+
+
+ print(usage)
}
-
}
diff --git a/regen/Version.swift b/regen/Version.swift
index 998b3ef..310ce5b 100644
--- a/regen/Version.swift
+++ b/regen/Version.swift
@@ -8,7 +8,7 @@
import Cocoa
class Version {
- static let current = "0.0.9"
+ static let current = "0.0.10"
static func display() {
print(Version.current)
diff --git a/regen/main.swift b/regen/main.swift
index 8b2d60f..836a831 100644
--- a/regen/main.swift
+++ b/regen/main.swift
@@ -1,4 +1,4 @@
-//
+//
// main.swift
// Regen
//
@@ -7,72 +7,26 @@
import Foundation
-class Main {
-
- private let fileManager: FileManager = FileManager.default
-
- public func run() {
- var skipOperationTimePrint:Bool = false
- let operationTimer = OperationTimer()
- operationTimer.start()
-
- let argumentsParser = ArgumentsParser(arguments: CommandLine.arguments)
- let operationType = argumentsParser.operationType()
- let path = fileManager.currentDirectoryPath
-
- Logger.logLevel = argumentsParser.verbose ? .verbose : .info;
- Logger.color = argumentsParser.color
-
- switch operationType {
- case .version:
- skipOperationTimePrint = true
- Version.display()
-
- case .images:
- self.runImageOperation(inPath: path, language: argumentsParser.language, nullableOutputFile: argumentsParser.output)
-
- case .localization:
- self.runLocalizationOperation(inPath: path, language: argumentsParser.language, nullableOutputFile: argumentsParser.output)
-
- default:
- skipOperationTimePrint = true
- Usage.display(color: argumentsParser.color)
- }
-
- if skipOperationTimePrint == false {
- let totalTime = String(format: "%.2f", operationTimer.end())
- Logger.info("Finish in \(totalTime) seconds")
- }
-
- exit(EXIT_SUCCESS)
- }
-
- private func runImageOperation(inPath: String, language: Language, nullableOutputFile: String?) {
- Logger.info("Searching for images in path: \(inPath)")
- let imageOperation = ImageOperation(fileManager: fileManager, language: language)
- let outputFile: String
- if let nonNilOutputFile = nullableOutputFile {
- outputFile = nonNilOutputFile
- } else {
- outputFile = "Images"
- }
- imageOperation.run(inPath, output: fileManager.currentDirectoryPath + "/" + outputFile)
- }
-
- private func runLocalizationOperation(inPath: String, language: Language, nullableOutputFile: String?) {
- Logger.info("Searching for localization files in path: \(inPath)")
- let localizationOperation = LocalizationOperation(fileManager: fileManager, language: language)
- let outputFile: String
- if let nonNilOutputFile = nullableOutputFile {
- outputFile = nonNilOutputFile
- } else {
- outputFile = "Strings"
- }
- localizationOperation.run(inPath, output: fileManager.currentDirectoryPath + "/" + outputFile)
-
- }
+let operationTimer = OperationTimer()
+operationTimer.start()
+
+let arguments = Array(CommandLine.arguments.dropFirst())
+
+let argumentsParser = ArgumentsParser(arguments: arguments)
+switch argumentsParser.operationType {
+case .version:
+ Version.display()
+case .usage:
+ Usage.display()
+case .localization(let parameters):
+ let operation = Localization.Operation(parameters: parameters)
+ operation.run()
+ print("Finished in: \(String(format: "%.5f", operationTimer.end())) seconds.")
+case .images(let parameters):
+ let operation = Images.Operation(parameters: parameters)
+ operation.run()
+ print("Finished in: \(String(format: "%.5f", operationTimer.end())) seconds.")
}
-let main = Main()
-main.run()
+