├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Assets └── logo.png ├── ConfigenDemo ├── AppEnvironment.map ├── AppEnvironment │ └── AppEnvironment.swift ├── ConfigenDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── johnsanderson.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── johnsanderson.xcuserdatad │ │ └── xcschemes │ │ ├── ConfigenDemo-Prod.xcscheme │ │ ├── ConfigenDemo-Test.xcscheme │ │ ├── ConfigenDemo.xcscheme │ │ ├── prod.xcscheme │ │ ├── test.xcscheme │ │ └── xcschememanagement.plist ├── ConfigenDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Environment.swift │ ├── Info.plist │ └── ViewController.swift ├── configen ├── prod-config.plist └── test-config.plist ├── LICENSE ├── README.md ├── configen.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── configen ├── ArrayUtils.swift ├── CommandLineKit ├── CommandLineKit.swift ├── Info.plist ├── Option.swift └── StringExtensions.swift ├── Extensions └── Character+Extensions.swift ├── FileGenerator.swift ├── FileTemplates.swift ├── OptionsParser.swift └── main.swift /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Configen CI 2 | 3 | on: 4 | pull_request: 5 | branches: [ '*' ] # trigger the CI on all PR request. 6 | 7 | jobs: 8 | build: 9 | runs-on: macOS-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v1 14 | - name: Building configen for the latest version of macOS and Xcode 15 | run: xcodebuild build -scheme configen -configuration Debug 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /Assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theappbusiness/ConfigGenerator/0605fb9ad54962a7193f1b9257a3ac643bf6c2e8/Assets/logo.png -------------------------------------------------------------------------------- /ConfigenDemo/AppEnvironment.map: -------------------------------------------------------------------------------- 1 | entryPointURL : URL 2 | enableFileSharing : Bool 3 | retryCount : Int 4 | adUnitPrefix : String 5 | analyticsKey : String 6 | environment : Environment 7 | -------------------------------------------------------------------------------- /ConfigenDemo/AppEnvironment/AppEnvironment.swift: -------------------------------------------------------------------------------- 1 | // auto-generated by configen 2 | // to add or remove properties, edit the mapping file: '/Users/johnsanderson/Documents/ConfigGenerator/ConfigenDemo/AppEnvironment.map'. 3 | // README: https://github.com/theappbusiness/ConfigGenerator/blob/main/README.md 4 | 5 | import Foundation 6 | 7 | class AppEnvironment { 8 | 9 | static let entryPointURL: URL = URL(string: "http://example.com/production")! 10 | 11 | static let enableFileSharing: Bool = true 12 | 13 | static let analyticsKey: String = "haf6d9fha8v56abs" 14 | 15 | static let environment: Environment = .production 16 | 17 | static let adUnitPrefix: String = "production_ad_unit" 18 | 19 | static let retryCount: Int = 4 20 | 21 | } 22 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 914E422B1E1FC3FF00F0AB05 /* prod-config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 914E42291E1FC3FF00F0AB05 /* prod-config.plist */; }; 11 | 914E422C1E1FC3FF00F0AB05 /* test-config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 914E422A1E1FC3FF00F0AB05 /* test-config.plist */; }; 12 | 914E422F1E1FC8A800F0AB05 /* AppEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914E422E1E1FC8A800F0AB05 /* AppEnvironment.swift */; }; 13 | 914E42311E1FF3C500F0AB05 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 914E42301E1FF3C500F0AB05 /* Environment.swift */; }; 14 | 91CF77C01E1FB178009FD04D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CF77BF1E1FB178009FD04D /* AppDelegate.swift */; }; 15 | 91CF77C21E1FB178009FD04D /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91CF77C11E1FB178009FD04D /* ViewController.swift */; }; 16 | 91CF77C51E1FB178009FD04D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 91CF77C31E1FB178009FD04D /* Main.storyboard */; }; 17 | 91CF77C71E1FB178009FD04D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 91CF77C61E1FB178009FD04D /* Assets.xcassets */; }; 18 | 91CF77CA1E1FB178009FD04D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 91CF77C81E1FB178009FD04D /* LaunchScreen.storyboard */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 914E42291E1FC3FF00F0AB05 /* prod-config.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "prod-config.plist"; sourceTree = SOURCE_ROOT; }; 23 | 914E422A1E1FC3FF00F0AB05 /* test-config.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "test-config.plist"; sourceTree = SOURCE_ROOT; }; 24 | 914E422D1E1FC7EB00F0AB05 /* AppEnvironment.map */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = AppEnvironment.map; sourceTree = SOURCE_ROOT; }; 25 | 914E422E1E1FC8A800F0AB05 /* AppEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; name = AppEnvironment.swift; path = AppEnvironment/AppEnvironment.swift; sourceTree = SOURCE_ROOT; tabWidth = 2; }; 26 | 914E42301E1FF3C500F0AB05 /* Environment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; 27 | 91CF77BC1E1FB178009FD04D /* ConfigenDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ConfigenDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | 91CF77BF1E1FB178009FD04D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 29 | 91CF77C11E1FB178009FD04D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 30 | 91CF77C41E1FB178009FD04D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 31 | 91CF77C61E1FB178009FD04D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 32 | 91CF77C91E1FB178009FD04D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 33 | 91CF77CB1E1FB178009FD04D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | 91CF77B91E1FB178009FD04D /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | 91CF77B31E1FB178009FD04D = { 48 | isa = PBXGroup; 49 | children = ( 50 | 91CF77BE1E1FB178009FD04D /* ConfigenDemo */, 51 | 91CF77BD1E1FB178009FD04D /* Products */, 52 | ); 53 | sourceTree = ""; 54 | }; 55 | 91CF77BD1E1FB178009FD04D /* Products */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 91CF77BC1E1FB178009FD04D /* ConfigenDemo.app */, 59 | ); 60 | name = Products; 61 | sourceTree = ""; 62 | }; 63 | 91CF77BE1E1FB178009FD04D /* ConfigenDemo */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 91CF77D21E1FB3F3009FD04D /* AppEnvironment */, 67 | 914E42301E1FF3C500F0AB05 /* Environment.swift */, 68 | 91CF77BF1E1FB178009FD04D /* AppDelegate.swift */, 69 | 91CF77C11E1FB178009FD04D /* ViewController.swift */, 70 | 91CF77C31E1FB178009FD04D /* Main.storyboard */, 71 | 91CF77C61E1FB178009FD04D /* Assets.xcassets */, 72 | 91CF77C81E1FB178009FD04D /* LaunchScreen.storyboard */, 73 | 91CF77CB1E1FB178009FD04D /* Info.plist */, 74 | ); 75 | path = ConfigenDemo; 76 | sourceTree = ""; 77 | }; 78 | 91CF77D21E1FB3F3009FD04D /* AppEnvironment */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 914E422E1E1FC8A800F0AB05 /* AppEnvironment.swift */, 82 | 914E422D1E1FC7EB00F0AB05 /* AppEnvironment.map */, 83 | 914E42291E1FC3FF00F0AB05 /* prod-config.plist */, 84 | 914E422A1E1FC3FF00F0AB05 /* test-config.plist */, 85 | ); 86 | name = AppEnvironment; 87 | sourceTree = ""; 88 | }; 89 | /* End PBXGroup section */ 90 | 91 | /* Begin PBXLegacyTarget section */ 92 | 91CF77DA1E1FB702009FD04D /* prod */ = { 93 | isa = PBXLegacyTarget; 94 | buildArgumentsString = "-p ./prod-config.plist -h ./AppEnvironment.map -n AppEnvironment -o ./AppEnvironment"; 95 | buildConfigurationList = 91CF77DB1E1FB702009FD04D /* Build configuration list for PBXLegacyTarget "prod" */; 96 | buildPhases = ( 97 | ); 98 | buildToolPath = ./configen; 99 | buildWorkingDirectory = ""; 100 | dependencies = ( 101 | ); 102 | name = prod; 103 | passBuildSettingsInEnvironment = 1; 104 | productName = prod; 105 | }; 106 | 91CF77DE1E1FB75E009FD04D /* test */ = { 107 | isa = PBXLegacyTarget; 108 | buildArgumentsString = "-p ./test-config.plist -h ./AppEnvironment.map -n AppEnvironment -o ./AppEnvironment"; 109 | buildConfigurationList = 91CF77DF1E1FB75E009FD04D /* Build configuration list for PBXLegacyTarget "test" */; 110 | buildPhases = ( 111 | ); 112 | buildToolPath = ./configen; 113 | buildWorkingDirectory = ""; 114 | dependencies = ( 115 | ); 116 | name = test; 117 | passBuildSettingsInEnvironment = 1; 118 | productName = test; 119 | }; 120 | /* End PBXLegacyTarget section */ 121 | 122 | /* Begin PBXNativeTarget section */ 123 | 91CF77BB1E1FB178009FD04D /* ConfigenDemo */ = { 124 | isa = PBXNativeTarget; 125 | buildConfigurationList = 91CF77CE1E1FB178009FD04D /* Build configuration list for PBXNativeTarget "ConfigenDemo" */; 126 | buildPhases = ( 127 | 91CF77B81E1FB178009FD04D /* Sources */, 128 | 91CF77B91E1FB178009FD04D /* Frameworks */, 129 | 91CF77BA1E1FB178009FD04D /* Resources */, 130 | ); 131 | buildRules = ( 132 | ); 133 | dependencies = ( 134 | ); 135 | name = ConfigenDemo; 136 | productName = ConfigenDemo; 137 | productReference = 91CF77BC1E1FB178009FD04D /* ConfigenDemo.app */; 138 | productType = "com.apple.product-type.application"; 139 | }; 140 | /* End PBXNativeTarget section */ 141 | 142 | /* Begin PBXProject section */ 143 | 91CF77B41E1FB178009FD04D /* Project object */ = { 144 | isa = PBXProject; 145 | attributes = { 146 | LastSwiftUpdateCheck = 0820; 147 | LastUpgradeCheck = 1000; 148 | ORGANIZATIONNAME = "John Sanderson"; 149 | TargetAttributes = { 150 | 91CF77BB1E1FB178009FD04D = { 151 | CreatedOnToolsVersion = 8.2.1; 152 | LastSwiftMigration = 1000; 153 | ProvisioningStyle = Automatic; 154 | }; 155 | 91CF77DA1E1FB702009FD04D = { 156 | CreatedOnToolsVersion = 8.2.1; 157 | DevelopmentTeam = VG9R2N57GC; 158 | ProvisioningStyle = Automatic; 159 | }; 160 | 91CF77DE1E1FB75E009FD04D = { 161 | CreatedOnToolsVersion = 8.2.1; 162 | DevelopmentTeam = VG9R2N57GC; 163 | ProvisioningStyle = Automatic; 164 | }; 165 | }; 166 | }; 167 | buildConfigurationList = 91CF77B71E1FB178009FD04D /* Build configuration list for PBXProject "ConfigenDemo" */; 168 | compatibilityVersion = "Xcode 3.2"; 169 | developmentRegion = English; 170 | hasScannedForEncodings = 0; 171 | knownRegions = ( 172 | en, 173 | Base, 174 | ); 175 | mainGroup = 91CF77B31E1FB178009FD04D; 176 | productRefGroup = 91CF77BD1E1FB178009FD04D /* Products */; 177 | projectDirPath = ""; 178 | projectRoot = ""; 179 | targets = ( 180 | 91CF77BB1E1FB178009FD04D /* ConfigenDemo */, 181 | 91CF77DA1E1FB702009FD04D /* prod */, 182 | 91CF77DE1E1FB75E009FD04D /* test */, 183 | ); 184 | }; 185 | /* End PBXProject section */ 186 | 187 | /* Begin PBXResourcesBuildPhase section */ 188 | 91CF77BA1E1FB178009FD04D /* Resources */ = { 189 | isa = PBXResourcesBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | 91CF77CA1E1FB178009FD04D /* LaunchScreen.storyboard in Resources */, 193 | 91CF77C71E1FB178009FD04D /* Assets.xcassets in Resources */, 194 | 914E422C1E1FC3FF00F0AB05 /* test-config.plist in Resources */, 195 | 91CF77C51E1FB178009FD04D /* Main.storyboard in Resources */, 196 | 914E422B1E1FC3FF00F0AB05 /* prod-config.plist in Resources */, 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | /* End PBXResourcesBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 91CF77B81E1FB178009FD04D /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 91CF77C21E1FB178009FD04D /* ViewController.swift in Sources */, 208 | 914E42311E1FF3C500F0AB05 /* Environment.swift in Sources */, 209 | 91CF77C01E1FB178009FD04D /* AppDelegate.swift in Sources */, 210 | 914E422F1E1FC8A800F0AB05 /* AppEnvironment.swift in Sources */, 211 | ); 212 | runOnlyForDeploymentPostprocessing = 0; 213 | }; 214 | /* End PBXSourcesBuildPhase section */ 215 | 216 | /* Begin PBXVariantGroup section */ 217 | 91CF77C31E1FB178009FD04D /* Main.storyboard */ = { 218 | isa = PBXVariantGroup; 219 | children = ( 220 | 91CF77C41E1FB178009FD04D /* Base */, 221 | ); 222 | name = Main.storyboard; 223 | sourceTree = ""; 224 | }; 225 | 91CF77C81E1FB178009FD04D /* LaunchScreen.storyboard */ = { 226 | isa = PBXVariantGroup; 227 | children = ( 228 | 91CF77C91E1FB178009FD04D /* Base */, 229 | ); 230 | name = LaunchScreen.storyboard; 231 | sourceTree = ""; 232 | }; 233 | /* End PBXVariantGroup section */ 234 | 235 | /* Begin XCBuildConfiguration section */ 236 | 91CF77CC1E1FB178009FD04D /* Debug */ = { 237 | isa = XCBuildConfiguration; 238 | buildSettings = { 239 | ALWAYS_SEARCH_USER_PATHS = NO; 240 | CLANG_ANALYZER_NONNULL = YES; 241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 242 | CLANG_CXX_LIBRARY = "libc++"; 243 | CLANG_ENABLE_MODULES = YES; 244 | CLANG_ENABLE_OBJC_ARC = YES; 245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 246 | CLANG_WARN_BOOL_CONVERSION = YES; 247 | CLANG_WARN_COMMA = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 251 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 252 | CLANG_WARN_EMPTY_BODY = YES; 253 | CLANG_WARN_ENUM_CONVERSION = YES; 254 | CLANG_WARN_INFINITE_RECURSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 257 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 258 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 259 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 260 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 261 | CLANG_WARN_STRICT_PROTOTYPES = YES; 262 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 263 | CLANG_WARN_UNREACHABLE_CODE = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 266 | COPY_PHASE_STRIP = NO; 267 | DEBUG_INFORMATION_FORMAT = dwarf; 268 | ENABLE_STRICT_OBJC_MSGSEND = YES; 269 | ENABLE_TESTABILITY = YES; 270 | GCC_C_LANGUAGE_STANDARD = gnu99; 271 | GCC_DYNAMIC_NO_PIC = NO; 272 | GCC_NO_COMMON_BLOCKS = YES; 273 | GCC_OPTIMIZATION_LEVEL = 0; 274 | GCC_PREPROCESSOR_DEFINITIONS = ( 275 | "DEBUG=1", 276 | "$(inherited)", 277 | ); 278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 280 | GCC_WARN_UNDECLARED_SELECTOR = YES; 281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 282 | GCC_WARN_UNUSED_FUNCTION = YES; 283 | GCC_WARN_UNUSED_VARIABLE = YES; 284 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 285 | MTL_ENABLE_DEBUG_INFO = YES; 286 | ONLY_ACTIVE_ARCH = YES; 287 | SDKROOT = iphoneos; 288 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 289 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 290 | TARGETED_DEVICE_FAMILY = "1,2"; 291 | }; 292 | name = Debug; 293 | }; 294 | 91CF77CD1E1FB178009FD04D /* Release */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ALWAYS_SEARCH_USER_PATHS = NO; 298 | CLANG_ANALYZER_NONNULL = YES; 299 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 300 | CLANG_CXX_LIBRARY = "libc++"; 301 | CLANG_ENABLE_MODULES = YES; 302 | CLANG_ENABLE_OBJC_ARC = YES; 303 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 304 | CLANG_WARN_BOOL_CONVERSION = YES; 305 | CLANG_WARN_COMMA = YES; 306 | CLANG_WARN_CONSTANT_CONVERSION = YES; 307 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 308 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 309 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 310 | CLANG_WARN_EMPTY_BODY = YES; 311 | CLANG_WARN_ENUM_CONVERSION = YES; 312 | CLANG_WARN_INFINITE_RECURSION = YES; 313 | CLANG_WARN_INT_CONVERSION = YES; 314 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 315 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 316 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 317 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 318 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 319 | CLANG_WARN_STRICT_PROTOTYPES = YES; 320 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 321 | CLANG_WARN_UNREACHABLE_CODE = YES; 322 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 323 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 324 | COPY_PHASE_STRIP = NO; 325 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 326 | ENABLE_NS_ASSERTIONS = NO; 327 | ENABLE_STRICT_OBJC_MSGSEND = YES; 328 | GCC_C_LANGUAGE_STANDARD = gnu99; 329 | GCC_NO_COMMON_BLOCKS = YES; 330 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 331 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 332 | GCC_WARN_UNDECLARED_SELECTOR = YES; 333 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 334 | GCC_WARN_UNUSED_FUNCTION = YES; 335 | GCC_WARN_UNUSED_VARIABLE = YES; 336 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 337 | MTL_ENABLE_DEBUG_INFO = NO; 338 | SDKROOT = iphoneos; 339 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 340 | TARGETED_DEVICE_FAMILY = "1,2"; 341 | VALIDATE_PRODUCT = YES; 342 | }; 343 | name = Release; 344 | }; 345 | 91CF77CF1E1FB178009FD04D /* Debug */ = { 346 | isa = XCBuildConfiguration; 347 | buildSettings = { 348 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 349 | DEVELOPMENT_TEAM = ""; 350 | INFOPLIST_FILE = ConfigenDemo/Info.plist; 351 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 352 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 353 | PRODUCT_BUNDLE_IDENTIFIER = TAB.ConfigenDemo; 354 | PRODUCT_NAME = "$(TARGET_NAME)"; 355 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 356 | SWIFT_VERSION = 4.2; 357 | }; 358 | name = Debug; 359 | }; 360 | 91CF77D01E1FB178009FD04D /* Release */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 364 | DEVELOPMENT_TEAM = ""; 365 | INFOPLIST_FILE = ConfigenDemo/Info.plist; 366 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 367 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 368 | PRODUCT_BUNDLE_IDENTIFIER = TAB.ConfigenDemo; 369 | PRODUCT_NAME = "$(TARGET_NAME)"; 370 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 371 | SWIFT_VERSION = 4.2; 372 | }; 373 | name = Release; 374 | }; 375 | 91CF77DC1E1FB702009FD04D /* Debug */ = { 376 | isa = XCBuildConfiguration; 377 | buildSettings = { 378 | DEBUGGING_SYMBOLS = YES; 379 | DEBUG_INFORMATION_FORMAT = dwarf; 380 | DEVELOPMENT_TEAM = VG9R2N57GC; 381 | GCC_GENERATE_DEBUGGING_SYMBOLS = YES; 382 | GCC_OPTIMIZATION_LEVEL = 0; 383 | OTHER_CFLAGS = ""; 384 | OTHER_LDFLAGS = ""; 385 | PRODUCT_NAME = "$(TARGET_NAME)"; 386 | }; 387 | name = Debug; 388 | }; 389 | 91CF77DD1E1FB702009FD04D /* Release */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 393 | DEVELOPMENT_TEAM = VG9R2N57GC; 394 | OTHER_CFLAGS = ""; 395 | OTHER_LDFLAGS = ""; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | }; 398 | name = Release; 399 | }; 400 | 91CF77E01E1FB75E009FD04D /* Debug */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | DEBUGGING_SYMBOLS = YES; 404 | DEBUG_INFORMATION_FORMAT = dwarf; 405 | DEVELOPMENT_TEAM = VG9R2N57GC; 406 | GCC_GENERATE_DEBUGGING_SYMBOLS = YES; 407 | GCC_OPTIMIZATION_LEVEL = 0; 408 | OTHER_CFLAGS = ""; 409 | OTHER_LDFLAGS = ""; 410 | PRODUCT_NAME = "$(TARGET_NAME)"; 411 | }; 412 | name = Debug; 413 | }; 414 | 91CF77E11E1FB75E009FD04D /* Release */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 418 | DEVELOPMENT_TEAM = VG9R2N57GC; 419 | OTHER_CFLAGS = ""; 420 | OTHER_LDFLAGS = ""; 421 | PRODUCT_NAME = "$(TARGET_NAME)"; 422 | }; 423 | name = Release; 424 | }; 425 | /* End XCBuildConfiguration section */ 426 | 427 | /* Begin XCConfigurationList section */ 428 | 91CF77B71E1FB178009FD04D /* Build configuration list for PBXProject "ConfigenDemo" */ = { 429 | isa = XCConfigurationList; 430 | buildConfigurations = ( 431 | 91CF77CC1E1FB178009FD04D /* Debug */, 432 | 91CF77CD1E1FB178009FD04D /* Release */, 433 | ); 434 | defaultConfigurationIsVisible = 0; 435 | defaultConfigurationName = Release; 436 | }; 437 | 91CF77CE1E1FB178009FD04D /* Build configuration list for PBXNativeTarget "ConfigenDemo" */ = { 438 | isa = XCConfigurationList; 439 | buildConfigurations = ( 440 | 91CF77CF1E1FB178009FD04D /* Debug */, 441 | 91CF77D01E1FB178009FD04D /* Release */, 442 | ); 443 | defaultConfigurationIsVisible = 0; 444 | defaultConfigurationName = Release; 445 | }; 446 | 91CF77DB1E1FB702009FD04D /* Build configuration list for PBXLegacyTarget "prod" */ = { 447 | isa = XCConfigurationList; 448 | buildConfigurations = ( 449 | 91CF77DC1E1FB702009FD04D /* Debug */, 450 | 91CF77DD1E1FB702009FD04D /* Release */, 451 | ); 452 | defaultConfigurationIsVisible = 0; 453 | defaultConfigurationName = Release; 454 | }; 455 | 91CF77DF1E1FB75E009FD04D /* Build configuration list for PBXLegacyTarget "test" */ = { 456 | isa = XCConfigurationList; 457 | buildConfigurations = ( 458 | 91CF77E01E1FB75E009FD04D /* Debug */, 459 | 91CF77E11E1FB75E009FD04D /* Release */, 460 | ); 461 | defaultConfigurationIsVisible = 0; 462 | defaultConfigurationName = Release; 463 | }; 464 | /* End XCConfigurationList section */ 465 | }; 466 | rootObject = 91CF77B41E1FB178009FD04D /* Project object */; 467 | } 468 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo.xcodeproj/project.xcworkspace/xcuserdata/johnsanderson.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theappbusiness/ConfigGenerator/0605fb9ad54962a7193f1b9257a3ac643bf6c2e8/ConfigenDemo/ConfigenDemo.xcodeproj/project.xcworkspace/xcuserdata/johnsanderson.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo.xcodeproj/xcuserdata/johnsanderson.xcuserdatad/xcschemes/ConfigenDemo-Prod.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 68 | 70 | 76 | 77 | 78 | 79 | 80 | 81 | 87 | 89 | 95 | 96 | 97 | 98 | 100 | 101 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo.xcodeproj/xcuserdata/johnsanderson.xcuserdatad/xcschemes/ConfigenDemo-Test.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 46 | 47 | 53 | 54 | 55 | 56 | 57 | 58 | 68 | 70 | 76 | 77 | 78 | 79 | 80 | 81 | 87 | 89 | 95 | 96 | 97 | 98 | 100 | 101 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo.xcodeproj/xcuserdata/johnsanderson.xcuserdatad/xcschemes/ConfigenDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo.xcodeproj/xcuserdata/johnsanderson.xcuserdatad/xcschemes/prod.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 38 | 39 | 40 | 41 | 47 | 48 | 50 | 51 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo.xcodeproj/xcuserdata/johnsanderson.xcuserdatad/xcschemes/test.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo.xcodeproj/xcuserdata/johnsanderson.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ConfigenDemo-Prod.xcscheme 8 | 9 | orderHint 10 | 3 11 | 12 | ConfigenDemo-Test.xcscheme 13 | 14 | orderHint 15 | 4 16 | 17 | ConfigenDemo.xcscheme 18 | 19 | orderHint 20 | 0 21 | 22 | prod.xcscheme 23 | 24 | isShown 25 | 26 | orderHint 27 | 1 28 | 29 | test.xcscheme 30 | 31 | isShown 32 | 33 | orderHint 34 | 2 35 | 36 | 37 | SuppressBuildableAutocreation 38 | 39 | 91CF77BB1E1FB178009FD04D 40 | 41 | primary 42 | 43 | 44 | 91CF77DA1E1FB702009FD04D 45 | 46 | primary 47 | 48 | 49 | 91CF77DE1E1FB75E009FD04D 50 | 51 | primary 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ConfigenDemo 4 | // 5 | // Created by John Sanderson on 06/01/2017. 6 | // Copyright © 2017 Kin + Carta. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo/Environment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Environment.swift 3 | // ConfigenDemo 4 | // 5 | // Created by John Sanderson on 06/01/2017. 6 | // Copyright © 2017 Kin + Carta. All rights reserved. 7 | // 8 | 9 | enum Environment { 10 | case development 11 | case test 12 | case production 13 | } 14 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ConfigenDemo/ConfigenDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // ConfigenDemo 4 | // 5 | // Created by John Sanderson on 06/01/2017. 6 | // Copyright © 2017 Kin + Carta. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /ConfigenDemo/configen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theappbusiness/ConfigGenerator/0605fb9ad54962a7193f1b9257a3ac643bf6c2e8/ConfigenDemo/configen -------------------------------------------------------------------------------- /ConfigenDemo/prod-config.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | entryPointURL 6 | http://example.com/production 7 | enableFileSharing 8 | 9 | retryCount 10 | 4 11 | adUnitPrefix 12 | production_ad_unit 13 | analyticsKey 14 | haf6d9fha8v56abs 15 | environment 16 | .production 17 | 18 | 19 | -------------------------------------------------------------------------------- /ConfigenDemo/test-config.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | entryPointURL 6 | http://example.com/test 7 | enableFileSharing 8 | 9 | retryCount 10 | 10 11 | adUnitPrefix 12 | test_ad_unit 13 | analyticsKey 14 | tay663jsba9dn6d 15 | environment 16 | .test 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © Kin + Carta 2015 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Configen - Kin + Carta Create](Assets/logo.png) 2 | 3 | # configen 4 | 5 | A command line tool to auto-generate configuration file code, for use in Xcode projects. To read about the motivation behind this project and the scenarios in which it might be used see our [blog post](https://www.theappbusiness.com/insights/a-robust-multi-environment-build-setup). 6 | 7 | The `configen` tool is used to auto-generate configuration code from a property list. It is intended to 8 | create the kind of configuration needed for external URLs or API keys used by your app. Currently supports both Swift and Objective-C code generation. 9 | 10 | # Installation 11 | 12 | To add the `configen` tool to your project you must first aquire the `configen` excecutable binary. The simplest way to do this is to download the executable binary from the latest release. 13 | 14 | Alternatively you can download or clone this repository. Once you have done so, open and build the `configen.xcodeproj` project in Xcode, right click on the `configen` product and select ‘Show in Finder’. 15 | 16 | Once you have the executable file, make a copy and add it to the root directory of your project. Now you are ready to go! Next you need to create the relevant files and set-up your project accordingly. This is outlined below. 17 | 18 | # Usage 19 | 20 | ## Step 1: The mapping file 21 | 22 | Before running the `configen` tool, you need to create a mapping file (.map), in which you define the configuration variables you support. For example: 23 | 24 | ```swift 25 | entryPointURL : URL 26 | enableFileSharing : Bool 27 | retryCount : Int 28 | adUnitPrefix : String 29 | analyticsKey : String 30 | arrayOfHashes: [String] 31 | environment : Environment 32 | ``` 33 | 34 | The `configen` mapping file uses a custom set of types which map to Swift types. Therefore for Objective-C code generation, you must still use Swift equivalent types in the mapping file. 35 | 36 | ## Step 2: A plist for each environment 37 | 38 | Then you need to create a property list (plist) file, in which you provide values for each of the keys defined in your mapping file, above. You need to create a property list file for each required environment. For example, you may have a `test` and a `production` environment. 39 | 40 | Using the above example, the plist source code for a production environment may look as follows: 41 | 42 | ``` 43 | 44 | 45 | entryPointURL 46 | http://example.com/production 47 | enableFileSharing 48 | 49 | retryCount 50 | 4 51 | adUnitPrefix 52 | production_ad_unit 53 | analyticsKey 54 | haf6d9fha8v56abs 55 | environment 56 | .Production 57 | 58 | 59 | ``` 60 | 61 | Before proceeding to the next step, ensure that both the mapping file and plist files are placed inside your project directory. To keep things simple it might be best to place all these files in the same place, for example, in a `Config` sub-folder. You will need to reference the path to these files in step 3. 62 | 63 | ## Step 3: An external build step for each environment 64 | 65 | Finally, you need to create a build target for each of your environments. This can be done be selecting File -> New -> Target and selecting 'External Build System' from the 'Cross-Platform' tab. 66 | 67 | In the settings of each build target point the 'Build Tool' to the location of the `configen` script that you copied to your directory earlier and invoke the arguments as follows. Note that the output directory must be created separately. 68 | 69 | ```sh 70 | configen --plist-path --hints-path --class-name --output-directory 71 | 72 | -p, --plist-path: 73 | Path to the input plist file 74 | -h, --hints-path: 75 | Path to the input hints file 76 | -n, --class-name: 77 | The output config class name 78 | -o, --output-directory: 79 | The output config class directory 80 | -c, --objective-c: 81 | Whether to generate Objective-C files instead of Swift 82 | 83 | # e.g. 84 | 85 | configen --plist-path EnvironmentConfig/EnvironmentConfig_Prod.plist --hints-path EnvironmentConfig.map --class-name EnvironmentConfig --output-directory EnvironmentConfig 86 | 87 | ``` 88 | 89 | `configen` generates Swift code by default. You can generate Objective-C code by providing the `-c` or `--objective-c` switches 90 | 91 | The best way to support multiple environments is to define a separate scheme for each one. Then add the relevant target as an external build step for each scheme ensuring that 'Parallelize Build' is disabled. 92 | 93 | Please refer to the example project included in the repository for further guidance. 94 | 95 | # Standard types supported 96 | 97 | * `Int`: Expects integer type in plist 98 | * `String`: Expects string type in plist 99 | * `Bool`: Expects Boolean type in plist 100 | * `Double`: Expects floating point type in plist 101 | * `URL`: Expects a string in the plist, which can be converted to a URL (validated at compile time) 102 | * `Array` : Expects an array of values in the plist 103 | 104 | # Custom types 105 | 106 | Any other type is supported, by providing a string in the plist which compiles successfully when converted to code. For example: 107 | 108 | ``` 109 | enum Environment { 110 | case Development 111 | case UAT 112 | case Production 113 | } 114 | ``` 115 | 116 | Providing the mapping type `environment : Environment` in the mapping file, and the string `.Production` in the plist, the property in your configuration class will be as follows: 117 | 118 | ``` 119 | static let environment: Environment = .Production 120 | ``` 121 | 122 | This is powerful, because it allows you to work with optionals, which are not supported by the standard types. For example: 123 | 124 | **Mapping file:** 125 | ``` 126 | retryCount : Int? 127 | ``` 128 | 129 | You have to make the type in your plist a string, and input either a number -- e.g. `1` -- or the word `nil`, so the output property becomes, for example: 130 | 131 | ``` 132 | let retryCount: Int? = nil 133 | ``` 134 | 135 | # Arrays 136 | 137 | Configen also supports reading arrays from the plist. For example, you can create an array of certificate hashes for pinning purposes and configen will automatically map them to an array in the generated configuration file. The arrays can be of any depth (for example: `[String]`, `[[URL]]`, `[[[Any]]]`, etc). 138 | 139 | The downside of using an array in the plist is that the order of the array and whether there are any array elements at all is not guaranteed, so keep that in mind when using this functionality. 140 | 141 | Example: 142 | 143 | ## Plist 144 | 145 | ``` 146 | 147 | 148 | 149 | 150 | arrayOfHashes 151 | 152 | 9BV3692736V893B47V893BY4V94B8V6123984BV6983V6B093 153 | BVQ09PY89V86BY98VY9876BV9786B98687B6976BOP967BP96 154 | PB869869P6B76P9B7869P8B69P697P69769769P7B697PB89B 155 | 156 | 157 | 158 | ``` 159 | 160 | ## Mapping (.map) 161 | 162 | `arrayOfHashes: [String]` 163 | 164 | ## Config file (generated) 165 | 166 | ``` 167 | static let arrayOfHashes: [String] = [ 168 | "9BV3692736V893B47V893BY4V94B8V6123984BV6983V6B093", 169 | "BVQ09PY89V86BY98VY9876BV9786B98687B6976BOP967BP96", 170 | "PB869869P6B76P9B7869P8B69P697P69769769P7B697PB89B" 171 | ] 172 | ``` 173 | -------------------------------------------------------------------------------- /configen.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2E378F961D5B811C00E9F524 /* OptionsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E378F951D5B811C00E9F524 /* OptionsParser.swift */; }; 11 | 2E4A8F311D5C77AB0025C677 /* FileTemplates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E4A8F301D5C77AB0025C677 /* FileTemplates.swift */; }; 12 | 2E4A8F331D5CB7D60025C677 /* FileGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E4A8F321D5CB7D60025C677 /* FileGenerator.swift */; }; 13 | 7CBA523323CDB84600B7136C /* ArrayUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CBA523223CDB84600B7136C /* ArrayUtils.swift */; }; 14 | 7CBA523623CDBB6300B7136C /* Character+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CBA523523CDBB6300B7136C /* Character+Extensions.swift */; }; 15 | 91ED18E61E02A48F00475042 /* CommandLineKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91084DEB1E02A0A9004E353B /* CommandLineKit.swift */; }; 16 | 91ED18E71E02A49200475042 /* Option.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91084DED1E02A0A9004E353B /* Option.swift */; }; 17 | 91ED18E81E02A49500475042 /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91084DEE1E02A0A9004E353B /* StringExtensions.swift */; }; 18 | EAD7A2A21BBABE83006A3921 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD7A2A11BBABE83006A3921 /* main.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXCopyFilesBuildPhase section */ 22 | EAD7A29C1BBABE83006A3921 /* CopyFiles */ = { 23 | isa = PBXCopyFilesBuildPhase; 24 | buildActionMask = 2147483647; 25 | dstPath = /usr/share/man/man1/; 26 | dstSubfolderSpec = 0; 27 | files = ( 28 | ); 29 | runOnlyForDeploymentPostprocessing = 1; 30 | }; 31 | /* End PBXCopyFilesBuildPhase section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 2E378F951D5B811C00E9F524 /* OptionsParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionsParser.swift; sourceTree = ""; }; 35 | 2E4A8F301D5C77AB0025C677 /* FileTemplates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileTemplates.swift; sourceTree = ""; }; 36 | 2E4A8F321D5CB7D60025C677 /* FileGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = FileGenerator.swift; sourceTree = ""; tabWidth = 4; }; 37 | 7CBA523223CDB84600B7136C /* ArrayUtils.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = ArrayUtils.swift; sourceTree = ""; tabWidth = 4; }; 38 | 7CBA523523CDBB6300B7136C /* Character+Extensions.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = "Character+Extensions.swift"; sourceTree = ""; tabWidth = 4; }; 39 | 91084DEB1E02A0A9004E353B /* CommandLineKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandLineKit.swift; sourceTree = ""; }; 40 | 91084DEC1E02A0A9004E353B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 41 | 91084DED1E02A0A9004E353B /* Option.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Option.swift; sourceTree = ""; }; 42 | 91084DEE1E02A0A9004E353B /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = ""; }; 43 | EAD7A29E1BBABE83006A3921 /* configen */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = configen; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | EAD7A2A11BBABE83006A3921 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | EAD7A29B1BBABE83006A3921 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 7CBA523423CDBB5400B7136C /* Extensions */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 7CBA523523CDBB6300B7136C /* Character+Extensions.swift */, 62 | ); 63 | path = Extensions; 64 | sourceTree = ""; 65 | }; 66 | 91084DEA1E02A0A9004E353B /* CommandLineKit */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 91084DEB1E02A0A9004E353B /* CommandLineKit.swift */, 70 | 91084DEC1E02A0A9004E353B /* Info.plist */, 71 | 91084DED1E02A0A9004E353B /* Option.swift */, 72 | 91084DEE1E02A0A9004E353B /* StringExtensions.swift */, 73 | ); 74 | path = CommandLineKit; 75 | sourceTree = ""; 76 | }; 77 | EAD7A2951BBABE83006A3921 = { 78 | isa = PBXGroup; 79 | children = ( 80 | EAD7A2A01BBABE83006A3921 /* configen */, 81 | EAD7A29F1BBABE83006A3921 /* Products */, 82 | ); 83 | sourceTree = ""; 84 | }; 85 | EAD7A29F1BBABE83006A3921 /* Products */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | EAD7A29E1BBABE83006A3921 /* configen */, 89 | ); 90 | name = Products; 91 | sourceTree = ""; 92 | }; 93 | EAD7A2A01BBABE83006A3921 /* configen */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 7CBA523423CDBB5400B7136C /* Extensions */, 97 | 91084DEA1E02A0A9004E353B /* CommandLineKit */, 98 | EAD7A2A11BBABE83006A3921 /* main.swift */, 99 | 2E378F951D5B811C00E9F524 /* OptionsParser.swift */, 100 | 2E4A8F301D5C77AB0025C677 /* FileTemplates.swift */, 101 | 2E4A8F321D5CB7D60025C677 /* FileGenerator.swift */, 102 | 7CBA523223CDB84600B7136C /* ArrayUtils.swift */, 103 | ); 104 | path = configen; 105 | sourceTree = ""; 106 | }; 107 | /* End PBXGroup section */ 108 | 109 | /* Begin PBXNativeTarget section */ 110 | EAD7A29D1BBABE83006A3921 /* configen */ = { 111 | isa = PBXNativeTarget; 112 | buildConfigurationList = EAD7A2A51BBABE83006A3921 /* Build configuration list for PBXNativeTarget "configen" */; 113 | buildPhases = ( 114 | EAD7A29A1BBABE83006A3921 /* Sources */, 115 | EAD7A29B1BBABE83006A3921 /* Frameworks */, 116 | EAD7A29C1BBABE83006A3921 /* CopyFiles */, 117 | ); 118 | buildRules = ( 119 | ); 120 | dependencies = ( 121 | ); 122 | name = configen; 123 | productName = configen; 124 | productReference = EAD7A29E1BBABE83006A3921 /* configen */; 125 | productType = "com.apple.product-type.tool"; 126 | }; 127 | /* End PBXNativeTarget section */ 128 | 129 | /* Begin PBXProject section */ 130 | EAD7A2961BBABE83006A3921 /* Project object */ = { 131 | isa = PBXProject; 132 | attributes = { 133 | LastSwiftUpdateCheck = 0700; 134 | LastUpgradeCheck = 1000; 135 | ORGANIZATIONNAME = "The App Business"; 136 | TargetAttributes = { 137 | EAD7A29D1BBABE83006A3921 = { 138 | CreatedOnToolsVersion = 7.0; 139 | LastSwiftMigration = 1020; 140 | }; 141 | }; 142 | }; 143 | buildConfigurationList = EAD7A2991BBABE83006A3921 /* Build configuration list for PBXProject "configen" */; 144 | compatibilityVersion = "Xcode 3.2"; 145 | developmentRegion = English; 146 | hasScannedForEncodings = 0; 147 | knownRegions = ( 148 | English, 149 | en, 150 | ); 151 | mainGroup = EAD7A2951BBABE83006A3921; 152 | productRefGroup = EAD7A29F1BBABE83006A3921 /* Products */; 153 | projectDirPath = ""; 154 | projectRoot = ""; 155 | targets = ( 156 | EAD7A29D1BBABE83006A3921 /* configen */, 157 | ); 158 | }; 159 | /* End PBXProject section */ 160 | 161 | /* Begin PBXSourcesBuildPhase section */ 162 | EAD7A29A1BBABE83006A3921 /* Sources */ = { 163 | isa = PBXSourcesBuildPhase; 164 | buildActionMask = 2147483647; 165 | files = ( 166 | 91ED18E81E02A49500475042 /* StringExtensions.swift in Sources */, 167 | 91ED18E71E02A49200475042 /* Option.swift in Sources */, 168 | 91ED18E61E02A48F00475042 /* CommandLineKit.swift in Sources */, 169 | 7CBA523623CDBB6300B7136C /* Character+Extensions.swift in Sources */, 170 | EAD7A2A21BBABE83006A3921 /* main.swift in Sources */, 171 | 2E4A8F331D5CB7D60025C677 /* FileGenerator.swift in Sources */, 172 | 2E378F961D5B811C00E9F524 /* OptionsParser.swift in Sources */, 173 | 7CBA523323CDB84600B7136C /* ArrayUtils.swift in Sources */, 174 | 2E4A8F311D5C77AB0025C677 /* FileTemplates.swift in Sources */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXSourcesBuildPhase section */ 179 | 180 | /* Begin XCBuildConfiguration section */ 181 | EAD7A2A31BBABE83006A3921 /* Debug */ = { 182 | isa = XCBuildConfiguration; 183 | buildSettings = { 184 | ALWAYS_SEARCH_USER_PATHS = NO; 185 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 186 | CLANG_CXX_LIBRARY = "libc++"; 187 | CLANG_ENABLE_MODULES = YES; 188 | CLANG_ENABLE_OBJC_ARC = YES; 189 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 190 | CLANG_WARN_BOOL_CONVERSION = YES; 191 | CLANG_WARN_COMMA = YES; 192 | CLANG_WARN_CONSTANT_CONVERSION = YES; 193 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 194 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 195 | CLANG_WARN_EMPTY_BODY = YES; 196 | CLANG_WARN_ENUM_CONVERSION = YES; 197 | CLANG_WARN_INFINITE_RECURSION = YES; 198 | CLANG_WARN_INT_CONVERSION = YES; 199 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 200 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 201 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 202 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 203 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 204 | CLANG_WARN_STRICT_PROTOTYPES = YES; 205 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 206 | CLANG_WARN_UNREACHABLE_CODE = YES; 207 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 208 | COPY_PHASE_STRIP = NO; 209 | DEBUG_INFORMATION_FORMAT = dwarf; 210 | ENABLE_STRICT_OBJC_MSGSEND = YES; 211 | ENABLE_TESTABILITY = YES; 212 | GCC_C_LANGUAGE_STANDARD = gnu99; 213 | GCC_DYNAMIC_NO_PIC = NO; 214 | GCC_NO_COMMON_BLOCKS = YES; 215 | GCC_OPTIMIZATION_LEVEL = 0; 216 | GCC_PREPROCESSOR_DEFINITIONS = ( 217 | "DEBUG=1", 218 | "$(inherited)", 219 | ); 220 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 221 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 222 | GCC_WARN_UNDECLARED_SELECTOR = YES; 223 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 224 | GCC_WARN_UNUSED_FUNCTION = YES; 225 | GCC_WARN_UNUSED_VARIABLE = YES; 226 | MACOSX_DEPLOYMENT_TARGET = 10.10; 227 | MTL_ENABLE_DEBUG_INFO = YES; 228 | ONLY_ACTIVE_ARCH = YES; 229 | SDKROOT = macosx; 230 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 231 | SWIFT_VERSION = 5.0; 232 | }; 233 | name = Debug; 234 | }; 235 | EAD7A2A41BBABE83006A3921 /* Release */ = { 236 | isa = XCBuildConfiguration; 237 | buildSettings = { 238 | ALWAYS_SEARCH_USER_PATHS = NO; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | COPY_PHASE_STRIP = NO; 263 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 264 | ENABLE_NS_ASSERTIONS = NO; 265 | ENABLE_STRICT_OBJC_MSGSEND = YES; 266 | GCC_C_LANGUAGE_STANDARD = gnu99; 267 | GCC_NO_COMMON_BLOCKS = YES; 268 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 269 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 270 | GCC_WARN_UNDECLARED_SELECTOR = YES; 271 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 272 | GCC_WARN_UNUSED_FUNCTION = YES; 273 | GCC_WARN_UNUSED_VARIABLE = YES; 274 | MACOSX_DEPLOYMENT_TARGET = 10.10; 275 | MTL_ENABLE_DEBUG_INFO = NO; 276 | SDKROOT = macosx; 277 | SWIFT_COMPILATION_MODE = wholemodule; 278 | SWIFT_VERSION = 5.0; 279 | }; 280 | name = Release; 281 | }; 282 | EAD7A2A61BBABE83006A3921 /* Debug */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | CLANG_ENABLE_MODULES = YES; 286 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 287 | PRODUCT_NAME = "$(TARGET_NAME)"; 288 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 289 | SWIFT_VERSION = 5.0; 290 | }; 291 | name = Debug; 292 | }; 293 | EAD7A2A71BBABE83006A3921 /* Release */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | CLANG_ENABLE_MODULES = YES; 297 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | SWIFT_VERSION = 5.0; 300 | }; 301 | name = Release; 302 | }; 303 | /* End XCBuildConfiguration section */ 304 | 305 | /* Begin XCConfigurationList section */ 306 | EAD7A2991BBABE83006A3921 /* Build configuration list for PBXProject "configen" */ = { 307 | isa = XCConfigurationList; 308 | buildConfigurations = ( 309 | EAD7A2A31BBABE83006A3921 /* Debug */, 310 | EAD7A2A41BBABE83006A3921 /* Release */, 311 | ); 312 | defaultConfigurationIsVisible = 0; 313 | defaultConfigurationName = Release; 314 | }; 315 | EAD7A2A51BBABE83006A3921 /* Build configuration list for PBXNativeTarget "configen" */ = { 316 | isa = XCConfigurationList; 317 | buildConfigurations = ( 318 | EAD7A2A61BBABE83006A3921 /* Debug */, 319 | EAD7A2A71BBABE83006A3921 /* Release */, 320 | ); 321 | defaultConfigurationIsVisible = 0; 322 | defaultConfigurationName = Release; 323 | }; 324 | /* End XCConfigurationList section */ 325 | }; 326 | rootObject = EAD7A2961BBABE83006A3921 /* Project object */; 327 | } 328 | -------------------------------------------------------------------------------- /configen.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /configen/ArrayUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ArrayUtils.swift 3 | // configen 4 | // 5 | // Created by Suyash Srijan on 13/01/2020. 6 | // Copyright © 2020 Kin + Carta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class ArrayUtils { 12 | enum Error: Swift.Error { 13 | case invalidArrayType 14 | } 15 | 16 | /// Check if this is a valid Array type by checking if the brackets are balanced 17 | static func isValidArrayType(_ rawType: String) -> Result { 18 | var stack: [Character] = [] 19 | var elementNameChars: [Character] = [] 20 | 21 | // A quick and simple check to see if this could *potentially* be an 22 | // Array type. 23 | guard rawType.first == .lSquare && rawType.last == .rSquare else { 24 | return .failure(.invalidArrayType) 25 | } 26 | 27 | // Okay, let's properly check if the brackets are balanced 28 | for character in rawType { 29 | if case .lSquare = character { 30 | stack.append(character) 31 | continue 32 | } 33 | 34 | if case .rSquare = character { 35 | guard stack.last == .lSquare else { 36 | return .failure(.invalidArrayType) 37 | } 38 | _ = stack.popLast() 39 | continue 40 | } 41 | 42 | // We're looking at something that is not a bracket. This is likely 43 | // the name of the array's element type (the innermost name if this is 44 | // a nested array). It can only be alphanumeric (i.e. String, UInt8, etc). 45 | if character.isLetter || character.isNumber { 46 | elementNameChars.append(character) 47 | } 48 | } 49 | 50 | // If we extracted the name of the element type and the brackets were 51 | // balanced, then this is a valid type. 52 | if !elementNameChars.isEmpty && stack.isEmpty { 53 | return .success(String(elementNameChars)) 54 | } 55 | 56 | // Otherwise, either we're looking at a different type or the user 57 | // has made a mistake when writing the type name. 58 | return .failure(.invalidArrayType) 59 | } 60 | 61 | /// Format a raw value of NSArray type to a String representation 62 | static func transformArrayToString(_ arrayElementType: String, rawValue: Any) -> String { 63 | precondition(rawValue is NSArray, "Expected an 'NSArray', got '\(type(of: rawValue))'") 64 | let rawValueToConvert = castArray(rawValue as! NSArray, arrayElementType: arrayElementType) 65 | var rawValueString = "\(rawValueToConvert)".trimmingCharacters(in: .whitespacesAndNewlines) 66 | // Special case: If we have an array of URLs, then we need to drop all 67 | // double quotes. 68 | if arrayElementType == "URL" { 69 | rawValueString = rawValueString.filter { $0 != "\"" } 70 | } 71 | return rawValueString 72 | } 73 | 74 | /// Transform an NSArray to Swift Array by explicitly bridging each element. 75 | private static func castArray(_ array: NSArray, arrayElementType: String) -> Any { 76 | var temp: [Any] = [] 77 | for index in 0.. Any { 90 | func emitFailedCastError() -> Never { 91 | fatalError("Cast from element of type '\(type(of: value))' to " + 92 | "type '\(arrayElementType)' is unsupported") 93 | } 94 | switch arrayElementType { 95 | case "String": 96 | return value as! String 97 | case "Bool": 98 | return value as! Bool 99 | case "Int": 100 | return value as! Int 101 | case "Double": 102 | return value as! Double 103 | case "URL": 104 | guard let str = value as? String, let _ = URL(string: str) else { 105 | emitFailedCastError() 106 | } 107 | return "URL(string: \(str))!" 108 | case "Any": 109 | return value 110 | default: 111 | // TODO: Handle Data, Date, etc types 112 | emitFailedCastError() 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /configen/CommandLineKit/CommandLineKit.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * CommandLine.swift 3 | * Copyright (c) 2014 Ben Gollmer. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | import Foundation 19 | /* Required for setlocale(3) */ 20 | #if os(OSX) 21 | import Darwin 22 | #elseif os(Linux) 23 | import Glibc 24 | #endif 25 | 26 | let shortOptionPrefix = "-" 27 | let longOptionPrefix = "--" 28 | 29 | /* Stop parsing arguments when an ArgumentStopper (--) is detected. This is a GNU getopt 30 | * convention; cf. https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html 31 | */ 32 | let argumentStopper = "--" 33 | 34 | /* Allow arguments to be attached to flags when separated by this character. 35 | * --flag=argument is equivalent to --flag argument 36 | */ 37 | let argumentAttacher: Character = "=" 38 | 39 | /* An output stream to stderr; used by CommandLine.printUsage(). */ 40 | private struct StderrOutputStream: TextOutputStream { 41 | static let stream = StderrOutputStream() 42 | func write(_ s: String) { 43 | fputs(s, stderr) 44 | } 45 | } 46 | 47 | /** 48 | * The CommandLine class implements a command-line interface for your app. 49 | * 50 | * To use it, define one or more Options (see Option.swift) and add them to your 51 | * CommandLine object, then invoke `parse()`. Each Option object will be populated with 52 | * the value given by the user. 53 | * 54 | * If any required options are missing or if an invalid value is found, `parse()` will throw 55 | * a `ParseError`. You can then call `printUsage()` to output an automatically-generated usage 56 | * message. 57 | */ 58 | public final class CommandLineKit { 59 | private var _arguments: [String] 60 | private var _options: [Option] = [Option]() 61 | private var _maxFlagDescriptionWidth: Int = 0 62 | private var _usedFlags: Set { 63 | var usedFlags = Set(minimumCapacity: _options.count * 2) 64 | 65 | for option in _options { 66 | for case let flag? in [option.shortFlag, option.longFlag] { 67 | usedFlags.insert(flag) 68 | } 69 | } 70 | 71 | return usedFlags 72 | } 73 | 74 | /** 75 | * After calling `parse()`, this property will contain any values that weren't captured 76 | * by an Option. For example: 77 | * 78 | * ``` 79 | * let cli = CommandLine() 80 | * let fileType = StringOption(shortFlag: "t", longFlag: "type", required: true, helpMessage: "Type of file") 81 | * 82 | * do { 83 | * try cli.parse() 84 | * print("File type is \(type), files are \(cli.unparsedArguments)") 85 | * catch { 86 | * cli.printUsage(error) 87 | * exit(EX_USAGE) 88 | * } 89 | * 90 | * --- 91 | * 92 | * $ ./readfiles --type=pdf ~/file1.pdf ~/file2.pdf 93 | * File type is pdf, files are ["~/file1.pdf", "~/file2.pdf"] 94 | * ``` 95 | */ 96 | public private(set) var unparsedArguments: [String] = [String]() 97 | 98 | /** 99 | * If supplied, this function will be called when printing usage messages. 100 | * 101 | * You can use the `defaultFormat` function to get the normally-formatted 102 | * output, either before or after modifying the provided string. For example: 103 | * 104 | * ``` 105 | * let cli = CommandLine() 106 | * cli.formatOutput = { str, type in 107 | * switch(type) { 108 | * case .Error: 109 | * // Make errors shouty 110 | * return defaultFormat(str.uppercaseString, type: type) 111 | * case .OptionHelp: 112 | * // Don't use the default indenting 113 | * return ">> \(s)\n" 114 | * default: 115 | * return defaultFormat(str, type: type) 116 | * } 117 | * } 118 | * ``` 119 | * 120 | * - note: Newlines are not appended to the result of this function. If you don't use 121 | * `defaultFormat()`, be sure to add them before returning. 122 | */ 123 | public var formatOutput: ((String, OutputType) -> String)? 124 | 125 | /** 126 | * The maximum width of all options' `flagDescription` properties; provided for use by 127 | * output formatters. 128 | * 129 | * - seealso: `defaultFormat`, `formatOutput` 130 | */ 131 | public var maxFlagDescriptionWidth: Int { 132 | if _maxFlagDescriptionWidth == 0 { 133 | _maxFlagDescriptionWidth = _options.map { $0.flagDescription.count }.sorted().first ?? 0 134 | } 135 | 136 | return _maxFlagDescriptionWidth 137 | } 138 | 139 | /** 140 | * The type of output being supplied to an output formatter. 141 | * 142 | * - seealso: `formatOutput` 143 | */ 144 | public enum OutputType { 145 | /** About text: `Usage: command-example [options]` and the like */ 146 | case about 147 | 148 | /** An error message: `Missing required option --extract` */ 149 | case error 150 | 151 | /** An Option's `flagDescription`: `-h, --help:` */ 152 | case optionFlag 153 | 154 | /** An Option's help message */ 155 | case optionHelp 156 | } 157 | 158 | /** A ParseError is thrown if the `parse()` method fails. */ 159 | public enum ParseError: Error, CustomStringConvertible { 160 | /** Thrown if an unrecognized argument is passed to `parse()` in strict mode */ 161 | case invalidArgument(String) 162 | 163 | /** Thrown if the value for an Option is invalid (e.g. a string is passed to an IntOption) */ 164 | case invalidValueForOption(Option, [String]) 165 | 166 | /** Thrown if an Option with required: true is missing */ 167 | case missingRequiredOptions([Option]) 168 | 169 | public var description: String { 170 | switch self { 171 | case let .invalidArgument(arg): 172 | return "Invalid argument: \(arg)" 173 | case let .invalidValueForOption(opt, vals): 174 | let vs = vals.joined(separator: ", ") 175 | return "Invalid value(s) for option \(opt.flagDescription): \(vs)" 176 | case let .missingRequiredOptions(opts): 177 | return "Missing required options: \(opts.map { return $0.flagDescription })" 178 | } 179 | } 180 | } 181 | 182 | /** 183 | * Initializes a CommandLine object. 184 | * 185 | * - parameter arguments: Arguments to parse. If omitted, the arguments passed to the app 186 | * on the command line will automatically be used. 187 | * 188 | * - returns: An initalized CommandLine object. 189 | */ 190 | public init(arguments: [String] = Swift.CommandLine.arguments) { 191 | self._arguments = arguments 192 | 193 | /* Initialize locale settings from the environment */ 194 | setlocale(LC_ALL, "") 195 | } 196 | 197 | /* Returns all argument values from flagIndex to the next flag or the end of the argument array. */ 198 | private func _getFlagValues(_ flagIndex: Int, _ attachedArg: String? = nil) -> [String] { 199 | var args: [String] = [String]() 200 | var skipFlagChecks = false 201 | 202 | if let a = attachedArg { 203 | args.append(a) 204 | } 205 | 206 | for i in flagIndex + 1 ..< _arguments.count { 207 | if !skipFlagChecks { 208 | if _arguments[i] == argumentStopper { 209 | skipFlagChecks = true 210 | continue 211 | } 212 | 213 | if _arguments[i].hasPrefix(shortOptionPrefix) && Int(_arguments[i]) == nil && 214 | _arguments[i].toDouble() == nil { 215 | break 216 | } 217 | } 218 | 219 | args.append(_arguments[i]) 220 | } 221 | 222 | return args 223 | } 224 | 225 | /** 226 | * Adds an Option to the command line. 227 | * 228 | * - parameter option: The option to add. 229 | */ 230 | public func addOption(_ option: Option) { 231 | let uf = _usedFlags 232 | for case let flag? in [option.shortFlag, option.longFlag] { 233 | assert(!uf.contains(flag), "Flag '\(flag)' already in use") 234 | } 235 | 236 | _options.append(option) 237 | _maxFlagDescriptionWidth = 0 238 | } 239 | 240 | /** 241 | * Adds one or more Options to the command line. 242 | * 243 | * - parameter options: An array containing the options to add. 244 | */ 245 | public func addOptions(_ options: [Option]) { 246 | for o in options { 247 | addOption(o) 248 | } 249 | } 250 | 251 | /** 252 | * Adds one or more Options to the command line. 253 | * 254 | * - parameter options: The options to add. 255 | */ 256 | public func addOptions(_ options: Option...) { 257 | for o in options { 258 | addOption(o) 259 | } 260 | } 261 | 262 | /** 263 | * Sets the command line Options. Any existing options will be overwritten. 264 | * 265 | * - parameter options: An array containing the options to set. 266 | */ 267 | public func setOptions(_ options: [Option]) { 268 | _options = [Option]() 269 | addOptions(options) 270 | } 271 | 272 | /** 273 | * Sets the command line Options. Any existing options will be overwritten. 274 | * 275 | * - parameter options: The options to set. 276 | */ 277 | public func setOptions(_ options: Option...) { 278 | _options = [Option]() 279 | addOptions(options) 280 | } 281 | 282 | /** 283 | * Parses command-line arguments into their matching Option values. 284 | * 285 | * - parameter strict: Fail if any unrecognized flags are present (default: false). 286 | * 287 | * - throws: A `ParseError` if argument parsing fails: 288 | * - `.InvalidArgument` if an unrecognized flag is present and `strict` is true 289 | * - `.InvalidValueForOption` if the value supplied to an option is not valid (for 290 | * example, a string is supplied for an IntOption) 291 | * - `.MissingRequiredOptions` if a required option isn't present 292 | */ 293 | public func parse(strict: Bool = false) throws { 294 | var strays = _arguments 295 | 296 | /* Nuke executable name */ 297 | strays[0] = "" 298 | 299 | let argumentsEnumerator = _arguments.enumerated() 300 | for (idx, arg) in argumentsEnumerator { 301 | if arg == argumentStopper { 302 | break 303 | } 304 | 305 | if !arg.hasPrefix(shortOptionPrefix) { 306 | continue 307 | } 308 | 309 | let skipChars = arg.hasPrefix(longOptionPrefix) ? 310 | longOptionPrefix.count : shortOptionPrefix.count 311 | let flagWithArg = arg[arg.index(arg.startIndex, offsetBy: skipChars).. 348 | */ 349 | let vals = (i == flagLength - 1) ? self._getFlagValues(idx, attachedArg) : [String]() 350 | guard option.setValue(vals) else { 351 | throw ParseError.invalidValueForOption(option, vals) 352 | } 353 | 354 | var claimedIdx = idx + option.claimedValues 355 | if attachedArg != nil { claimedIdx -= 1 } 356 | for i in idx...claimedIdx { 357 | strays[i] = "" 358 | } 359 | 360 | flagMatched = true 361 | break 362 | } 363 | } 364 | } 365 | 366 | /* Invalid flag */ 367 | guard !strict || flagMatched else { 368 | throw ParseError.invalidArgument(arg) 369 | } 370 | } 371 | 372 | /* Check to see if any required options were not matched */ 373 | let missingOptions = _options.filter { $0.required && !$0.wasSet } 374 | guard missingOptions.count == 0 else { 375 | throw ParseError.missingRequiredOptions(missingOptions) 376 | } 377 | 378 | unparsedArguments = strays.filter { $0 != "" } 379 | } 380 | 381 | /** 382 | * Provides the default formatting of `printUsage()` output. 383 | * 384 | * - parameter s: The string to format. 385 | * - parameter type: Type of output. 386 | * 387 | * - returns: The formatted string. 388 | * - seealso: `formatOutput` 389 | */ 390 | public func defaultFormat(s: String, type: OutputType) -> String { 391 | switch type { 392 | case .about: 393 | return "\(s)\n" 394 | case .error: 395 | return "\(s)\n\n" 396 | case .optionFlag: 397 | return " \(s.padded(toWidth: maxFlagDescriptionWidth)):\n" 398 | case .optionHelp: 399 | return " \(s)\n" 400 | } 401 | } 402 | 403 | /* printUsage() is generic for OutputStreamType because the Swift compiler crashes 404 | * on inout protocol function parameters in Xcode 7 beta 1 (rdar://21372694). 405 | */ 406 | 407 | /** 408 | * Prints a usage message. 409 | * 410 | * - parameter to: An OutputStreamType to write the error message to. 411 | */ 412 | public func printUsage(_ to: inout TargetStream) { 413 | /* Nil coalescing operator (??) doesn't work on closures :( */ 414 | let format = formatOutput != nil ? formatOutput! : defaultFormat 415 | 416 | let name = _arguments[0] 417 | print(format("Usage: \(name) [options]", .about), terminator: "", to: &to) 418 | 419 | for opt in _options { 420 | print(format(opt.flagDescription, .optionFlag), terminator: "", to: &to) 421 | print(format(opt.helpMessage, .optionHelp), terminator: "", to: &to) 422 | } 423 | } 424 | 425 | /** 426 | * Prints a usage message. 427 | * 428 | * - parameter error: An error thrown from `parse()`. A description of the error 429 | * (e.g. "Missing required option --extract") will be printed before the usage message. 430 | * - parameter to: An OutputStreamType to write the error message to. 431 | */ 432 | public func printUsage(_ error: Error, to: inout TargetStream) { 433 | let format = formatOutput != nil ? formatOutput! : defaultFormat 434 | print(format("\(error)", .error), terminator: "", to: &to) 435 | printUsage(&to) 436 | } 437 | 438 | /** 439 | * Prints a usage message. 440 | * 441 | * - parameter error: An error thrown from `parse()`. A description of the error 442 | * (e.g. "Missing required option --extract") will be printed before the usage message. 443 | */ 444 | public func printUsage(_ error: Error) { 445 | var out = StderrOutputStream.stream 446 | printUsage(error, to: &out) 447 | } 448 | 449 | /** 450 | * Prints a usage message. 451 | */ 452 | public func printUsage() { 453 | var out = StderrOutputStream.stream 454 | printUsage(&out) 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /configen/CommandLineKit/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSHumanReadableCopyright 24 | Copyright © 2014 Ben Gollmer. Licensed under the Apache License, Version 2.0. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /configen/CommandLineKit/Option.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * Option.swift 3 | * Copyright (c) 2014 Ben Gollmer. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /** 19 | * The base class for a command-line option. 20 | */ 21 | public class Option { 22 | public let shortFlag: String? 23 | public let longFlag: String? 24 | public let required: Bool 25 | public let helpMessage: String 26 | 27 | /** True if the option was set when parsing command-line arguments */ 28 | public var wasSet: Bool { 29 | return false 30 | } 31 | 32 | public var claimedValues: Int { return 0 } 33 | 34 | public var flagDescription: String { 35 | switch (shortFlag, longFlag) { 36 | case let (sf?, lf?): 37 | return "\(shortOptionPrefix)\(sf), \(longOptionPrefix)\(lf)" 38 | case (nil, let lf?): 39 | return "\(longOptionPrefix)\(lf)" 40 | case (let sf?, nil): 41 | return "\(shortOptionPrefix)\(sf)" 42 | default: 43 | return "" 44 | } 45 | } 46 | 47 | internal init(_ shortFlag: String?, _ longFlag: String?, _ required: Bool, _ helpMessage: String) { 48 | if let sf = shortFlag { 49 | assert(sf.count == 1, "Short flag must be a single character") 50 | assert(Int(sf) == nil && sf.toDouble() == nil, "Short flag cannot be a numeric value") 51 | } 52 | 53 | if let lf = longFlag { 54 | assert(Int(lf) == nil && lf.toDouble() == nil, "Long flag cannot be a numeric value") 55 | } 56 | 57 | self.shortFlag = shortFlag 58 | self.longFlag = longFlag 59 | self.helpMessage = helpMessage 60 | self.required = required 61 | } 62 | 63 | /* The optional casts in these initalizers force them to call the private initializer. Without 64 | * the casts, they recursively call themselves. 65 | */ 66 | 67 | /** Initializes a new Option that has both long and short flags. */ 68 | public convenience init(shortFlag: String, longFlag: String, required: Bool = false, helpMessage: String) { 69 | self.init(shortFlag as String?, longFlag, required, helpMessage) 70 | } 71 | 72 | /** Initializes a new Option that has only a short flag. */ 73 | public convenience init(shortFlag: String, required: Bool = false, helpMessage: String) { 74 | self.init(shortFlag as String?, nil, required, helpMessage) 75 | } 76 | 77 | /** Initializes a new Option that has only a long flag. */ 78 | public convenience init(longFlag: String, required: Bool = false, helpMessage: String) { 79 | self.init(nil, longFlag as String?, required, helpMessage) 80 | } 81 | 82 | func flagMatch(_ flag: String) -> Bool { 83 | return flag == shortFlag || flag == longFlag 84 | } 85 | 86 | func setValue(_ values: [String]) -> Bool { 87 | return false 88 | } 89 | } 90 | 91 | /** 92 | * A boolean option. The presence of either the short or long flag will set the value to true; 93 | * absence of the flag(s) is equivalent to false. 94 | */ 95 | public class BoolOption: Option { 96 | private var _value: Bool = false 97 | 98 | public var value: Bool { 99 | return _value 100 | } 101 | 102 | override public var wasSet: Bool { 103 | return _value 104 | } 105 | 106 | override func setValue(_ values: [String]) -> Bool { 107 | _value = true 108 | return true 109 | } 110 | } 111 | 112 | /** An option that accepts a positive or negative integer value. */ 113 | public class IntOption: Option { 114 | private var _value: Int? 115 | 116 | public var value: Int? { 117 | return _value 118 | } 119 | 120 | override public var wasSet: Bool { 121 | return _value != nil 122 | } 123 | 124 | override public var claimedValues: Int { 125 | return _value != nil ? 1 : 0 126 | } 127 | 128 | override func setValue(_ values: [String]) -> Bool { 129 | if values.count == 0 { 130 | return false 131 | } 132 | 133 | if let val = Int(values[0]) { 134 | _value = val 135 | return true 136 | } 137 | 138 | return false 139 | } 140 | } 141 | 142 | /** 143 | * An option that represents an integer counter. Each time the short or long flag is found 144 | * on the command-line, the counter will be incremented. 145 | */ 146 | public class CounterOption: Option { 147 | private var _value: Int = 0 148 | 149 | public var value: Int { 150 | return _value 151 | } 152 | 153 | override public var wasSet: Bool { 154 | return _value > 0 155 | } 156 | 157 | public func reset() { 158 | _value = 0 159 | } 160 | 161 | override func setValue(_ values: [String]) -> Bool { 162 | _value += 1 163 | return true 164 | } 165 | } 166 | 167 | /** An option that accepts a positive or negative floating-point value. */ 168 | public class DoubleOption: Option { 169 | private var _value: Double? 170 | 171 | public var value: Double? { 172 | return _value 173 | } 174 | 175 | override public var wasSet: Bool { 176 | return _value != nil 177 | } 178 | 179 | override public var claimedValues: Int { 180 | return _value != nil ? 1 : 0 181 | } 182 | 183 | override func setValue(_ values: [String]) -> Bool { 184 | if values.count == 0 { 185 | return false 186 | } 187 | 188 | if let val = values[0].toDouble() { 189 | _value = val 190 | return true 191 | } 192 | 193 | return false 194 | } 195 | } 196 | 197 | /** An option that accepts a string value. */ 198 | public class StringOption: Option { 199 | private var _value: String? 200 | 201 | public var value: String? { 202 | return _value 203 | } 204 | 205 | override public var wasSet: Bool { 206 | return _value != nil 207 | } 208 | 209 | override public var claimedValues: Int { 210 | return _value != nil ? 1 : 0 211 | } 212 | 213 | override func setValue(_ values: [String]) -> Bool { 214 | if values.count == 0 { 215 | return false 216 | } 217 | 218 | _value = values[0] 219 | return true 220 | } 221 | } 222 | 223 | /** An option that accepts one or more string values. */ 224 | public class MultiStringOption: Option { 225 | private var _value: [String]? 226 | 227 | public var value: [String]? { 228 | return _value 229 | } 230 | 231 | override public var wasSet: Bool { 232 | return _value != nil 233 | } 234 | 235 | override public var claimedValues: Int { 236 | if let v = _value { 237 | return v.count 238 | } 239 | 240 | return 0 241 | } 242 | 243 | override func setValue(_ values: [String]) -> Bool { 244 | if values.count == 0 { 245 | return false 246 | } 247 | 248 | _value = values 249 | return true 250 | } 251 | } 252 | 253 | /** An option that represents an enum value. */ 254 | public class EnumOption: Option where T.RawValue == String { 255 | private var _value: T? 256 | public var value: T? { 257 | return _value 258 | } 259 | 260 | override public var wasSet: Bool { 261 | return _value != nil 262 | } 263 | 264 | override public var claimedValues: Int { 265 | return _value != nil ? 1 : 0 266 | } 267 | 268 | /* Re-defining the intializers is necessary to make the Swift 2 compiler happy, as 269 | * of Xcode 7 beta 2. 270 | */ 271 | 272 | internal override init(_ shortFlag: String?, _ longFlag: String?, _ required: Bool, _ helpMessage: String) { 273 | super.init(shortFlag, longFlag, required, helpMessage) 274 | } 275 | 276 | /** Initializes a new Option that has both long and short flags. */ 277 | public convenience init(shortFlag: String, longFlag: String, required: Bool = false, helpMessage: String) { 278 | self.init(shortFlag as String?, longFlag, required, helpMessage) 279 | } 280 | 281 | /** Initializes a new Option that has only a short flag. */ 282 | public convenience init(shortFlag: String, required: Bool = false, helpMessage: String) { 283 | self.init(shortFlag as String?, nil, required, helpMessage) 284 | } 285 | 286 | /** Initializes a new Option that has only a long flag. */ 287 | public convenience init(longFlag: String, required: Bool = false, helpMessage: String) { 288 | self.init(nil, longFlag as String?, required, helpMessage) 289 | } 290 | 291 | override func setValue(_ values: [String]) -> Bool { 292 | if values.count == 0 { 293 | return false 294 | } 295 | 296 | if let v = T(rawValue: values[0]) { 297 | _value = v 298 | return true 299 | } 300 | 301 | return false 302 | } 303 | 304 | } 305 | -------------------------------------------------------------------------------- /configen/CommandLineKit/StringExtensions.swift: -------------------------------------------------------------------------------- 1 | /* 2 | * StringExtensions.swift 3 | * Copyright (c) 2014 Ben Gollmer. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* Required for localeconv(3) */ 19 | #if os(OSX) 20 | import Darwin 21 | #elseif os(Linux) 22 | import Glibc 23 | #endif 24 | 25 | internal extension String { 26 | /* Retrieves locale-specified decimal separator from the environment 27 | * using localeconv(3). 28 | */ 29 | private func _localDecimalPoint() -> Character { 30 | let locale = localeconv() 31 | if locale != nil { 32 | if let decimalPoint = locale?.pointee.decimal_point { 33 | return Character(UnicodeScalar(UInt32(decimalPoint.pointee))!) 34 | } 35 | } 36 | 37 | return "." 38 | } 39 | 40 | /** 41 | * Attempts to parse the string value into a Double. 42 | * 43 | * - returns: A Double if the string can be parsed, nil otherwise. 44 | */ 45 | func toDouble() -> Double? { 46 | var characteristic: String = "0" 47 | var mantissa: String = "0" 48 | var inMantissa: Bool = false 49 | var isNegative: Bool = false 50 | let decimalPoint = self._localDecimalPoint() 51 | 52 | let charactersEnumerator = self.enumerated() 53 | for (i, c) in charactersEnumerator { 54 | if i == 0 && c == "-" { 55 | isNegative = true 56 | continue 57 | } 58 | 59 | if c == decimalPoint { 60 | inMantissa = true 61 | continue 62 | } 63 | 64 | if Int(String(c)) != nil { 65 | if !inMantissa { 66 | characteristic.append(c) 67 | } else { 68 | mantissa.append(c) 69 | } 70 | } else { 71 | /* Non-numeric character found, bail */ 72 | return nil 73 | } 74 | } 75 | 76 | let doubleCharacteristic = Double(Int(characteristic)!) 77 | return (doubleCharacteristic + 78 | Double(Int(mantissa)!) / pow(Double(10), Double(mantissa.count - 1))) * 79 | (isNegative ? -1 : 1) 80 | } 81 | 82 | /** 83 | * Splits a string into an array of string components. 84 | * 85 | * - parameter by: The character to split on. 86 | * - parameter maxSplits: The maximum number of splits to perform. If 0, all possible splits are made. 87 | * 88 | * - returns: An array of string components. 89 | */ 90 | func split(by: Character, maxSplits: Int = 0) -> [String] { 91 | var s = [String]() 92 | var numSplits = 0 93 | 94 | var curIdx = self.startIndex 95 | for i in self.indices { 96 | let c = self[i] 97 | if c == by && (maxSplits == 0 || numSplits < maxSplits) { 98 | let substring = self[curIdx.. String { 122 | var s = self 123 | var currentLength = self.count 124 | 125 | while currentLength < width { 126 | s.append(padChar) 127 | currentLength += 1 128 | } 129 | 130 | return s 131 | } 132 | 133 | /** 134 | * Wraps a string to the specified width. 135 | * 136 | * This just does simple greedy word-packing, it doesn't go full Knuth-Plass. 137 | * If a single word is longer than the line width, it will be placed (unsplit) 138 | * on a line by itself. 139 | * 140 | * - parameter atWidth: The maximum length of a line. 141 | * - parameter wrapBy: The line break character to use. 142 | * - parameter splitBy: The character to use when splitting the string into words. 143 | * 144 | * - returns: A new string, wrapped at the given width. 145 | */ 146 | func wrapped(atWidth width: Int, wrapBy: Character = "\n", splitBy: Character = " ") -> String { 147 | var s = "" 148 | var currentLineWidth = 0 149 | 150 | for word in self.split(by: splitBy) { 151 | let wordLength = word.count 152 | 153 | if currentLineWidth + wordLength + 1 > width { 154 | /* Word length is greater than line length, can't wrap */ 155 | if wordLength >= width { 156 | s += word 157 | } 158 | 159 | s.append(wrapBy) 160 | currentLineWidth = 0 161 | } 162 | 163 | currentLineWidth += wordLength + 1 164 | s += word 165 | s.append(splitBy) 166 | } 167 | 168 | return s 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /configen/Extensions/Character+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Character+Extensions.swift 3 | // configen 4 | // 5 | // Created by Suyash Srijan on 13/01/2020. 6 | // Copyright © 2020 Kin + Carta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Character { 12 | static let lSquare = Character("[") 13 | static let rSquare = Character("]") 14 | } 15 | -------------------------------------------------------------------------------- /configen/FileGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileGenerator.swift 3 | // configen 4 | // 5 | // Created by Dónal O'Brien on 11/08/2016. 6 | // Copyright © 2016 Kin + Carta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct FileGenerator { 12 | 13 | let optionsParser: OptionsParser 14 | 15 | var autoGenerationComment: String { 16 | return """ 17 | // auto-generated by \(optionsParser.appName)\n// to add or remove properties, edit the mapping file: '\(optionsParser.inputHintsFilePath)'.\n// README: https://github.com/theappbusiness/ConfigGenerator/blob/main/README.md\n\n 18 | """ 19 | } 20 | 21 | func generateHeaderFile(withTemplate template: HeaderTemplate) { 22 | 23 | var headerBodyContent = "" 24 | optionsParser.sortedHints.forEach { hint in 25 | let headerLine = methodDeclaration(for: hint, template: template) 26 | headerBodyContent.append("\n" + headerLine + ";" + "\n") 27 | } 28 | 29 | var headerBody = template.headerBody 30 | headerBody.replace(token: template.bodyToken, withString: headerBodyContent) 31 | 32 | do { 33 | let headerOutputString = autoGenerationComment + template.headerImportStatements + headerBody 34 | try headerOutputString.write(toFile: template.outputHeaderFileName, atomically: true, encoding: String.Encoding.utf8) 35 | } catch { 36 | fatalError("Failed to write to file at path \(template.outputHeaderFileName)") 37 | } 38 | 39 | } 40 | 41 | func generateImplementationFile(withTemplate template: ImplementationTemplate) { 42 | var implementationBodyContent = "" 43 | optionsParser.sortedHints.forEach { hint in 44 | let implementationLine = methodImplementation(for: hint, template: template) 45 | implementationBodyContent.append("\n" + implementationLine + "\n") 46 | } 47 | 48 | var implementationBody = template.implementationBody 49 | implementationBody.replace(token: template.bodyToken, withString: implementationBodyContent) 50 | 51 | do { 52 | let implementationOutputString = autoGenerationComment + template.implementationImportStatements + implementationBody 53 | try implementationOutputString.write(toFile: template.outputImplementationFileName, atomically: true, encoding: String.Encoding.utf8) 54 | } catch { 55 | fatalError("Failed to write to file at path \(template.outputImplementationFileName)") 56 | } 57 | 58 | } 59 | 60 | private func methodDeclaration(for hint: OptionsParser.Hint, template: HeaderTemplate) -> String { 61 | var line = "" 62 | 63 | switch hint.type { 64 | case "Double": 65 | line = template.doubleDeclaration 66 | 67 | case "Int": 68 | line = template.integerDeclaration 69 | 70 | case "String": 71 | line = template.stringDeclaration 72 | 73 | case "Bool": 74 | line = template.booleanDeclaration 75 | 76 | case "URL": 77 | line = template.urlDeclaration 78 | 79 | default: 80 | line = template.customDeclaration 81 | line.replace(token: template.customTypeToken, withString: hint.type) 82 | } 83 | 84 | line.replace(token: template.variableNameToken, withString: hint.variableName) 85 | 86 | return line 87 | } 88 | 89 | private func methodImplementation(for hint: OptionsParser.Hint, template: ImplementationTemplate) -> String { 90 | 91 | guard let value = optionsParser.plistDictionary[hint.variableName] else { 92 | fatalError("No configuration setting for variable name: \(hint.variableName)") 93 | } 94 | 95 | var line = "" 96 | 97 | switch hint.type { 98 | case "Double": 99 | line = template.doubleImplementation 100 | 101 | case "Int": 102 | line = template.integerImplementation 103 | 104 | case "String": 105 | line = template.stringImplementation 106 | 107 | case "Bool": 108 | guard let value = value as? Bool else { fatalError("Not a bool!") } 109 | let boolString = value ? template.trueString : template.falseString 110 | line = template.booleanImplementation 111 | line.replace(token: template.valueToken, withString: boolString) 112 | 113 | case "URL": 114 | guard let url = URL(string: "\(value)") else { fatalError("Not a URL!") } 115 | guard url.host != nil else { 116 | fatalError("Found URL without host: \(url) for setting: \(hint.variableName)") 117 | } 118 | line = template.urlImplementation 119 | default: 120 | // Check if this is an Array type 121 | if case let .success(arrayElementType) = ArrayUtils.isValidArrayType(hint.type) { 122 | let arrayString = ArrayUtils.transformArrayToString(arrayElementType, rawValue: value) 123 | line = template.customImplementation 124 | line.replace(token: template.variableNameToken, withString: hint.variableName) 125 | line.replace(token: template.customTypeToken, withString: hint.type) 126 | line.replace(token: template.valueToken, withString: arrayString) 127 | return line 128 | } 129 | guard value is String else { 130 | fatalError("Value (\(value)) must be a string in order to be used by custom type \(hint.type)") 131 | } 132 | line = template.customImplementation 133 | line.replace(token: template.customTypeToken, withString: hint.type) 134 | } 135 | 136 | line.replace(token: template.variableNameToken, withString: hint.variableName) 137 | line.replace(token: template.valueToken, withString: "\(value)") 138 | 139 | return line 140 | } 141 | } 142 | 143 | extension String { 144 | mutating func replace(token: String, withString string: String) { 145 | self = replacingOccurrences(of: token, with: string) 146 | } 147 | 148 | var trimmed: String { 149 | return (self as NSString).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 150 | } 151 | 152 | func match(regex: String) -> Bool { 153 | guard let regex = try? NSRegularExpression(pattern: regex, options: .caseInsensitive) else { 154 | return false 155 | } 156 | 157 | let matches = regex.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count)) 158 | return !matches.isEmpty 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /configen/FileTemplates.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileTemplates.swift 3 | // configen 4 | // 5 | // Created by Dónal O'Brien on 11/08/2016. 6 | // Copyright © 2016 Kin + Carta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol Template { 12 | var variableNameToken: String { get } 13 | var customTypeToken: String { get } 14 | var bodyToken: String { get } 15 | } 16 | 17 | extension Template { 18 | var variableNameToken: String { return "$VARIABLE_NAME_TOKEN" } 19 | var customTypeToken: String { return "$CUSTOM_TYPE_TOKEN" } 20 | var bodyToken: String { return "$BODY_TOKEN" } 21 | } 22 | 23 | protocol HeaderTemplate: Template { 24 | 25 | var outputHeaderFileName: String { get } 26 | var headerImportStatements: String { get } 27 | var headerBody: String { get } 28 | 29 | var doubleDeclaration: String { get } 30 | var integerDeclaration: String { get } 31 | var stringDeclaration: String { get } 32 | var booleanDeclaration: String { get } 33 | var urlDeclaration: String { get } 34 | var customDeclaration: String { get } 35 | } 36 | 37 | protocol ImplementationTemplate: Template { 38 | 39 | var outputImplementationFileName: String { get } 40 | var implementationImportStatements: String { get } 41 | var implementationBody: String { get } 42 | 43 | var doubleImplementation: String { get } 44 | var integerImplementation: String { get } 45 | var stringImplementation: String { get } 46 | var booleanImplementation: String { get } 47 | var trueString: String { get } 48 | var falseString: String { get } 49 | var urlImplementation: String { get } 50 | var customImplementation: String { get } 51 | var valueToken: String { get } 52 | } 53 | 54 | extension ImplementationTemplate { 55 | var valueToken: String { return "$VALUE_TOKEN" } 56 | } 57 | 58 | struct ObjectiveCTemplate: HeaderTemplate, ImplementationTemplate { 59 | 60 | let optionsParser: OptionsParser 61 | 62 | // MARK: - HeaderTemplate 63 | 64 | var outputHeaderFileName: String { return "\(optionsParser.outputClassDirectory)/\(optionsParser.outputClassName).h" } 65 | 66 | var headerBody: String { return "@interface \(optionsParser.outputClassName) : NSObject \n\(bodyToken)\n@end\n" } 67 | 68 | var doubleDeclaration: String { return "+ (NSNumber *)\(variableNameToken)" } 69 | var integerDeclaration: String { return "+ (NSNumber *)\(variableNameToken)" } 70 | var stringDeclaration: String { return "+ (NSString *)\(variableNameToken)" } 71 | var booleanDeclaration: String { return "+ (BOOL)\(variableNameToken)" } 72 | var urlDeclaration: String { return "+ (NSURL *)\(variableNameToken)" } 73 | var customDeclaration: String { return "+ (\(customTypeToken))\(variableNameToken)" } 74 | var headerImportStatements: String { return "#import \n\n" } 75 | 76 | // MARK: - ImplementationTemplate 77 | 78 | var outputImplementationFileName: String { return "\(optionsParser.outputClassDirectory)/\(optionsParser.outputClassName).m" } 79 | 80 | var implementationImportStatements: String { return "#import \"\(optionsParser.outputClassName).h\"" } 81 | 82 | var implementationBody: String { return "\n\n@implementation \(optionsParser.outputClassName) \n\(bodyToken)\n@end\n" } 83 | 84 | var integerImplementation: String { return integerDeclaration + "\n{\n return @\(valueToken);\n}" } 85 | var doubleImplementation: String { return doubleDeclaration + "\n{\n return @\(valueToken);\n}" } 86 | var stringImplementation: String { return stringDeclaration + "\n{\n return @\"\(valueToken)\";\n}" } 87 | var booleanImplementation: String { return booleanDeclaration + "\n{\n return \(valueToken);\n}" } 88 | var trueString: String { return "YES" } 89 | var falseString: String { return "NO" } 90 | var urlImplementation: String { return urlDeclaration + "\n{\n return [NSURL URLWithString:@\"\(valueToken)\"];\n}" } 91 | var customImplementation: String { return customDeclaration + "\n{\n return \(valueToken);\n}" } 92 | } 93 | 94 | struct SwiftTemplate: ImplementationTemplate { 95 | 96 | let optionsParser: OptionsParser 97 | 98 | // MARK: - ImplementationTemplate 99 | 100 | var implementationImportStatements: String { return "import Foundation" } 101 | 102 | var outputImplementationFileName: String { return "\(optionsParser.outputClassDirectory)/\(optionsParser.outputClassName).swift" } 103 | 104 | var implementationBody: String { return "\n\nclass \(optionsParser.outputClassName) {\n\(bodyToken)\n}\n" } 105 | 106 | var integerImplementation: String { return " static let \(variableNameToken): Int = \(valueToken)" } 107 | var doubleImplementation: String { return " static let \(variableNameToken): Double = \(valueToken)" } 108 | var stringImplementation: String { return " static let \(variableNameToken): String = \"\(valueToken)\"" } 109 | var booleanImplementation: String { return " static let \(variableNameToken): Bool = \(valueToken)" } 110 | 111 | var trueString: String { return "true" } 112 | var falseString: String { return "false" } 113 | 114 | var urlImplementation: String { return " static let \(variableNameToken): URL = URL(string: \"\(valueToken)\")!" } 115 | var customImplementation: String { return " static let \(variableNameToken): \(customTypeToken) = \(valueToken)" } 116 | } 117 | -------------------------------------------------------------------------------- /configen/OptionsParser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionsParser.swift 3 | // configen 4 | // 5 | // Created by Dónal O'Brien on 10/08/2016. 6 | // Copyright © 2016 Kin + Carta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class OptionsParser { 12 | 13 | typealias Hint = (variableName: String, type: String) 14 | 15 | let appName: String 16 | let inputPlistFilePath: String 17 | let inputHintsFilePath: String 18 | let outputClassName: String 19 | let outputClassDirectory: String 20 | let isObjC: Bool 21 | 22 | init(appName: String) { 23 | let cli = CommandLineKit() 24 | let inputPlistFilePath = StringOption(shortFlag: "p", longFlag: "plist-path", required: true, helpMessage: "Path to the input plist file") 25 | let inputHintsFilePath = StringOption(shortFlag: "h", longFlag: "hints-path", required: true, helpMessage: "Path to the input hints file") 26 | let outputClassName = StringOption(shortFlag: "n", longFlag: "class-name", required: true, helpMessage: "The output config class name") 27 | let outputClassDirectory = StringOption(shortFlag: "o", longFlag: "output-directory", required: true, helpMessage: "The output config class directory") 28 | let useObjc = BoolOption(shortFlag: "c", longFlag: "objective-c", helpMessage: "Whether to generate Objective-C files instead of Swift") 29 | cli.addOptions(inputPlistFilePath, inputHintsFilePath, outputClassName, outputClassDirectory, useObjc) 30 | 31 | do { 32 | try cli.parse() 33 | } catch { 34 | cli.printUsage(error) 35 | fatalError() 36 | } 37 | 38 | self.appName = appName 39 | self.inputPlistFilePath = inputPlistFilePath.value! 40 | self.inputHintsFilePath = inputHintsFilePath.value! 41 | self.outputClassName = outputClassName.value! 42 | self.outputClassDirectory = outputClassDirectory.value! 43 | self.isObjC = useObjc.value 44 | } 45 | 46 | lazy var plistDictionary: [String: AnyObject] = { [unowned self] in 47 | let inputPlistFilePathURL = URL(fileURLWithPath: self.inputPlistFilePath) 48 | guard let data = try? Data(contentsOf: inputPlistFilePathURL) else { 49 | fatalError("No data at path: \(self.inputPlistFilePath)") 50 | } 51 | 52 | guard let plistDictionary = (try? PropertyListSerialization.propertyList(from: data, options: [], format: nil)) as? [String: AnyObject] else { 53 | fatalError("Failed to create plist") 54 | } 55 | 56 | return plistDictionary 57 | }() 58 | 59 | lazy var sortedHints: [Hint] = { [unowned self] in 60 | guard let hintsString = try? String(contentsOfFile: self.inputHintsFilePath, encoding: String.Encoding.utf8) else { 61 | fatalError("No data at path: \(self.inputHintsFilePath)") 62 | } 63 | 64 | var hints = [Hint]() 65 | let hintLines = hintsString.components(separatedBy: CharacterSet.newlines) 66 | for hintLine in hintLines where hintLine.trimmed.count > 0 { 67 | let separatedHints = hintLine.components(separatedBy: CharacterSet(charactersIn: ":")).map { $0.trimmed } 68 | guard separatedHints.count == 2 else { 69 | fatalError("Expected \"variableName : Type\", instead of \"\(hintLine)\"") 70 | } 71 | hints.append(Hint(variableName: separatedHints[0], type: separatedHints[1])) 72 | } 73 | return hints.sorted(by: <) 74 | }() 75 | } 76 | -------------------------------------------------------------------------------- /configen/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // configen 4 | // 5 | // Created by Sam Dods on 29/09/2015. 6 | // Copyright © 2015 Kin + Carta. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let appName = (CommandLine.arguments.first! as NSString).lastPathComponent 12 | let parser = OptionsParser(appName: appName) 13 | let fileGenerator = FileGenerator(optionsParser: parser) 14 | 15 | if parser.isObjC { 16 | let template = ObjectiveCTemplate(optionsParser: parser) 17 | fileGenerator.generateHeaderFile(withTemplate: template) 18 | fileGenerator.generateImplementationFile(withTemplate: template) 19 | } else { 20 | let template = SwiftTemplate(optionsParser: parser) 21 | fileGenerator.generateImplementationFile(withTemplate: template) 22 | } 23 | --------------------------------------------------------------------------------