├── .gitignore ├── LICENSE ├── OptionKit.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── OptionKit.xccheckout └── xcshareddata │ └── xcschemes │ └── OptionKit.xcscheme ├── OptionKit ├── Core.swift ├── Info.plist └── OptionKit.h ├── OptionKitTests ├── CoreTests.swift └── Info.plist └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Pods 2 | *.xcuserdatad 3 | .DS_Store 4 | Carthage/** 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Alexandros Salazar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OptionKit.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7D50B47919D3A46600743806 /* OptionKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7D50B46D19D3A46600743806 /* OptionKit.framework */; }; 11 | 7D50B48019D3A46600743806 /* CoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D50B47F19D3A46600743806 /* CoreTests.swift */; }; 12 | 7D50B48A19D3A48500743806 /* Core.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7D50B48919D3A48500743806 /* Core.swift */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXContainerItemProxy section */ 16 | 7D50B47A19D3A46600743806 /* PBXContainerItemProxy */ = { 17 | isa = PBXContainerItemProxy; 18 | containerPortal = 7D50B46419D3A46600743806 /* Project object */; 19 | proxyType = 1; 20 | remoteGlobalIDString = 7D50B46C19D3A46600743806; 21 | remoteInfo = OptionKit; 22 | }; 23 | /* End PBXContainerItemProxy section */ 24 | 25 | /* Begin PBXCopyFilesBuildPhase section */ 26 | 7DABE47F1AC7786600D0017B /* CopyFiles */ = { 27 | isa = PBXCopyFilesBuildPhase; 28 | buildActionMask = 2147483647; 29 | dstPath = ""; 30 | dstSubfolderSpec = 10; 31 | files = ( 32 | ); 33 | runOnlyForDeploymentPostprocessing = 0; 34 | }; 35 | /* End PBXCopyFilesBuildPhase section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 7D50B46D19D3A46600743806 /* OptionKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OptionKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 7D50B47119D3A46600743806 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 7D50B47219D3A46600743806 /* OptionKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OptionKit.h; sourceTree = ""; }; 41 | 7D50B47819D3A46600743806 /* OptionKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OptionKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 7D50B47E19D3A46600743806 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | 7D50B47F19D3A46600743806 /* CoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreTests.swift; sourceTree = ""; }; 44 | 7D50B48919D3A48500743806 /* Core.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Core.swift; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 7D50B46919D3A46600743806 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | 7D50B47519D3A46600743806 /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | 7D50B47919D3A46600743806 /* OptionKit.framework in Frameworks */, 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | 7D50B46319D3A46600743806 = { 67 | isa = PBXGroup; 68 | children = ( 69 | 7D50B46F19D3A46600743806 /* OptionKit */, 70 | 7D50B47C19D3A46600743806 /* OptionKitTests */, 71 | 7D50B46E19D3A46600743806 /* Products */, 72 | ); 73 | sourceTree = ""; 74 | }; 75 | 7D50B46E19D3A46600743806 /* Products */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 7D50B46D19D3A46600743806 /* OptionKit.framework */, 79 | 7D50B47819D3A46600743806 /* OptionKitTests.xctest */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | 7D50B46F19D3A46600743806 /* OptionKit */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 7D50B47219D3A46600743806 /* OptionKit.h */, 88 | 7D50B47019D3A46600743806 /* Supporting Files */, 89 | 7D50B48919D3A48500743806 /* Core.swift */, 90 | ); 91 | path = OptionKit; 92 | sourceTree = ""; 93 | }; 94 | 7D50B47019D3A46600743806 /* Supporting Files */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 7D50B47119D3A46600743806 /* Info.plist */, 98 | ); 99 | name = "Supporting Files"; 100 | sourceTree = ""; 101 | }; 102 | 7D50B47C19D3A46600743806 /* OptionKitTests */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 7D50B47F19D3A46600743806 /* CoreTests.swift */, 106 | 7D50B47D19D3A46600743806 /* Supporting Files */, 107 | ); 108 | path = OptionKitTests; 109 | sourceTree = ""; 110 | }; 111 | 7D50B47D19D3A46600743806 /* Supporting Files */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 7D50B47E19D3A46600743806 /* Info.plist */, 115 | ); 116 | name = "Supporting Files"; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXHeadersBuildPhase section */ 122 | 7D50B46A19D3A46600743806 /* Headers */ = { 123 | isa = PBXHeadersBuildPhase; 124 | buildActionMask = 2147483647; 125 | files = ( 126 | ); 127 | runOnlyForDeploymentPostprocessing = 0; 128 | }; 129 | /* End PBXHeadersBuildPhase section */ 130 | 131 | /* Begin PBXNativeTarget section */ 132 | 7D50B46C19D3A46600743806 /* OptionKit */ = { 133 | isa = PBXNativeTarget; 134 | buildConfigurationList = 7D50B48319D3A46600743806 /* Build configuration list for PBXNativeTarget "OptionKit" */; 135 | buildPhases = ( 136 | 7D50B46819D3A46600743806 /* Sources */, 137 | 7D50B46919D3A46600743806 /* Frameworks */, 138 | 7D50B46A19D3A46600743806 /* Headers */, 139 | 7D50B46B19D3A46600743806 /* Resources */, 140 | ); 141 | buildRules = ( 142 | ); 143 | dependencies = ( 144 | ); 145 | name = OptionKit; 146 | productName = OptionKit; 147 | productReference = 7D50B46D19D3A46600743806 /* OptionKit.framework */; 148 | productType = "com.apple.product-type.framework"; 149 | }; 150 | 7D50B47719D3A46600743806 /* OptionKitTests */ = { 151 | isa = PBXNativeTarget; 152 | buildConfigurationList = 7D50B48619D3A46600743806 /* Build configuration list for PBXNativeTarget "OptionKitTests" */; 153 | buildPhases = ( 154 | 7D50B47419D3A46600743806 /* Sources */, 155 | 7D50B47519D3A46600743806 /* Frameworks */, 156 | 7D50B47619D3A46600743806 /* Resources */, 157 | 7DABE47F1AC7786600D0017B /* CopyFiles */, 158 | ); 159 | buildRules = ( 160 | ); 161 | dependencies = ( 162 | 7D50B47B19D3A46600743806 /* PBXTargetDependency */, 163 | ); 164 | name = OptionKitTests; 165 | productName = OptionKitTests; 166 | productReference = 7D50B47819D3A46600743806 /* OptionKitTests.xctest */; 167 | productType = "com.apple.product-type.bundle.unit-test"; 168 | }; 169 | /* End PBXNativeTarget section */ 170 | 171 | /* Begin PBXProject section */ 172 | 7D50B46419D3A46600743806 /* Project object */ = { 173 | isa = PBXProject; 174 | attributes = { 175 | LastSwiftMigration = 0700; 176 | LastSwiftUpdateCheck = 0700; 177 | LastUpgradeCheck = 0900; 178 | ORGANIZATIONNAME = nomothetis; 179 | TargetAttributes = { 180 | 7D50B46C19D3A46600743806 = { 181 | CreatedOnToolsVersion = 6.1; 182 | LastSwiftMigration = 0900; 183 | }; 184 | 7D50B47719D3A46600743806 = { 185 | CreatedOnToolsVersion = 6.1; 186 | LastSwiftMigration = 0900; 187 | }; 188 | }; 189 | }; 190 | buildConfigurationList = 7D50B46719D3A46600743806 /* Build configuration list for PBXProject "OptionKit" */; 191 | compatibilityVersion = "Xcode 3.2"; 192 | developmentRegion = English; 193 | hasScannedForEncodings = 0; 194 | knownRegions = ( 195 | en, 196 | ); 197 | mainGroup = 7D50B46319D3A46600743806; 198 | productRefGroup = 7D50B46E19D3A46600743806 /* Products */; 199 | projectDirPath = ""; 200 | projectRoot = ""; 201 | targets = ( 202 | 7D50B46C19D3A46600743806 /* OptionKit */, 203 | 7D50B47719D3A46600743806 /* OptionKitTests */, 204 | ); 205 | }; 206 | /* End PBXProject section */ 207 | 208 | /* Begin PBXResourcesBuildPhase section */ 209 | 7D50B46B19D3A46600743806 /* Resources */ = { 210 | isa = PBXResourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | 7D50B47619D3A46600743806 /* Resources */ = { 217 | isa = PBXResourcesBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | }; 223 | /* End PBXResourcesBuildPhase section */ 224 | 225 | /* Begin PBXSourcesBuildPhase section */ 226 | 7D50B46819D3A46600743806 /* Sources */ = { 227 | isa = PBXSourcesBuildPhase; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | 7D50B48A19D3A48500743806 /* Core.swift in Sources */, 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | }; 234 | 7D50B47419D3A46600743806 /* Sources */ = { 235 | isa = PBXSourcesBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | 7D50B48019D3A46600743806 /* CoreTests.swift in Sources */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXSourcesBuildPhase section */ 243 | 244 | /* Begin PBXTargetDependency section */ 245 | 7D50B47B19D3A46600743806 /* PBXTargetDependency */ = { 246 | isa = PBXTargetDependency; 247 | target = 7D50B46C19D3A46600743806 /* OptionKit */; 248 | targetProxy = 7D50B47A19D3A46600743806 /* PBXContainerItemProxy */; 249 | }; 250 | /* End PBXTargetDependency section */ 251 | 252 | /* Begin XCBuildConfiguration section */ 253 | 7D50B48119D3A46600743806 /* Debug */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ALWAYS_SEARCH_USER_PATHS = NO; 257 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 258 | CLANG_CXX_LIBRARY = "libc++"; 259 | CLANG_ENABLE_MODULES = YES; 260 | CLANG_ENABLE_OBJC_ARC = YES; 261 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 262 | CLANG_WARN_BOOL_CONVERSION = YES; 263 | CLANG_WARN_COMMA = YES; 264 | CLANG_WARN_CONSTANT_CONVERSION = YES; 265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 266 | CLANG_WARN_EMPTY_BODY = YES; 267 | CLANG_WARN_ENUM_CONVERSION = YES; 268 | CLANG_WARN_INFINITE_RECURSION = YES; 269 | CLANG_WARN_INT_CONVERSION = YES; 270 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 271 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 272 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 273 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 274 | CLANG_WARN_STRICT_PROTOTYPES = YES; 275 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 276 | CLANG_WARN_UNREACHABLE_CODE = YES; 277 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 278 | COPY_PHASE_STRIP = NO; 279 | CURRENT_PROJECT_VERSION = 1; 280 | ENABLE_STRICT_OBJC_MSGSEND = YES; 281 | ENABLE_TESTABILITY = YES; 282 | GCC_C_LANGUAGE_STANDARD = gnu99; 283 | GCC_DYNAMIC_NO_PIC = NO; 284 | GCC_NO_COMMON_BLOCKS = YES; 285 | GCC_OPTIMIZATION_LEVEL = 0; 286 | GCC_PREPROCESSOR_DEFINITIONS = ( 287 | "DEBUG=1", 288 | "$(inherited)", 289 | ); 290 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 291 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 292 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 293 | GCC_WARN_UNDECLARED_SELECTOR = YES; 294 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 295 | GCC_WARN_UNUSED_FUNCTION = YES; 296 | GCC_WARN_UNUSED_VARIABLE = YES; 297 | MACOSX_DEPLOYMENT_TARGET = 10.9; 298 | MTL_ENABLE_DEBUG_INFO = YES; 299 | ONLY_ACTIVE_ARCH = YES; 300 | SDKROOT = macosx; 301 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 302 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 303 | VERSIONING_SYSTEM = "apple-generic"; 304 | VERSION_INFO_PREFIX = ""; 305 | }; 306 | name = Debug; 307 | }; 308 | 7D50B48219D3A46600743806 /* Release */ = { 309 | isa = XCBuildConfiguration; 310 | buildSettings = { 311 | ALWAYS_SEARCH_USER_PATHS = NO; 312 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 313 | CLANG_CXX_LIBRARY = "libc++"; 314 | CLANG_ENABLE_MODULES = YES; 315 | CLANG_ENABLE_OBJC_ARC = YES; 316 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 317 | CLANG_WARN_BOOL_CONVERSION = YES; 318 | CLANG_WARN_COMMA = YES; 319 | CLANG_WARN_CONSTANT_CONVERSION = YES; 320 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 321 | CLANG_WARN_EMPTY_BODY = YES; 322 | CLANG_WARN_ENUM_CONVERSION = YES; 323 | CLANG_WARN_INFINITE_RECURSION = YES; 324 | CLANG_WARN_INT_CONVERSION = YES; 325 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 326 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 328 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 329 | CLANG_WARN_STRICT_PROTOTYPES = YES; 330 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 331 | CLANG_WARN_UNREACHABLE_CODE = YES; 332 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 333 | COPY_PHASE_STRIP = YES; 334 | CURRENT_PROJECT_VERSION = 1; 335 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 336 | ENABLE_NS_ASSERTIONS = NO; 337 | ENABLE_STRICT_OBJC_MSGSEND = YES; 338 | GCC_C_LANGUAGE_STANDARD = gnu99; 339 | GCC_NO_COMMON_BLOCKS = YES; 340 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 341 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 342 | GCC_WARN_UNDECLARED_SELECTOR = YES; 343 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 344 | GCC_WARN_UNUSED_FUNCTION = YES; 345 | GCC_WARN_UNUSED_VARIABLE = YES; 346 | MACOSX_DEPLOYMENT_TARGET = 10.9; 347 | MTL_ENABLE_DEBUG_INFO = NO; 348 | SDKROOT = macosx; 349 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 350 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 351 | VERSIONING_SYSTEM = "apple-generic"; 352 | VERSION_INFO_PREFIX = ""; 353 | }; 354 | name = Release; 355 | }; 356 | 7D50B48419D3A46600743806 /* Debug */ = { 357 | isa = XCBuildConfiguration; 358 | buildSettings = { 359 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 360 | CLANG_ENABLE_MODULES = YES; 361 | COMBINE_HIDPI_IMAGES = YES; 362 | CURRENT_PROJECT_VERSION = 0.0.1; 363 | DEFINES_MODULE = YES; 364 | DYLIB_COMPATIBILITY_VERSION = 0; 365 | DYLIB_CURRENT_VERSION = 0.0.1; 366 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 367 | FRAMEWORK_SEARCH_PATHS = ( 368 | "$(inherited)", 369 | "$(PROJECT_DIR)/Carthage/Build/Mac", 370 | ); 371 | FRAMEWORK_VERSION = 0; 372 | INFOPLIST_FILE = OptionKit/Info.plist; 373 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 374 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 375 | PRODUCT_BUNDLE_IDENTIFIER = "org.nomothetis.$(PRODUCT_NAME:rfc1034identifier)"; 376 | PRODUCT_NAME = "$(TARGET_NAME)"; 377 | SKIP_INSTALL = YES; 378 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 379 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 380 | SWIFT_VERSION = 4.0; 381 | }; 382 | name = Debug; 383 | }; 384 | 7D50B48519D3A46600743806 /* Release */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 388 | CLANG_ENABLE_MODULES = YES; 389 | COMBINE_HIDPI_IMAGES = YES; 390 | CURRENT_PROJECT_VERSION = 0.0.1; 391 | DEFINES_MODULE = YES; 392 | DYLIB_COMPATIBILITY_VERSION = 0; 393 | DYLIB_CURRENT_VERSION = 0.0.1; 394 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 395 | FRAMEWORK_SEARCH_PATHS = ( 396 | "$(inherited)", 397 | "$(PROJECT_DIR)/Carthage/Build/Mac", 398 | ); 399 | FRAMEWORK_VERSION = 0; 400 | INFOPLIST_FILE = OptionKit/Info.plist; 401 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 402 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; 403 | PRODUCT_BUNDLE_IDENTIFIER = "org.nomothetis.$(PRODUCT_NAME:rfc1034identifier)"; 404 | PRODUCT_NAME = "$(TARGET_NAME)"; 405 | SKIP_INSTALL = YES; 406 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 407 | SWIFT_VERSION = 4.0; 408 | VALIDATE_PRODUCT = YES; 409 | }; 410 | name = Release; 411 | }; 412 | 7D50B48719D3A46600743806 /* Debug */ = { 413 | isa = XCBuildConfiguration; 414 | buildSettings = { 415 | COMBINE_HIDPI_IMAGES = YES; 416 | FRAMEWORK_SEARCH_PATHS = ( 417 | "$(DEVELOPER_FRAMEWORKS_DIR)", 418 | "$(inherited)", 419 | "$(PROJECT_DIR)/Carthage/Build/Mac", 420 | ); 421 | GCC_PREPROCESSOR_DEFINITIONS = ( 422 | "DEBUG=1", 423 | "$(inherited)", 424 | ); 425 | INFOPLIST_FILE = OptionKitTests/Info.plist; 426 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 427 | PRODUCT_BUNDLE_IDENTIFIER = "org.nomothetis.$(PRODUCT_NAME:rfc1034identifier)"; 428 | PRODUCT_NAME = "$(TARGET_NAME)"; 429 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 430 | SWIFT_VERSION = 4.0; 431 | }; 432 | name = Debug; 433 | }; 434 | 7D50B48819D3A46600743806 /* Release */ = { 435 | isa = XCBuildConfiguration; 436 | buildSettings = { 437 | COMBINE_HIDPI_IMAGES = YES; 438 | FRAMEWORK_SEARCH_PATHS = ( 439 | "$(DEVELOPER_FRAMEWORKS_DIR)", 440 | "$(inherited)", 441 | "$(PROJECT_DIR)/Carthage/Build/Mac", 442 | ); 443 | INFOPLIST_FILE = OptionKitTests/Info.plist; 444 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; 445 | PRODUCT_BUNDLE_IDENTIFIER = "org.nomothetis.$(PRODUCT_NAME:rfc1034identifier)"; 446 | PRODUCT_NAME = "$(TARGET_NAME)"; 447 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 448 | SWIFT_VERSION = 4.0; 449 | }; 450 | name = Release; 451 | }; 452 | /* End XCBuildConfiguration section */ 453 | 454 | /* Begin XCConfigurationList section */ 455 | 7D50B46719D3A46600743806 /* Build configuration list for PBXProject "OptionKit" */ = { 456 | isa = XCConfigurationList; 457 | buildConfigurations = ( 458 | 7D50B48119D3A46600743806 /* Debug */, 459 | 7D50B48219D3A46600743806 /* Release */, 460 | ); 461 | defaultConfigurationIsVisible = 0; 462 | defaultConfigurationName = Release; 463 | }; 464 | 7D50B48319D3A46600743806 /* Build configuration list for PBXNativeTarget "OptionKit" */ = { 465 | isa = XCConfigurationList; 466 | buildConfigurations = ( 467 | 7D50B48419D3A46600743806 /* Debug */, 468 | 7D50B48519D3A46600743806 /* Release */, 469 | ); 470 | defaultConfigurationIsVisible = 0; 471 | defaultConfigurationName = Release; 472 | }; 473 | 7D50B48619D3A46600743806 /* Build configuration list for PBXNativeTarget "OptionKitTests" */ = { 474 | isa = XCConfigurationList; 475 | buildConfigurations = ( 476 | 7D50B48719D3A46600743806 /* Debug */, 477 | 7D50B48819D3A46600743806 /* Release */, 478 | ); 479 | defaultConfigurationIsVisible = 0; 480 | defaultConfigurationName = Release; 481 | }; 482 | /* End XCConfigurationList section */ 483 | }; 484 | rootObject = 7D50B46419D3A46600743806 /* Project object */; 485 | } 486 | -------------------------------------------------------------------------------- /OptionKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /OptionKit.xcodeproj/project.xcworkspace/xcshareddata/OptionKit.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 1247A27A-410C-4F61-AE55-035937794CC4 9 | IDESourceControlProjectName 10 | OptionKit 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | B141D43A78807E302A086A84B4BECA9271DA3018 14 | https://github.com/nomothetis/OptionKit.git 15 | 16 | IDESourceControlProjectPath 17 | OptionKit.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | B141D43A78807E302A086A84B4BECA9271DA3018 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/nomothetis/OptionKit.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | B141D43A78807E302A086A84B4BECA9271DA3018 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | B141D43A78807E302A086A84B4BECA9271DA3018 36 | IDESourceControlWCCName 37 | OptionKit 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /OptionKit.xcodeproj/xcshareddata/xcschemes/OptionKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 80 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 98 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /OptionKit/Core.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Core.swift 3 | // OptionKit 4 | // 5 | // Created by Salazar, Alexandros on 9/24/14. 6 | // Copyright (c) 2014 nomothetis. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /** 12 | Eventually intends to be a getopt-compatible option parser. 13 | */ 14 | 15 | public enum OptionTrigger : Equatable, CustomDebugStringConvertible, Hashable { 16 | case short(Character) 17 | case long(String) 18 | case mixed(Character, String) 19 | 20 | public var debugDescription:String { 21 | get { 22 | switch self { 23 | case .short(let c): 24 | return "-\(c)" 25 | case .long(let str): 26 | return "--\(str)" 27 | case .mixed(let c, let str): 28 | return "[-\(c)|--\(str)]" 29 | } 30 | } 31 | } 32 | 33 | public var usageDescription:String { 34 | get { 35 | switch self { 36 | case .short(let c): 37 | return "[-\(c)]" 38 | case .long(let str): 39 | return "[--\(str)]" 40 | case .mixed(let c, let str): 41 | return "[-\(c)|--\(str)]" 42 | } 43 | } 44 | } 45 | 46 | public var hashValue:Int { 47 | get { 48 | return self.usageDescription.hashValue 49 | } 50 | } 51 | } 52 | 53 | /// Describes an option for the parser. 54 | /// 55 | /// An Option consists of a trigger and a number of required parameters, which 56 | /// defaults to zero. It also has includes a description, which is empty by default. The 57 | /// description does not affect equality. 58 | public struct Option : Equatable, CustomStringConvertible, CustomDebugStringConvertible, Hashable { 59 | let trigger:OptionTrigger 60 | let numberOfParameters:Int 61 | 62 | /// The description of how the option works. This description has no bearing on equality. 63 | let helpDescription:String 64 | 65 | /// The designated initializer 66 | /// 67 | /// Creates an option definition from a trigger and a required number of parameters. 68 | /// 69 | /// - parameter trigger: the trigger that the parser will use to decide the option is 70 | /// being called. 71 | /// - parameter numberOfParameters: the number of required parameters. Defaults to 0. 72 | /// - parameter helpDescription: the string that will be displayed when the -h flag is triggered. 73 | /// 74 | /// - returns: An OptionDefinition suitable for use by an OptionParser 75 | public init(trigger trig:OptionTrigger, numberOfParameters num:Int = 0, helpDescription desc:String = "") { 76 | self.trigger = trig 77 | self.numberOfParameters = num 78 | self.helpDescription = desc 79 | } 80 | 81 | /// Determines if the given string matches this trigger. 82 | /// 83 | /// - parameter str: the string. 84 | /// - returns: `true` if the string matches this option's trigger, `false` otherwise. 85 | func matches(_ str:String) -> Bool { 86 | switch self.trigger { 87 | case .short(let char): 88 | return str == "-" + String(char) 89 | case .long(let longKey): 90 | return str == "--" + longKey 91 | case .mixed(let char, let longKey): 92 | return (str == "--" + longKey) || str == "-" + String(char) 93 | } 94 | } 95 | 96 | static func isValidOptionString(_ str:String) -> Bool{ 97 | let length = str.characters.count 98 | if length < 2 { 99 | return false 100 | } 101 | 102 | if length == 2 { 103 | if str[str.startIndex] != "-" { 104 | return false 105 | } 106 | 107 | return str[str.characters.index(str.startIndex, offsetBy: 1)] != "-" 108 | } 109 | 110 | /* Okay, count greater than 2. Full option! */ 111 | return str[str.startIndex ... str.characters.index(str.startIndex, offsetBy: 1)] == "--" 112 | } 113 | 114 | public var description: String { 115 | get { 116 | return "\(self.trigger) requires \(self.numberOfParameters) parameters" 117 | } 118 | } 119 | 120 | public var debugDescription:String { 121 | get { 122 | return "{ Opt: \(self.trigger), \(self.numberOfParameters) }" 123 | } 124 | } 125 | 126 | public var hashValue:Int { 127 | get { 128 | return self.debugDescription.hashValue 129 | } 130 | } 131 | } 132 | 133 | private struct OptionData : Equatable, CustomStringConvertible, CustomDebugStringConvertible { 134 | let option:Option 135 | let parameters:[String] 136 | 137 | fileprivate init(definition def:Option, parameters params:[String] = []) { 138 | self.option = def 139 | self.parameters = params 140 | } 141 | 142 | fileprivate var isValid:Bool { 143 | get { 144 | return parameters.count == option.numberOfParameters 145 | } 146 | } 147 | 148 | fileprivate var description: String { 149 | get { 150 | return "\(self.option), parameters \(parameters) are given" 151 | } 152 | } 153 | 154 | fileprivate var debugDescription:String { 155 | get { 156 | return "{ OptionData:\n \(self.option.debugDescription)\n \(self.parameters)}" 157 | } 158 | } 159 | } 160 | 161 | /// Represents the result of a successful parse. 162 | /// 163 | /// The dictionary is a mapping of encountered options to their parameters, where no-parameter 164 | /// options map to an empty array.. The array is the list of remaining parameters. 165 | public typealias ParseData = ([Option:[String]], [String]) 166 | 167 | /// The option parser. 168 | /// 169 | /// This is the workhorse of the library. It is initialized with a list of options and parses an 170 | /// array of strings assumed to be the call paramerers. 171 | public struct OptionParser { 172 | public let definitions:[Option] 173 | 174 | /// Initializes the parser. 175 | /// 176 | /// By default, each parser has an option triggered by `-h` and `--help`. It also provides 177 | /// a console-displayable string of the options via the `helpStringForCommandName` method. 178 | /// 179 | /// - parameter definitions: the option definitions to parse for. 180 | /// - returns: a parser 181 | public init(definitions defs:[Option] = []) { 182 | let helpOption = Option(trigger:.mixed("h", "help"), helpDescription: "Display command help.") 183 | if defs.contains(helpOption) { 184 | self.definitions = defs 185 | } else { 186 | self.definitions = defs + [helpOption] 187 | } 188 | } 189 | 190 | /// Returns a default help string based on the passed command name and the existing options. 191 | /// 192 | /// The string is suitable to be displayed on the command line and consists of multiple lines, 193 | /// all under 80 characters. 194 | /// 195 | /// - parameter commandName: the name of the command. 196 | /// - returns: an English-language string suitable for command-line display. 197 | public func helpStringForCommandName(_ commandName:String) -> String { 198 | let maximumLineWidth = 80 199 | 200 | // The leading string, to properly indent. 201 | var leadingString = " " 202 | for _ in 0.. ParseData { 233 | let normalizedParams = OptionParser.normalizeParameters(parameters) 234 | let firstCall = ([OptionData](), [String]()) 235 | 236 | let (parsedOptions, args) = try normalizedParams.reduce(firstCall) { tuple, next in 237 | 238 | let (optArray, args) = tuple 239 | /* First check if we are in the process of parsing an option with parameters. */ 240 | if let lastOpt = optArray.last { 241 | 242 | /* Since we have parsed an option already, let's check if it needs more parameters. */ 243 | if lastOpt.option.numberOfParameters > lastOpt.parameters.count { 244 | 245 | /* The option expects parameters; parameters cannot look like option triggers. */ 246 | if (Option.isValidOptionString(next)) { 247 | throw OptionKitError.invalidOption(description: "Option \(lastOpt) before option \(next) was declared") 248 | } 249 | 250 | /* Sanity prevails, the next element is not an option trigger. */ 251 | let shortOptArray = optArray[0 ..< optArray.count - 1] 252 | let newOption = OptionData(definition: lastOpt.option, parameters: lastOpt.parameters + [next]) 253 | return (shortOptArray + [newOption], args) 254 | } 255 | 256 | /* No need for more parameters; parse the next option. */ 257 | return try self.parseNewFlag(tuple, flagCandidate: next) 258 | } 259 | 260 | /* This is the first option. Parse it! */ 261 | return try self.parseNewFlag(tuple, flagCandidate: next) 262 | } 263 | 264 | // We need to carry out one last check. Because of the way the above reduce works, it's 265 | // possible the very last option is in fact not valid. There are ways around that, but 266 | // that's actually slower than just checking the last element at the end. 267 | if let lastOpt = parsedOptions.last, !lastOpt.isValid { 268 | throw OptionKitError.invalidOption(description: "Option \(lastOpt)") 269 | } 270 | 271 | var dict = [Option:[String]]() 272 | for opt in parsedOptions { 273 | dict[opt.option] = opt.parameters 274 | } 275 | return (dict, args) 276 | } 277 | 278 | fileprivate func parseNewFlag(_ current: ([OptionData], [String]), flagCandidate:String) throws -> ([OptionData], [String]) { 279 | /* Does the next element want to be a flag? */ 280 | if Option.isValidOptionString(flagCandidate) { 281 | for flag in self.definitions { 282 | if flag.matches(flagCandidate) { 283 | let newOption = OptionData(definition: flag, parameters: []) 284 | return (current.0 + [newOption], current.1) 285 | } 286 | } 287 | 288 | throw OptionKitError.invalidOption(description: "Invalid option: \(flagCandidate)") 289 | } 290 | 291 | return (current.0, current.1 + [flagCandidate]) 292 | } 293 | 294 | static func normalizeParameters(_ parameters:[String]) -> [String] { 295 | return parameters.reduce([String]()) { memo, next in 296 | let index = next.characters.index(next.startIndex, offsetBy: 0) 297 | if next[index] != "-" { 298 | return memo + [next] 299 | } 300 | 301 | let secondIndex = next.characters.index(index, offsetBy: 1) 302 | if next[secondIndex] == "-" { 303 | /* Assume everything that follows is valid. */ 304 | return memo + [next] 305 | } 306 | 307 | /* Okay, we have one or more single-character flags. */ 308 | var params = [String]() 309 | for char in next[secondIndex.. Bool { 321 | switch (lhs, rhs) { 322 | case (.short(let x), .short(let y)): 323 | return x == y 324 | case (.long(let x), .long(let y)): 325 | return x == y 326 | case (.mixed(let x1, let x2), .mixed(let y1, let y2)): 327 | return (x1 == y1) && (x2 == y2) 328 | default: 329 | return false 330 | } 331 | } 332 | 333 | public func ==(lhs:Option, rhs:Option) -> Bool { 334 | return lhs.hashValue == rhs.hashValue 335 | } 336 | 337 | private func ==(lhs:OptionData, rhs:OptionData) -> Bool { 338 | return (lhs.option == rhs.option) && (lhs.parameters == rhs.parameters) 339 | } 340 | 341 | /// MARK: - Error types 342 | 343 | public enum OptionKitError: Error { 344 | case invalidOption(description: String) 345 | } 346 | 347 | -------------------------------------------------------------------------------- /OptionKit/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 nomothetis. All rights reserved. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /OptionKit/OptionKit.h: -------------------------------------------------------------------------------- 1 | // 2 | // OptionKit.h 3 | // OptionKit 4 | // 5 | // Created by Salazar, Alexandros on 9/24/14. 6 | // Copyright (c) 2014 nomothetis. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for OptionKit. 12 | FOUNDATION_EXPORT double OptionKitVersionNumber; 13 | 14 | //! Project version string for OptionKit. 15 | FOUNDATION_EXPORT const unsigned char OptionKitVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /OptionKitTests/CoreTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OptionKitTests.swift 3 | // OptionKitTests 4 | // 5 | // Created by Salazar, Alexandros on 9/24/14. 6 | // Copyright (c) 2014 nomothetis. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import XCTest 11 | import OptionKit 12 | 13 | class OptionKitTests: XCTestCase { 14 | 15 | func testParserWithNoOption() { 16 | let parser = OptionParser() 17 | 18 | do { 19 | let opts = try parser.parse(["--hello"]) 20 | XCTFail("Empty parser should process no options other than -h|--help; instead processed: \(opts)") 21 | } catch let OptionKitError.invalidOption(description: description) { 22 | XCTAssertEqual(description, "Invalid option: --hello", "Incorrect error description") 23 | } catch { 24 | XCTFail("Parsing failed with unexpected error: \(error)") 25 | } 26 | 27 | do { 28 | let opts = try parser.parse(["-v"]) 29 | XCTFail("Empty parser should process no options other than -h|--help; instead processed: \(opts)") 30 | } catch let OptionKitError.invalidOption(description: description) { 31 | XCTAssertEqual(description, "Invalid option: -v", "Incorrect error description") 32 | } catch { 33 | XCTFail("Parsing failed with unexpected error: \(error)") 34 | } 35 | } 36 | 37 | func testParserWithNoParameterShortOption() { 38 | let optionDescription = Option(trigger:.short("h")) 39 | let parser = OptionParser(definitions:[optionDescription]) 40 | 41 | var params = ["h"] 42 | do { 43 | let (options, rest) = try parser.parse(params) 44 | XCTAssertEqual(rest, ["h"], "Incorrect non-option parameters") 45 | XCTAssertEqual(0, options.count, "Nothing should have been parsed.") 46 | } catch { 47 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(error)") 48 | } 49 | 50 | params = ["-h"] 51 | do { 52 | let (options, _) = try parser.parse(params) 53 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 54 | XCTAssertNotNil(options[optionDescription], "Incorrect option parsed.") 55 | } catch { 56 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(error)") 57 | } 58 | 59 | params = ["-i"] 60 | do { 61 | try parser.parse(params) 62 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 63 | } catch let OptionKitError.invalidOption(description: description) { 64 | XCTAssertEqual(description, "Invalid option: -i", "Incorrect error description") 65 | } catch { 66 | XCTFail("Parsing failed with unexpected error: \(error)") 67 | } 68 | 69 | params = ["-h", "--bad-option"] 70 | do { 71 | try parser.parse(params) 72 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 73 | } catch let OptionKitError.invalidOption(description: description) { 74 | XCTAssertEqual(description, "Invalid option: --bad-option", "Incorrect error description") 75 | } catch { 76 | XCTFail("Parsing failed with unexpected error: \(error)") 77 | } 78 | 79 | params = ["-h", "-n"] 80 | do { 81 | try parser.parse(params) 82 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 83 | } catch let OptionKitError.invalidOption(description: description) { 84 | XCTAssertEqual(description, "Invalid option: -n", "Incorrect error description") 85 | } catch { 86 | XCTFail("Parsing failed with unexpected error: \(error)") 87 | } 88 | 89 | // Check that order doesn't matter. 90 | params = ["-h", "lastIsBest"] 91 | do { 92 | let (options, rest) = try parser.parse(params) 93 | XCTAssertEqual(rest, ["lastIsBest"], "Incorrect non-option parameters") 94 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 95 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 96 | } catch { 97 | XCTFail("Parser \(parser) should not have failed to parse \(params) with error: \(error)") 98 | } 99 | 100 | params = ["firstRules", "-h"] 101 | do { 102 | let (options, rest) = try parser.parse(params) 103 | XCTAssertEqual(rest, ["firstRules"], "Incorrect non-option parameters") 104 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 105 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 106 | } catch { 107 | XCTFail("Parser \(parser) should not have failed to parse \(params) with error: \(error)") 108 | } 109 | 110 | params = ["sandwiches", "-h", "rock"] 111 | do { 112 | let (options, rest) = try parser.parse(params) 113 | XCTAssertEqual(rest, ["sandwiches", "rock"], "Incorrect non-option parameters") 114 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 115 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 116 | } catch { 117 | XCTFail("Parser \(parser) should not have failed to parse \(params) with error: \(error)") 118 | } 119 | } 120 | 121 | func testInvalidCallsOfNoParameterShortOption() { 122 | let optionDescription = Option(trigger:.short("h")) 123 | let parser = OptionParser(definitions:[optionDescription]) 124 | 125 | let params = ["--hello"] 126 | do { 127 | try parser.parse(params) 128 | XCTFail("Parsing should not have succeeded for parser: \(parser), options: \(params)") 129 | } catch let OptionKitError.invalidOption(description: description) { 130 | XCTAssertEqual(description, "Invalid option: --hello", "Incorrect error description") 131 | } catch { 132 | XCTFail("Parsing failed with unexpected error: \(error)") 133 | } 134 | } 135 | 136 | 137 | func testParserWithNoParameterLongOption() { 138 | let optionDescription = Option(trigger:.long("hello")) 139 | let parser = OptionParser(definitions:[optionDescription]) 140 | 141 | var params = ["hello"] 142 | do { 143 | let (options, rest) = try parser.parse(params) 144 | XCTAssertEqual(rest, ["hello"], "Incorrect non-option parameters") 145 | XCTAssertEqual(0, options.count, "Nothing should have been parsed.") 146 | } catch { 147 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 148 | } 149 | 150 | params = ["--hello"] 151 | do { 152 | let (options, rest) = try parser.parse(params) 153 | XCTAssertEqual(rest, [], "Incorrect non-option parameters") 154 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 155 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 156 | } catch { 157 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 158 | } 159 | 160 | params = ["-i"] 161 | do { 162 | try parser.parse(params) 163 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 164 | } catch let OptionKitError.invalidOption(description: description) { 165 | XCTAssertEqual(description, "Invalid option: -i", "Incorrect error description") 166 | } catch { 167 | XCTFail("Parsing failed with unexpected error: \(error)") 168 | } 169 | 170 | params = ["--hello", "--bad-option"] 171 | do { 172 | try parser.parse(params) 173 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 174 | } catch let OptionKitError.invalidOption(description: description) { 175 | XCTAssertEqual(description, "Invalid option: --bad-option", "Incorrect error description") 176 | } catch { 177 | XCTFail("Parsing failed with unexpected error: \(error)") 178 | } 179 | 180 | params = ["--hello", "-n"] 181 | do { 182 | try parser.parse(params) 183 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 184 | } catch let OptionKitError.invalidOption(description: description) { 185 | XCTAssertEqual(description, "Invalid option: -n", "Incorrect error description") 186 | } catch { 187 | XCTFail("Parsing failed with unexpected error: \(error)") 188 | } 189 | 190 | // Check that order doesn't matter. 191 | params = ["--hello", "lastIsBest"] 192 | do { 193 | let (options, rest) = try parser.parse(params) 194 | XCTAssertEqual(rest, ["lastIsBest"], "Incorrect non-option parameters") 195 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 196 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 197 | } catch { 198 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 199 | } 200 | 201 | params = ["firstRules", "--hello"] 202 | do { 203 | let (options, rest) = try parser.parse(params) 204 | XCTAssertEqual(rest, ["firstRules"], "Incorrect non-option parameters") 205 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 206 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 207 | } catch { 208 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 209 | } 210 | 211 | params = ["sandwiches", "--hello", "rock"] 212 | do { 213 | let (options, rest) = try parser.parse(params) 214 | XCTAssertEqual(rest, ["sandwiches", "rock"], "Incorrect non-option parameters") 215 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 216 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 217 | } catch { 218 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 219 | } 220 | } 221 | 222 | func testInvalidCallsOfNoParamterLongOption() { 223 | let optionDescription = Option(trigger:.long("vroom"), numberOfParameters:0) 224 | let parser = OptionParser(definitions:[optionDescription]) 225 | 226 | let params = ["-v"] 227 | do { 228 | try parser.parse(params) 229 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 230 | } catch let OptionKitError.invalidOption(description: description) { 231 | XCTAssertEqual(description, "Invalid option: -v", "Incorrect error description") 232 | } catch { 233 | XCTFail("Parsing failed with unexpected error: \(error)") 234 | } 235 | } 236 | 237 | func testParserWithNoParameterMixedOption() { 238 | let optionDescription = Option(trigger:.mixed("h", "hello")) 239 | let parser = OptionParser(definitions:[optionDescription]) 240 | 241 | var params = ["h"] 242 | do { 243 | let (options, rest) = try parser.parse(params) 244 | XCTAssertEqual(rest, ["h"], "Incorrect non-option parameters") 245 | XCTAssertEqual(0, options.count, "No options should have been parsed.") 246 | } catch { 247 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 248 | } 249 | 250 | params = ["-h"] 251 | do { 252 | let (options, rest) = try parser.parse(params) 253 | XCTAssertEqual(rest, [], "Incorrect non-option parameters") 254 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 255 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 256 | } catch { 257 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 258 | } 259 | 260 | params = ["-i"] 261 | do { 262 | try parser.parse(params) 263 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 264 | } catch let OptionKitError.invalidOption(description: description) { 265 | XCTAssertEqual(description, "Invalid option: -i", "Incorrect error description") 266 | } catch { 267 | XCTFail("Parsing failed with unexpected error: \(error)") 268 | } 269 | 270 | params = ["-h", "--bad-option"] 271 | do { 272 | try parser.parse(params) 273 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 274 | } catch let OptionKitError.invalidOption(description: description) { 275 | XCTAssertEqual(description, "Invalid option: --bad-option", "Incorrect error description") 276 | } catch { 277 | XCTFail("Parsing failed with unexpected error: \(error)") 278 | } 279 | 280 | params = ["-h", "-n"] 281 | do { 282 | try parser.parse(params) 283 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 284 | } catch let OptionKitError.invalidOption(description: description) { 285 | XCTAssertEqual(description, "Invalid option: -n", "Incorrect error description") 286 | } catch { 287 | XCTFail("Parsing failed with unexpected error: \(error)") 288 | } 289 | 290 | // Check that order doesn't matter. 291 | params = ["-h", "lastIsBest"] 292 | do { 293 | let (options, rest) = try parser.parse(params) 294 | XCTAssertEqual(rest, ["lastIsBest"], "Incorrect non-option parameters") 295 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 296 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 297 | } catch { 298 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 299 | } 300 | 301 | params = ["firstRules", "-h"] 302 | do { 303 | let (options, rest) = try parser.parse(params) 304 | XCTAssertEqual(rest, ["firstRules"], "Incorrect non-option parameters") 305 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 306 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 307 | } catch { 308 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 309 | } 310 | 311 | params = ["sandwiches", "-h", "rock"] 312 | do { 313 | let (options, rest) = try parser.parse(params) 314 | XCTAssertEqual(rest, ["sandwiches", "rock"], "Incorrect non-option parameters") 315 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 316 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 317 | } catch { 318 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 319 | } 320 | 321 | // Check that the long option also works. 322 | 323 | params = ["--hello"] 324 | do { 325 | let (options, rest) = try parser.parse(params) 326 | XCTAssertEqual(rest, [], "Incorrect non-option parameters") 327 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 328 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 329 | } catch { 330 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 331 | } 332 | 333 | params = ["--hello", "--bad-option"] 334 | do { 335 | try parser.parse(params) 336 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 337 | } catch let OptionKitError.invalidOption(description: description) { 338 | XCTAssertEqual(description, "Invalid option: --bad-option", "Incorrect error description") 339 | } catch { 340 | XCTFail("Parsing failed with unexpected error: \(error)") 341 | } 342 | 343 | params = ["--hello", "-n"] 344 | do { 345 | try parser.parse(params) 346 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 347 | } catch let OptionKitError.invalidOption(description: description) { 348 | XCTAssertEqual(description, "Invalid option: -n", "Incorrect error description") 349 | } catch { 350 | XCTFail("Parsing failed with unexpected error: \(error)") 351 | } 352 | 353 | // Check that order doesn't matter. 354 | params = ["--hello", "lastIsBest"] 355 | do { 356 | let (options, rest) = try parser.parse(params) 357 | XCTAssertEqual(rest, ["lastIsBest"], "Incorrect non-option parameters") 358 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 359 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 360 | } catch { 361 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 362 | } 363 | 364 | params = ["firstRules", "--hello"] 365 | do { 366 | let (options, rest) = try parser.parse(params) 367 | XCTAssertEqual(rest, ["firstRules"], "Incorrect non-option parameters") 368 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 369 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 370 | } catch { 371 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 372 | } 373 | 374 | params = ["sandwiches", "--hello", "rock"] 375 | do { 376 | let (options, rest) = try parser.parse(params) 377 | XCTAssertEqual(rest, ["sandwiches", "rock"], "Incorrect non-option parameters") 378 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 379 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 380 | } catch { 381 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 382 | } 383 | } 384 | 385 | func testOptionWithParameters() { 386 | // One parameter. 387 | var optionDescription = Option(trigger:.mixed("h", "hello"), numberOfParameters:1) 388 | var parser = OptionParser(definitions:[optionDescription]) 389 | 390 | var params = ["-h", "world"] 391 | do { 392 | let (options, rest) = try parser.parse(params) 393 | XCTAssertEqual(rest, [], "Incorrect non-option parameters") 394 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 395 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 396 | } catch { 397 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 398 | } 399 | 400 | params = ["--hello", "world"] 401 | do { 402 | let (options, rest) = try parser.parse(params) 403 | XCTAssertEqual(rest, [], "Incorrect non-option parameters") 404 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 405 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 406 | } catch { 407 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 408 | } 409 | 410 | params = ["--hello"] 411 | do { 412 | try parser.parse(params) 413 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 414 | } catch let OptionKitError.invalidOption(description: description) { 415 | XCTAssertEqual(description, "Option [-h|--hello] requires 1 parameters, parameters [] are given", "Incorrect error description") 416 | } catch { 417 | XCTFail("Parsing failed with unexpected error: \(error)") 418 | } 419 | 420 | params = ["--hello", "--world"] 421 | do { 422 | try parser.parse(params) 423 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 424 | } catch let OptionKitError.invalidOption(description: description) { 425 | XCTAssertEqual(description, "Option [-h|--hello] requires 1 parameters, parameters [] are given before option --world was declared", "Incorrect error description") 426 | } catch { 427 | XCTFail("Parsing failed with unexpected error: \(error)") 428 | } 429 | 430 | params = ["--hello", "-w"] 431 | do { 432 | try parser.parse(params) 433 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 434 | } catch let OptionKitError.invalidOption(description: description) { 435 | XCTAssertEqual(description, "Option [-h|--hello] requires 1 parameters, parameters [] are given before option -w was declared", "Incorrect error description") 436 | } catch { 437 | XCTFail("Parsing failed with unexpected error: \(error)") 438 | } 439 | 440 | 441 | optionDescription = Option(trigger:.mixed("h", "hello"), numberOfParameters:3) 442 | parser = OptionParser(definitions:[optionDescription]) 443 | 444 | params = ["-h", "world", "of", "coke"] 445 | do { 446 | let (options, rest) = try parser.parse(params) 447 | XCTAssertEqual(rest, [], "Incorrect non-option parameters") 448 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 449 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 450 | } catch { 451 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 452 | } 453 | 454 | params = ["--hello", "world", "of", "coke"] 455 | do { 456 | let (options, rest) = try parser.parse(params) 457 | XCTAssertEqual(rest, [], "Incorrect non-option parameters") 458 | XCTAssertEqual(1, options.count, "Incorrect number of options parsed.") 459 | XCTAssertNotNil(options[optionDescription], "Parser \(parser) should have parsed \(params)") 460 | } catch { 461 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 462 | } 463 | 464 | params = ["--hello"] 465 | do { 466 | try parser.parse(params) 467 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 468 | } catch let OptionKitError.invalidOption(description: description) { 469 | XCTAssertEqual(description, "Option [-h|--hello] requires 3 parameters, parameters [] are given", "Incorrect error description") 470 | } catch { 471 | XCTFail("Parsing failed with unexpected error: \(error)") 472 | } 473 | 474 | params = ["--hello", "world"] 475 | do { 476 | try parser.parse(params) 477 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 478 | } catch let OptionKitError.invalidOption(description: description) { 479 | XCTAssertEqual(description, "Option [-h|--hello] requires 3 parameters, parameters [\"world\"] are given", "Incorrect error description") 480 | } catch { 481 | XCTFail("Parsing failed with unexpected error: \(error)") 482 | } 483 | 484 | params = ["--hello", "world", "of"] 485 | do { 486 | try parser.parse(params) 487 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 488 | } catch let OptionKitError.invalidOption(description: description) { 489 | XCTAssertEqual(description, "Option [-h|--hello] requires 3 parameters, parameters [\"world\", \"of\"] are given", "Incorrect error description") 490 | } catch { 491 | XCTFail("Parsing failed with unexpected error: \(error)") 492 | } 493 | } 494 | 495 | 496 | func testMixOfParametersAndNoParameters() { 497 | let optionDescription = Option(trigger:.mixed("h", "hello"), numberOfParameters:1) 498 | let optionDescription2 = Option(trigger:.mixed("p", "pom")) 499 | let optionDescription3 = Option(trigger:.mixed("n", "nom"), numberOfParameters:2) 500 | let parser = OptionParser(definitions:[optionDescription, optionDescription2, optionDescription3]) 501 | let expectedParameters1 = ["world"] 502 | let expectedParameters2 = [String]() 503 | let expectedParameters3 = ["boo", "hoo"] 504 | 505 | var params = ["--hello", "world", "of"] 506 | do { 507 | let (options, rest) = try parser.parse(params) 508 | XCTAssertEqual(rest, ["of"], "Incorrect non-option parameters") 509 | XCTAssertEqual(1, options.count, "Parser \(parser) should have parsed \(params)") 510 | if let optParams = options[optionDescription] { 511 | XCTAssertEqual(optParams, expectedParameters1, "Incorrect parameters for \(optionDescription)") 512 | } else { 513 | XCTFail("No parameters for option \(optionDescription)") 514 | } 515 | } catch { 516 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 517 | } 518 | 519 | params = ["--hello", "world"] 520 | do { 521 | let (options, rest) = try parser.parse(params) 522 | XCTAssertEqual(rest, [], "Incorrect non-option parameters") 523 | XCTAssertEqual(1, options.count, "Parser \(parser) should have parsed \(params)") 524 | if let optParams = options[optionDescription] { 525 | XCTAssertEqual(optParams, expectedParameters1, "Incorrect parameters for \(optionDescription)") 526 | } else { 527 | XCTFail("No parameters for option \(optionDescription)") 528 | } 529 | } catch { 530 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 531 | } 532 | 533 | params = ["--hello", "world", "-p"] 534 | do { 535 | let (options, rest) = try parser.parse(params) 536 | XCTAssertEqual(rest, [], "Incorrect non-option parameters") 537 | XCTAssertEqual(2, options.count, "Parser \(parser) should have parsed \(params)") 538 | if let optParams = options[optionDescription] { 539 | XCTAssertEqual(optParams, expectedParameters1, "Incorrect parameters for \(optionDescription)") 540 | } else { 541 | XCTFail("No parameters for option \(optionDescription)") 542 | } 543 | 544 | if let optParams2 = options[optionDescription2] { 545 | XCTAssertEqual(optParams2, expectedParameters2, "Incorrect parameters for \(optionDescription)") 546 | } else { 547 | XCTFail("No parameters for option \(optionDescription2)") 548 | } 549 | } catch { 550 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 551 | } 552 | 553 | params = ["--hello", "world", "-p"] 554 | do { 555 | let (options, rest) = try parser.parse(params) 556 | XCTAssertEqual(rest, [], "Incorrect non-option parameters") 557 | XCTAssertEqual(2, options.count, "Parser \(parser) should have parsed \(params)") 558 | if let optParams = options[optionDescription] { 559 | XCTAssertEqual(optParams, expectedParameters1, "Incorrect parameters for \(optionDescription)") 560 | } else { 561 | XCTFail("No parameters for option \(optionDescription)") 562 | } 563 | 564 | if let optParams2 = options[optionDescription2] { 565 | XCTAssertEqual(optParams2, expectedParameters2, "Incorrect parameters for \(optionDescription)") 566 | } else { 567 | XCTFail("No parameters for option \(optionDescription2)") 568 | } 569 | } catch { 570 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 571 | } 572 | 573 | params = ["--hello", "world", "-p", "-n", "boo", "hoo"] 574 | do { 575 | let (options, rest) = try parser.parse(params) 576 | XCTAssertEqual(rest, [], "Incorrect non-option parameters") 577 | XCTAssertEqual(3, options.count, "Parser \(parser) should have parsed \(params)") 578 | if let optParams = options[optionDescription] { 579 | XCTAssertEqual(optParams, expectedParameters1, "Incorrect parameters for \(optionDescription)") 580 | } else { 581 | XCTFail("No parameters for option \(optionDescription)") 582 | } 583 | 584 | if let optParams2 = options[optionDescription2] { 585 | XCTAssertEqual(optParams2, expectedParameters2, "Incorrect parameters for \(optionDescription)") 586 | } else { 587 | XCTFail("No parameters for option \(optionDescription2)") 588 | } 589 | 590 | if let optParams3 = options[optionDescription3] { 591 | XCTAssertEqual(optParams3, expectedParameters3, "Incorrect parameters for \(optionDescription)") 592 | } else { 593 | XCTFail("No parameters for option \(optionDescription3)") 594 | } 595 | } catch { 596 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 597 | } 598 | 599 | params = ["--hello", "world", "-p", "-n", "boo", "hoo", "rest"] 600 | do { 601 | let (options, rest) = try parser.parse(params) 602 | XCTAssertEqual(rest, ["rest"], "Incorrect non-option parameters") 603 | XCTAssertEqual(3, options.count, "Parser \(parser) should have parsed \(params)") 604 | if let optParams = options[optionDescription] { 605 | XCTAssertEqual(optParams, expectedParameters1, "Incorrect parameters for \(optionDescription)") 606 | } else { 607 | XCTFail("No parameters for option \(optionDescription)") 608 | } 609 | 610 | if let optParams2 = options[optionDescription2] { 611 | XCTAssertEqual(optParams2, expectedParameters2, "Incorrect parameters for \(optionDescription)") 612 | } else { 613 | XCTFail("No parameters for option \(optionDescription2)") 614 | } 615 | 616 | if let optParams3 = options[optionDescription3] { 617 | XCTAssertEqual(optParams3, expectedParameters3, "Incorrect parameters for \(optionDescription)") 618 | } else { 619 | XCTFail("No parameters for option \(optionDescription3)") 620 | } 621 | } catch { 622 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 623 | } 624 | 625 | // Tests that options can be passed at any time 626 | params = ["-p", "-n", "boo", "hoo", "rest", "--hello", "world"] 627 | do { 628 | let (options, rest) = try parser.parse(params) 629 | XCTAssertEqual(rest, ["rest"], "Incorrect non-option parameters") 630 | XCTAssertEqual(3, options.count, "Parser \(parser) should have parsed \(params)") 631 | if let optParams = options[optionDescription] { 632 | XCTAssertEqual(optParams, expectedParameters1, "Incorrect parameters for \(optionDescription)") 633 | } else { 634 | XCTFail("No parameters for option \(optionDescription)") 635 | } 636 | 637 | if let optParams2 = options[optionDescription2] { 638 | XCTAssertEqual(optParams2, expectedParameters2, "Incorrect parameters for \(optionDescription)") 639 | } else { 640 | XCTFail("No parameters for option \(optionDescription2)") 641 | } 642 | 643 | if let optParams3 = options[optionDescription3] { 644 | XCTAssertEqual(optParams3, expectedParameters3, "Incorrect parameters for \(optionDescription)") 645 | } else { 646 | XCTFail("No parameters for option \(optionDescription3)") 647 | } 648 | } catch { 649 | XCTFail("Parsing should have succeeded for parser: \(parser), options: \(params)") 650 | } 651 | 652 | 653 | // Now test the failure states: times when all the parameters aren't passed. 654 | params = ["-p", "-n", "boo", "--hello", "world"] 655 | do { 656 | try parser.parse(params) 657 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 658 | } catch let OptionKitError.invalidOption(description: description) { 659 | XCTAssertEqual(description, "Option [-n|--nom] requires 2 parameters, parameters [\"boo\"] are given before option --hello was declared", "Incorrect error description") 660 | } catch { 661 | XCTFail("Parsing failed with unexpected error: \(error)") 662 | } 663 | 664 | params = ["-p", "-n", "boo", "--hello"] 665 | do { 666 | try parser.parse(params) 667 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 668 | } catch let OptionKitError.invalidOption(description: description) { 669 | XCTAssertEqual(description, "Option [-n|--nom] requires 2 parameters, parameters [\"boo\"] are given before option --hello was declared", "Incorrect error description") 670 | } catch { 671 | XCTFail("Parsing failed with unexpected error: \(error)") 672 | } 673 | 674 | params = ["-n", "boo", "hoo", "--hello"] 675 | do { 676 | try parser.parse(params) 677 | XCTFail("Parsing should not have succeeded for parser: \(parser), options:\(params)") 678 | } catch let OptionKitError.invalidOption(description: description) { 679 | XCTAssertEqual(description, "Option [-h|--hello] requires 1 parameters, parameters [] are given", "Incorrect error description") 680 | } catch { 681 | XCTFail("Parsing failed with unexpected error: \(error)") 682 | } 683 | } 684 | 685 | } 686 | -------------------------------------------------------------------------------- /OptionKitTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OptionKit - Option Parsing in Swift 2 | ========= 3 | 4 | OptionKit is an OS X framework to parse basic command-line options in pure Swift. Currently, 5 | it has the most basic functionality necessary, but will probably expand to include more 6 | advanced features as the need arises. 7 | 8 | ## Usage 9 | 10 | OptionKit currently supports three types of options: 11 | 12 | * Short options, triggered by flags of type `-f` 13 | * Long options, triggered by flags of type `--long-option` 14 | * Mixed options, triggered by either type, such as `-v` or `--version` 15 | 16 | An option can have zero or more required parameters. Parameters are restricted 17 | in that they cannot begin with `-` or `--`, as they would be confused with triggers. 18 | 19 | OptionKit's `OptionParser` class returns a `ParseData` type, which consists of: 20 | 21 | * A dictionary of `Option` objects mapped to their (possibly empty) parameter list. 22 | * A list of remaining arguments. 23 | 24 | ### Example 25 | 26 | A simple, full example called `optionsTest.swift` might be: 27 | 28 | ```swift 29 | #!/usr/bin/env xcrun swift -F /Library/Frameworks 30 | 31 | import Foundation 32 | import OptionKit 33 | 34 | let opt1 = Option(trigger:.Mixed("e", "echo")) 35 | let opt2 = Option(trigger:.Mixed("h", "help")) 36 | let opt3 = Option(trigger:.Mixed("a", "allow-nothing")) 37 | let opt4 = Option(trigger:.Mixed("b", "break-everything")) 38 | let opt5 = Option(trigger:.Mixed("c", "counterstrike")) 39 | let parser = OptionParser(definitions:[opt1, opt3, opt4, opt5]) 40 | 41 | let actualArguments = Array(Process.arguments[1.. 1.0.0 107 | ``` 108 | 109 | ### To Do 110 | 111 | * Add support for sub-parsers. 112 | * Make help string include per-option help. 113 | --------------------------------------------------------------------------------