├── .gitignore
├── .swift-version
├── .travis.yml
├── CONTRIBUTING.md
├── LICENSE.txt
├── Moderator.podspec
├── Moderator.xcodeproj
├── ModeratorTests_Info.plist
├── Moderator_Info.plist
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── Moderator Swift 3.xcscheme
│ ├── Moderator.xcscheme
│ └── xcschememanagement.plist
├── Package.swift
├── Package@swift-4.swift
├── README.md
├── Sources
├── Moderator.swift
├── Parsers.swift
└── SwiftCompat.swift
└── Tests
├── LinuxMain.swift
└── ModeratorTests
└── Moderator_Tests.swift
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | .DS_Store
4 | build/
5 | .build
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | xcuserdata
15 | *.xccheckout
16 | *.moved-aside
17 | DerivedData
18 | *.hmap
19 | *.ipa
20 |
--------------------------------------------------------------------------------
/.swift-version:
--------------------------------------------------------------------------------
1 | 4.1.2
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | matrix:
2 | include:
3 | - name: "Linux Swift 3.1.1"
4 | os: linux
5 | language: generic
6 | dist: trusty
7 | sudo: required
8 | env:
9 | - SWIFT_BRANCH=swift-3.1.1-release
10 | - SWIFT_VERSION=swift-3.1.1-RELEASE
11 | install:
12 | - mkdir swift
13 | - curl https://swift.org/builds/$SWIFT_BRANCH/ubuntu1404/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu14.04.tar.gz -s | tar -xz -C swift
14 | - export PATH="$(pwd)/swift/$SWIFT_VERSION-ubuntu14.04/usr/bin:$PATH"
15 |
16 | - name: "Linux Swift 4.1.3"
17 | os: linux
18 | language: generic
19 | dist: trusty
20 | sudo: required
21 | env:
22 | - SWIFT_BRANCH=swift-4.1.3-release
23 | - SWIFT_VERSION=swift-4.1.3-RELEASE
24 | install:
25 | - mkdir swift
26 | - curl https://swift.org/builds/$SWIFT_BRANCH/ubuntu1404/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu14.04.tar.gz -s | tar -xz -C swift
27 | - export PATH="$(pwd)/swift/$SWIFT_VERSION-ubuntu14.04/usr/bin:$PATH"
28 |
29 | - name: "Linux Swift 4.2.3"
30 | os: linux
31 | language: generic
32 | dist: trusty
33 | sudo: required
34 | env:
35 | - SWIFT_BRANCH=swift-4.2.3-release
36 | - SWIFT_VERSION=swift-4.2.3-RELEASE
37 | install:
38 | - mkdir swift
39 | - curl https://swift.org/builds/$SWIFT_BRANCH/ubuntu1404/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu14.04.tar.gz -s | tar -xz -C swift
40 | - export PATH="$(pwd)/swift/$SWIFT_VERSION-ubuntu14.04/usr/bin:$PATH"
41 |
42 | - name: "Linux Swift 5.0"
43 | os: linux
44 | language: generic
45 | dist: trusty
46 | sudo: required
47 | env:
48 | - SWIFT_BRANCH=swift-5.0-release
49 | - SWIFT_VERSION=swift-5.0-RELEASE
50 | install:
51 | - mkdir swift
52 | - curl https://swift.org/builds/$SWIFT_BRANCH/ubuntu1404/$SWIFT_VERSION/$SWIFT_VERSION-ubuntu14.04.tar.gz -s | tar -xz -C swift
53 | - export PATH="$(pwd)/swift/$SWIFT_VERSION-ubuntu14.04/usr/bin:$PATH"
54 |
55 | - name: "Mac Xcode 9"
56 | os: osx
57 | osx_image: xcode9.4
58 | language: generic
59 | sudo: required
60 |
61 | - name: "Mac Xcode 10"
62 | os: osx
63 | osx_image: xcode10
64 | language: generic
65 | sudo: required
66 |
67 | script:
68 | - swift package reset
69 | - swift build
70 | - swift test
71 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## How to contribute
2 |
3 | Issues and suggestions [are always welcome](https://github.com/kareman/Moderator.swift/issues).
4 |
5 | If you want to make changes yourself please follow the standard [pull request guidelines](http://help.github.com/pull-requests/). In short: fork, create topic branch, one commit per atomic change, make sure all unit tests pass, and create the pull request.
6 |
7 | If it's a sizeable change or will break backwards compatibility it's probably best if you create an issue first so we can discuss the changes beforehand.
8 |
9 | #### Testing
10 |
11 | - Unit tests are awesome. Please create new ones to test the changes you make.
12 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Kåre Morstøl, NotTooBad Software (nottoobadsoftware.com)
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 |
--------------------------------------------------------------------------------
/Moderator.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'Moderator'
3 | s.version = '0.5.1'
4 | s.summary = 'A simple, modular command line argument parser in Swift.'
5 | s.description = 'Moderator is a simple Swift library for parsing commandline arguments.'
6 | s.homepage = 'https://github.com/kareman/Moderator'
7 | s.license = { type: 'MIT', file: 'LICENSE.txt' }
8 | s.author = { 'Kare Morstol' => 'kare@nottoobadsoftware.com' }
9 | s.source = { git: 'https://github.com/kareman/Moderator.git', tag: s.version.to_s }
10 | s.source_files = 'Sources/*.swift'
11 | s.osx.deployment_target = '10.10'
12 | end
13 |
--------------------------------------------------------------------------------
/Moderator.xcodeproj/ModeratorTests_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | BNDL
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Moderator.xcodeproj/Moderator_Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | CFBundleDevelopmentRegion
5 | en
6 | CFBundleExecutable
7 | $(EXECUTABLE_NAME)
8 | CFBundleIdentifier
9 | $(PRODUCT_BUNDLE_IDENTIFIER)
10 | CFBundleInfoDictionaryVersion
11 | 6.0
12 | CFBundleName
13 | $(PRODUCT_NAME)
14 | CFBundlePackageType
15 | FMWK
16 | CFBundleShortVersionString
17 | 1.0
18 | CFBundleSignature
19 | ????
20 | CFBundleVersion
21 | $(CURRENT_PROJECT_VERSION)
22 | NSPrincipalClass
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Moderator.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | BA002F8121261C2C00ECA66A /* SwiftCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA002F8021261C2C00ECA66A /* SwiftCompat.swift */; };
11 | OBJ_22 /* Moderator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Moderator.swift */; };
12 | OBJ_23 /* Parsers.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_10 /* Parsers.swift */; };
13 | OBJ_30 /* Moderator_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* Moderator_Tests.swift */; };
14 | OBJ_32 /* Moderator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* Moderator.framework */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXContainerItemProxy section */
18 | BAB4C2131DDE1618001201AE /* PBXContainerItemProxy */ = {
19 | isa = PBXContainerItemProxy;
20 | containerPortal = OBJ_1 /* Project object */;
21 | proxyType = 1;
22 | remoteGlobalIDString = OBJ_17;
23 | remoteInfo = Moderator;
24 | };
25 | /* End PBXContainerItemProxy section */
26 |
27 | /* Begin PBXFileReference section */
28 | BA002F8021261C2C00ECA66A /* SwiftCompat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftCompat.swift; sourceTree = ""; };
29 | OBJ_10 /* Parsers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parsers.swift; sourceTree = ""; };
30 | OBJ_13 /* Moderator_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Moderator_Tests.swift; sourceTree = ""; };
31 | OBJ_15 /* Moderator.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Moderator.framework; sourceTree = BUILT_PRODUCTS_DIR; };
32 | OBJ_16 /* ModeratorTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = ModeratorTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
33 | OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; };
34 | OBJ_9 /* Moderator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Moderator.swift; sourceTree = ""; };
35 | /* End PBXFileReference section */
36 |
37 | /* Begin PBXFrameworksBuildPhase section */
38 | OBJ_24 /* Frameworks */ = {
39 | isa = PBXFrameworksBuildPhase;
40 | buildActionMask = 0;
41 | files = (
42 | );
43 | runOnlyForDeploymentPostprocessing = 0;
44 | };
45 | OBJ_31 /* Frameworks */ = {
46 | isa = PBXFrameworksBuildPhase;
47 | buildActionMask = 0;
48 | files = (
49 | OBJ_32 /* Moderator.framework in Frameworks */,
50 | );
51 | runOnlyForDeploymentPostprocessing = 0;
52 | };
53 | /* End PBXFrameworksBuildPhase section */
54 |
55 | /* Begin PBXGroup section */
56 | OBJ_11 /* Tests */ = {
57 | isa = PBXGroup;
58 | children = (
59 | OBJ_12 /* ModeratorTests */,
60 | );
61 | path = Tests;
62 | sourceTree = "";
63 | };
64 | OBJ_12 /* ModeratorTests */ = {
65 | isa = PBXGroup;
66 | children = (
67 | OBJ_13 /* Moderator_Tests.swift */,
68 | );
69 | name = ModeratorTests;
70 | path = Tests/ModeratorTests;
71 | sourceTree = SOURCE_ROOT;
72 | };
73 | OBJ_14 /* Products */ = {
74 | isa = PBXGroup;
75 | children = (
76 | OBJ_15 /* Moderator.framework */,
77 | OBJ_16 /* ModeratorTests.xctest */,
78 | );
79 | name = Products;
80 | sourceTree = BUILT_PRODUCTS_DIR;
81 | };
82 | OBJ_5 = {
83 | isa = PBXGroup;
84 | children = (
85 | OBJ_6 /* Package.swift */,
86 | OBJ_7 /* Sources */,
87 | OBJ_11 /* Tests */,
88 | OBJ_14 /* Products */,
89 | );
90 | sourceTree = "";
91 | };
92 | OBJ_7 /* Sources */ = {
93 | isa = PBXGroup;
94 | children = (
95 | OBJ_8 /* Moderator */,
96 | );
97 | path = Sources;
98 | sourceTree = "";
99 | };
100 | OBJ_8 /* Moderator */ = {
101 | isa = PBXGroup;
102 | children = (
103 | BA002F8021261C2C00ECA66A /* SwiftCompat.swift */,
104 | OBJ_9 /* Moderator.swift */,
105 | OBJ_10 /* Parsers.swift */,
106 | );
107 | name = Moderator;
108 | path = Sources;
109 | sourceTree = SOURCE_ROOT;
110 | };
111 | /* End PBXGroup section */
112 |
113 | /* Begin PBXNativeTarget section */
114 | OBJ_17 /* Moderator */ = {
115 | isa = PBXNativeTarget;
116 | buildConfigurationList = OBJ_18 /* Build configuration list for PBXNativeTarget "Moderator" */;
117 | buildPhases = (
118 | OBJ_21 /* Sources */,
119 | OBJ_24 /* Frameworks */,
120 | );
121 | buildRules = (
122 | );
123 | dependencies = (
124 | );
125 | name = Moderator;
126 | productName = Moderator;
127 | productReference = OBJ_15 /* Moderator.framework */;
128 | productType = "com.apple.product-type.framework";
129 | };
130 | OBJ_25 /* ModeratorTests */ = {
131 | isa = PBXNativeTarget;
132 | buildConfigurationList = OBJ_26 /* Build configuration list for PBXNativeTarget "ModeratorTests" */;
133 | buildPhases = (
134 | OBJ_29 /* Sources */,
135 | OBJ_31 /* Frameworks */,
136 | );
137 | buildRules = (
138 | );
139 | dependencies = (
140 | OBJ_33 /* PBXTargetDependency */,
141 | );
142 | name = ModeratorTests;
143 | productName = ModeratorTests;
144 | productReference = OBJ_16 /* ModeratorTests.xctest */;
145 | productType = "com.apple.product-type.bundle.unit-test";
146 | };
147 | /* End PBXNativeTarget section */
148 |
149 | /* Begin PBXProject section */
150 | OBJ_1 /* Project object */ = {
151 | isa = PBXProject;
152 | attributes = {
153 | LastUpgradeCheck = 9999;
154 | TargetAttributes = {
155 | OBJ_17 = {
156 | LastSwiftMigration = "";
157 | };
158 | OBJ_25 = {
159 | LastSwiftMigration = "";
160 | };
161 | };
162 | };
163 | buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "Moderator" */;
164 | compatibilityVersion = "Xcode 3.2";
165 | developmentRegion = English;
166 | hasScannedForEncodings = 0;
167 | knownRegions = (
168 | en,
169 | );
170 | mainGroup = OBJ_5;
171 | productRefGroup = OBJ_14 /* Products */;
172 | projectDirPath = "";
173 | projectRoot = "";
174 | targets = (
175 | OBJ_17 /* Moderator */,
176 | OBJ_25 /* ModeratorTests */,
177 | );
178 | };
179 | /* End PBXProject section */
180 |
181 | /* Begin PBXSourcesBuildPhase section */
182 | OBJ_21 /* Sources */ = {
183 | isa = PBXSourcesBuildPhase;
184 | buildActionMask = 0;
185 | files = (
186 | OBJ_22 /* Moderator.swift in Sources */,
187 | OBJ_23 /* Parsers.swift in Sources */,
188 | BA002F8121261C2C00ECA66A /* SwiftCompat.swift in Sources */,
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | };
192 | OBJ_29 /* Sources */ = {
193 | isa = PBXSourcesBuildPhase;
194 | buildActionMask = 0;
195 | files = (
196 | OBJ_30 /* Moderator_Tests.swift in Sources */,
197 | );
198 | runOnlyForDeploymentPostprocessing = 0;
199 | };
200 | /* End PBXSourcesBuildPhase section */
201 |
202 | /* Begin PBXTargetDependency section */
203 | OBJ_33 /* PBXTargetDependency */ = {
204 | isa = PBXTargetDependency;
205 | target = OBJ_17 /* Moderator */;
206 | targetProxy = BAB4C2131DDE1618001201AE /* PBXContainerItemProxy */;
207 | };
208 | /* End PBXTargetDependency section */
209 |
210 | /* Begin XCBuildConfiguration section */
211 | BA002F82212647D700ECA66A /* Debug Swift 3 */ = {
212 | isa = XCBuildConfiguration;
213 | buildSettings = {
214 | COMBINE_HIDPI_IMAGES = YES;
215 | COPY_PHASE_STRIP = NO;
216 | DEBUG_INFORMATION_FORMAT = dwarf;
217 | DYLIB_INSTALL_NAME_BASE = "@rpath";
218 | ENABLE_NS_ASSERTIONS = YES;
219 | GCC_OPTIMIZATION_LEVEL = 0;
220 | MACOSX_DEPLOYMENT_TARGET = 10.10;
221 | ONLY_ACTIVE_ARCH = YES;
222 | OTHER_SWIFT_FLAGS = "-DXcode";
223 | PRODUCT_NAME = "$(TARGET_NAME)";
224 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
225 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
226 | SWIFT_VERSION = 3.0;
227 | USE_HEADERMAP = NO;
228 | };
229 | name = "Debug Swift 3";
230 | };
231 | BA002F83212647D700ECA66A /* Debug Swift 3 */ = {
232 | isa = XCBuildConfiguration;
233 | buildSettings = {
234 | ENABLE_TESTABILITY = YES;
235 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks";
236 | HEADER_SEARCH_PATHS = "";
237 | INFOPLIST_FILE = Moderator.xcodeproj/Moderator_Info.plist;
238 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
239 | OTHER_LDFLAGS = "$(inherited)";
240 | OTHER_SWIFT_FLAGS = "$(inherited)";
241 | PRODUCT_BUNDLE_IDENTIFIER = Moderator;
242 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
243 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
244 | SUPPORTED_PLATFORMS = macosx;
245 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
246 | TARGET_NAME = Moderator;
247 | };
248 | name = "Debug Swift 3";
249 | };
250 | BA002F84212647D700ECA66A /* Debug Swift 3 */ = {
251 | isa = XCBuildConfiguration;
252 | buildSettings = {
253 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
254 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks";
255 | HEADER_SEARCH_PATHS = "";
256 | INFOPLIST_FILE = Moderator.xcodeproj/ModeratorTests_Info.plist;
257 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
258 | OTHER_LDFLAGS = "$(inherited)";
259 | OTHER_SWIFT_FLAGS = "$(inherited)";
260 | SUPPORTED_PLATFORMS = macosx;
261 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
262 | TARGET_NAME = ModeratorTests;
263 | };
264 | name = "Debug Swift 3";
265 | };
266 | OBJ_19 /* Debug */ = {
267 | isa = XCBuildConfiguration;
268 | buildSettings = {
269 | ENABLE_TESTABILITY = YES;
270 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks";
271 | HEADER_SEARCH_PATHS = "";
272 | INFOPLIST_FILE = Moderator.xcodeproj/Moderator_Info.plist;
273 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
274 | OTHER_LDFLAGS = "$(inherited)";
275 | OTHER_SWIFT_FLAGS = "$(inherited)";
276 | PRODUCT_BUNDLE_IDENTIFIER = Moderator;
277 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
278 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
279 | SUPPORTED_PLATFORMS = macosx;
280 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
281 | TARGET_NAME = Moderator;
282 | };
283 | name = Debug;
284 | };
285 | OBJ_20 /* Release */ = {
286 | isa = XCBuildConfiguration;
287 | buildSettings = {
288 | ENABLE_TESTABILITY = YES;
289 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks";
290 | HEADER_SEARCH_PATHS = "";
291 | INFOPLIST_FILE = Moderator.xcodeproj/Moderator_Info.plist;
292 | LD_RUNPATH_SEARCH_PATHS = "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx";
293 | OTHER_LDFLAGS = "$(inherited)";
294 | OTHER_SWIFT_FLAGS = "$(inherited)";
295 | PRODUCT_BUNDLE_IDENTIFIER = Moderator;
296 | PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)";
297 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
298 | SUPPORTED_PLATFORMS = macosx;
299 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
300 | TARGET_NAME = Moderator;
301 | };
302 | name = Release;
303 | };
304 | OBJ_27 /* Debug */ = {
305 | isa = XCBuildConfiguration;
306 | buildSettings = {
307 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
308 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks";
309 | HEADER_SEARCH_PATHS = "";
310 | INFOPLIST_FILE = Moderator.xcodeproj/ModeratorTests_Info.plist;
311 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
312 | OTHER_LDFLAGS = "$(inherited)";
313 | OTHER_SWIFT_FLAGS = "$(inherited)";
314 | SUPPORTED_PLATFORMS = macosx;
315 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
316 | TARGET_NAME = ModeratorTests;
317 | };
318 | name = Debug;
319 | };
320 | OBJ_28 /* Release */ = {
321 | isa = XCBuildConfiguration;
322 | buildSettings = {
323 | EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
324 | FRAMEWORK_SEARCH_PATHS = "$(PLATFORM_DIR)/Developer/Library/Frameworks";
325 | HEADER_SEARCH_PATHS = "";
326 | INFOPLIST_FILE = Moderator.xcodeproj/ModeratorTests_Info.plist;
327 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks";
328 | OTHER_LDFLAGS = "$(inherited)";
329 | OTHER_SWIFT_FLAGS = "$(inherited)";
330 | SUPPORTED_PLATFORMS = macosx;
331 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = SWIFT_PACKAGE;
332 | TARGET_NAME = ModeratorTests;
333 | };
334 | name = Release;
335 | };
336 | OBJ_3 /* Debug */ = {
337 | isa = XCBuildConfiguration;
338 | buildSettings = {
339 | COMBINE_HIDPI_IMAGES = YES;
340 | COPY_PHASE_STRIP = NO;
341 | DEBUG_INFORMATION_FORMAT = dwarf;
342 | DYLIB_INSTALL_NAME_BASE = "@rpath";
343 | ENABLE_NS_ASSERTIONS = YES;
344 | GCC_OPTIMIZATION_LEVEL = 0;
345 | MACOSX_DEPLOYMENT_TARGET = 10.10;
346 | ONLY_ACTIVE_ARCH = YES;
347 | OTHER_SWIFT_FLAGS = "-DXcode";
348 | PRODUCT_NAME = "$(TARGET_NAME)";
349 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
350 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
351 | SWIFT_VERSION = 4.0;
352 | USE_HEADERMAP = NO;
353 | };
354 | name = Debug;
355 | };
356 | OBJ_4 /* Release */ = {
357 | isa = XCBuildConfiguration;
358 | buildSettings = {
359 | COMBINE_HIDPI_IMAGES = YES;
360 | COPY_PHASE_STRIP = YES;
361 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
362 | DYLIB_INSTALL_NAME_BASE = "@rpath";
363 | GCC_OPTIMIZATION_LEVEL = s;
364 | MACOSX_DEPLOYMENT_TARGET = 10.10;
365 | OTHER_SWIFT_FLAGS = "-DXcode";
366 | PRODUCT_NAME = "$(TARGET_NAME)";
367 | SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator";
368 | SWIFT_OPTIMIZATION_LEVEL = "-O";
369 | SWIFT_VERSION = 4.0;
370 | USE_HEADERMAP = NO;
371 | };
372 | name = Release;
373 | };
374 | /* End XCBuildConfiguration section */
375 |
376 | /* Begin XCConfigurationList section */
377 | OBJ_18 /* Build configuration list for PBXNativeTarget "Moderator" */ = {
378 | isa = XCConfigurationList;
379 | buildConfigurations = (
380 | OBJ_19 /* Debug */,
381 | BA002F83212647D700ECA66A /* Debug Swift 3 */,
382 | OBJ_20 /* Release */,
383 | );
384 | defaultConfigurationIsVisible = 0;
385 | defaultConfigurationName = Debug;
386 | };
387 | OBJ_2 /* Build configuration list for PBXProject "Moderator" */ = {
388 | isa = XCConfigurationList;
389 | buildConfigurations = (
390 | OBJ_3 /* Debug */,
391 | BA002F82212647D700ECA66A /* Debug Swift 3 */,
392 | OBJ_4 /* Release */,
393 | );
394 | defaultConfigurationIsVisible = 0;
395 | defaultConfigurationName = Debug;
396 | };
397 | OBJ_26 /* Build configuration list for PBXNativeTarget "ModeratorTests" */ = {
398 | isa = XCConfigurationList;
399 | buildConfigurations = (
400 | OBJ_27 /* Debug */,
401 | BA002F84212647D700ECA66A /* Debug Swift 3 */,
402 | OBJ_28 /* Release */,
403 | );
404 | defaultConfigurationIsVisible = 0;
405 | defaultConfigurationName = Debug;
406 | };
407 | /* End XCConfigurationList section */
408 | };
409 | rootObject = OBJ_1 /* Project object */;
410 | }
411 |
--------------------------------------------------------------------------------
/Moderator.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Moderator.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Moderator.xcodeproj/xcshareddata/xcschemes/Moderator Swift 3.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
35 |
41 |
42 |
43 |
44 |
45 |
51 |
52 |
53 |
54 |
57 |
58 |
59 |
60 |
61 |
62 |
72 |
73 |
79 |
80 |
81 |
82 |
83 |
84 |
90 |
91 |
93 |
94 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/Moderator.xcodeproj/xcshareddata/xcschemes/Moderator.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
35 |
41 |
42 |
43 |
44 |
45 |
51 |
52 |
53 |
54 |
55 |
56 |
66 |
67 |
73 |
74 |
75 |
76 |
77 |
78 |
84 |
85 |
87 |
88 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/Moderator.xcodeproj/xcshareddata/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | SchemeUserState
5 |
6 | Moderator.xcscheme
7 |
8 |
9 | SuppressBuildableAutocreation
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:3.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Moderator"
8 | )
9 |
--------------------------------------------------------------------------------
/Package@swift-4.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:4.0
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Moderator",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "Moderator",
12 | targets: ["Moderator"]),
13 | ],
14 | targets: [
15 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
16 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
17 | .target(
18 | name: "Moderator",
19 | path: "Sources"),
20 |
21 | // Test Targets
22 | .testTarget(
23 | name: "ModeratorTests",
24 | dependencies: ["Moderator"]
25 | )
26 | ]
27 | )
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [Run shell commands](https://github.com/kareman/SwiftShell) | Parse command line arguments | [Handle files and directories](https://github.com/kareman/FileSmith)
2 |
3 | ---
4 |
5 | [](https://travis-ci.org/kareman/Moderator)   [](https://github.com/Carthage/Carthage)
6 |
7 | # Moderator
8 |
9 | Moderator is a simple Swift library for parsing commandline arguments.
10 |
11 | ## Features
12 |
13 | - [x] Modular, easy to extend.
14 | - [x] Supports a [cross platform syntax](https://nottoobadsoftware.com/blog/uncategorized/cross-platform-command-line-arguments-syntax/).
15 | - [x] Generates help text automatically.
16 | - [x] Handles arguments of the type '--option=value'.
17 | - [x] Optional strict parsing, where an error is thrown if there are any unrecognised arguments.
18 | - [x] Any arguments after an "\--" argument are taken literally, they are not parsed as options and any '=' are left untouched.
19 |
20 | See also [why Moderator was created](https://nottoobadsoftware.com/blog/swift/moderator-parsing-commandline-arguments-in-swift/).
21 |
22 | ## Example
23 |
24 | from [linuxmain-generator](https://github.com/kareman/linuxmain-generator):
25 |
26 | ```Swift
27 | import Moderator
28 | import FileSmith
29 |
30 | let arguments = Moderator(description: "Automatically add code to Swift Package Manager projects to run unit tests on Linux.")
31 | let overwrite = arguments.add(.option("o","overwrite", description: "Replace /LinuxMain.swift if it already exists."))
32 | let testdirarg = arguments.add(Argument
33 | .optionWithValue("testdir", name: "test directory", description: "The path to the directory with the unit tests.")
34 | .default("Tests"))
35 | _ = arguments.add(Argument
36 | .singleArgument(name: "directory", description: "The project root directory.")
37 | .default("./")
38 | .map { (projectpath: String) in
39 | let projectdir = try Directory(open: projectpath)
40 | try projectdir.verifyContains("Package.swift")
41 | Directory.current = projectdir
42 | })
43 |
44 | do {
45 | try arguments.parse()
46 |
47 | let testdir = try Directory(open: testdirarg.value)
48 | if !overwrite.value && testdir.contains("LinuxMain.swift") {
49 | throw ArgumentError(errormessage: "\(testdir.path)/LinuxMain.swift already exists. Use -o/--overwrite to replace it.")
50 | }
51 | ...
52 | } catch {
53 | WritableFile.stderror.print(error)
54 | exit(Int32(error._code))
55 | }
56 | ```
57 |
58 | Automatically generated help text:
59 |
60 | ```text
61 | Automatically add code to Swift Package Manager projects to run unit tests on Linux.
62 |
63 | Usage: linuxmain-generator
64 | -o,--overwrite:
65 | Replace /LinuxMain.swift if it already exists.
66 | --testdir :
67 | The path to the directory with the unit tests. Default = 'Tests'.
68 | :
69 | The project root directory. Default = './'.
70 | ```
71 |
72 | ## Introduction
73 |
74 | Moderator works by having a single Moderator object which you add individual argument parsers to. When you start parsing it goes through each argument parser _in the order they were added_. Each parser takes the array of string arguments from the command line, finds the arguments it is responsible for, processes them and throws any errors if anything is wrong, _removes the arguments from the array_, returns its output (which for some parsers may be nil if the argument was not found) and passes the modified array to the next parser.
75 |
76 | This keeps the code simple and each parser only has to take care of its own arguments. The built-in parsers can easily be customised, and you can create your own parsers from scratch.
77 |
78 | ## Built-in parsers
79 |
80 | ### Option
81 |
82 | ```swift
83 | func option(_ names: String..., description: String? = nil) -> Argument
84 | ```
85 |
86 | Handles option arguments like `-h` and `--help`. Returns true if the argument is present and false otherwise.
87 |
88 | ### Option with value
89 |
90 | ```swift
91 | func optionWithValue(_ names: String..., name valuename: String? = nil, description: String? = nil)
92 | -> Argument
93 | ```
94 |
95 | Handles option arguments with a following value, like `--help `. It returns the value as a String, or nil if the option is not present.
96 |
97 | ### Single argument
98 |
99 | ```swift
100 | func singleArgument(name: String, description: String? = nil) -> Argument
101 | ```
102 |
103 | Returns the next argument, or nil if there are no more arguments or the next argument is an option. Must be added after any option parsers.
104 |
105 | ## Customise
106 |
107 | ### `default`
108 |
109 | Can be used on parsers returning optionals, to replace nil with a default value.
110 |
111 | ### `required`
112 |
113 | Can be used on parsers returning optionals, to throw an error on nil.
114 |
115 | ### `map`
116 |
117 | Takes the output of any argument parser and converts it to something else.
118 |
119 | ### `repeat`
120 |
121 | Looks for multiple occurrences of an argument, by repeating an optional parser until it returns nil.
122 |
123 | ```swift
124 | let m = Moderator()
125 | let options = m.add(Argument.optionWithValue("b").repeat())
126 | let multiple = m.add(Argument.singleArgument(name: "multiple").repeat())
127 |
128 | try m.parse(["-b", "b1", "-b", "b2", "-b", "b3", "one", "two", "three"])
129 | ```
130 |
131 | `options.value` is now `["b1", "b2", "b3"]` and `multiple.value` is `["one", "two", "three"]`.
132 |
133 | ### `count`
134 |
135 | Counts the number of times an option argument occurs.
136 |
137 | ```swift
138 |
139 | let m = Moderator()
140 | let option = m.add(Argument.option("b").count())
141 |
142 | try m.parse(["-b", "-b", "some", "other", "-b", "arguments"])
143 | ```
144 |
145 | `option.value` returns `3`
146 |
147 | ## Add new parsers
148 |
149 | If the built in parsers and customisations are not enough, you can easily create your own parsers. As an example here is the implementation of the singleArgument parser:
150 |
151 | ```swift
152 | extension Argument {
153 | public static func singleArgument (name: String, description: String? = nil) -> Argument {
154 | return Argument(usage: description.map { ("<"+name+">", $0) }) { args in
155 | let index = args.first == "--" ? args.index(after: args.startIndex) : args.startIndex
156 | guard index != args.endIndex, !isOption(index: index, args: args) else { return (nil, args) }
157 | var args = args
158 | return (args.remove(at: index), args)
159 | }
160 | }
161 | }
162 | ```
163 |
164 | In the Argument initialiser you return a tuple with the output of the parser and the arguments array without the processed argument(s).
165 |
166 | ## Installation
167 |
168 | ### [Swift Package Manager](https://github.com/apple/swift-package-manager)
169 |
170 | Add `.Package(url: "https://github.com/kareman/Moderator", "0.5.0")` to your Package.swift:
171 |
172 | ```swift
173 | import PackageDescription
174 |
175 | let package = Package(
176 | name: "somename",
177 | dependencies: [
178 | .Package(url: "https://github.com/kareman/Moderator", "0.5.0")
179 | ]
180 | )
181 | ```
182 |
183 | and run `swift build`.
184 |
185 | ### [Carthage](https://github.com/Carthage/Carthage)
186 |
187 | Add `github "kareman/Moderator"` to your Cartfile, then run `carthage update` and add the resulting framework to the "Embedded Binaries" section of the application. See [Carthage's README][carthage-installation] for further instructions.
188 |
189 | [carthage-installation]: https://github.com/Carthage/Carthage/blob/master/README.md#adding-frameworks-to-an-application
190 |
191 | ### [CocoaPods](https://cocoapods.org/)
192 |
193 | Add `Moderator` to your `Podfile`.
194 |
195 | ```ruby
196 | pod "Moderator", git: "https://github.com/kareman/Moderator.git"
197 | ```
198 |
199 | Then run `pod install` to install it.
200 |
201 | ## License
202 |
203 | Released under the MIT License (MIT), http://opensource.org/licenses/MIT
204 |
205 | Kåre Morstøl, [NotTooBad Software](https://nottoobadsoftware.com)
206 |
--------------------------------------------------------------------------------
/Sources/Moderator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Moderator.swift
3 | //
4 | // Created by Kåre Morstøl.
5 | // Copyright (c) 2016 NotTooBad Software. All rights reserved.
6 | //
7 |
8 | import Foundation
9 |
10 | public final class Moderator {
11 | fileprivate var parsers: [Argument] = []
12 | public fileprivate(set) var remaining: [String] = []
13 | let description: String
14 |
15 | /// Creates a Moderator object
16 | ///
17 | /// - Parameter description: The description of this executable, printed at the beginning of the help text. Empty by default.
18 | public init (description: String = "") {
19 | self.description = description.isEmpty ? description : description + "\n\n"
20 | _ = add(.joinedOptionAndArgumentParser())
21 | }
22 |
23 | public func add (_ p: Argument) -> FutureValue {
24 | let b = FutureValue()
25 | parsers.append(p.map {b.value = $0})
26 | return b
27 | }
28 |
29 | public func parse (_ args: [String], strict: Bool = true) throws {
30 | do {
31 | remaining = try parsers.reduce(args) { (args, parser) in try parser.parse(args).remainder }
32 | if remaining.count == 1 && remaining.first == "--" {
33 | remaining = []
34 | }
35 | if strict && !remaining.isEmpty {
36 | throw ArgumentError(errormessage: "Unknown arguments: " + self.remaining.joined(separator: " "))
37 | }
38 | } catch var error as ArgumentError {
39 | error.usagetext = error.usagetext ?? self.usagetext
40 | throw error
41 | }
42 | }
43 |
44 | public func parse (strict: Bool = true) throws {
45 | try parse(Array(CommandLine.arguments.dropFirst()), strict: strict)
46 | }
47 |
48 | static func commandName() -> String {
49 | return URL(fileURLWithPath: CommandLine.arguments.first ?? "").lastPathComponent
50 | }
51 |
52 | public var usagetext: String {
53 | let usagetexts = parsers.compactMap { $0.usage }
54 | guard !usagetexts.isEmpty else {return ""}
55 | return usagetexts.reduce(description + "Usage: \(Moderator.commandName())\n") {
56 | (acc:String, usagetext:UsageText) -> String in
57 | return acc + format(usagetext: usagetext) + "\n"
58 | }
59 | }
60 | }
61 |
62 | func format(usagetext: UsageText) -> String {
63 | guard let usagetext = usagetext else { return "" }
64 | return " " + usagetext.title + ":\n " + usagetext.description
65 | }
66 |
67 | // https://github.com/robrix/Box
68 | // Copyright (c) 2014 Rob Rix. All rights reserved.
69 |
70 | /// A value that will be set sometime in the future.
71 | public final class FutureValue: CustomStringConvertible {
72 | /// Initializes a `FutureValue` with the given value.
73 | public init(_ value: T) {
74 | self.value = value
75 | }
76 |
77 | /// Initializes an empty `FutureValue`.
78 | public init() {
79 | self._value = nil
80 | }
81 |
82 | private var _value: T!
83 |
84 | /// The (mutable) value.
85 | public var value: T {
86 | get {
87 | precondition(_value != nil, "Remember to call Argument.parse() before accessing value of arguments.")
88 | return _value!
89 | }
90 | set {
91 | _value = newValue
92 | }
93 | }
94 |
95 | /// Constructs a new FutureValue by transforming `value` by `f`.
96 | public func map(_ f: (T) -> U) -> FutureValue {
97 | return FutureValue(f(value))
98 | }
99 |
100 | // MARK: Printable
101 |
102 | public var description: String {
103 | return String(describing: value)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Sources/Parsers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Moderator.swift
3 | //
4 | // Created by Kåre Morstøl.
5 | // Copyright (c) 2016 NotTooBad Software. All rights reserved.
6 | //
7 |
8 | // Should ideally and eventually be compatible with http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html ,
9 | // with the addition of "--longname". For more, see http://blog.nottoobadsoftware.com/uncategorized/cross-platform-command-line-arguments-syntax/ .
10 |
11 | public typealias UsageText = (title: String, description: String)?
12 |
13 | public struct Argument {
14 | public let usage: UsageText
15 | public let parse: ([String]) throws -> (value: Value, remainder: [String])
16 |
17 | public init (usage: UsageText = nil, p: @escaping ([String]) throws -> (value: Value, remainder: [String])) {
18 | self.parse = p
19 | self.usage = usage
20 | }
21 |
22 | public init (usage: UsageText = nil, value: Value) {
23 | self.parse = { args in (value, args) }
24 | self.usage = usage
25 | }
26 | }
27 |
28 | extension Argument {
29 | public func map (_ f: @escaping (Value) throws -> Outvalue) -> Argument {
30 | return Argument(usage: self.usage) { args in
31 | let result = try self.parse(args)
32 | return (value: try f(result.value), remainder: result.remainder)
33 | }
34 | }
35 | }
36 |
37 | public struct ArgumentError: Error, CustomStringConvertible {
38 | public let errormessage: String
39 | public internal(set) var usagetext: String? = nil
40 |
41 | public init (errormessage: String, usagetext: String? = nil) {
42 | self.errormessage = errormessage
43 | self.usagetext = usagetext
44 | }
45 |
46 | public var description: String { return errormessage + (usagetext.map { "\n" + $0 } ?? "") }
47 | }
48 |
49 | extension Argument {
50 | static func isOption (index: Array.Index, args: [String]) -> Bool {
51 | if let i = args.index(of: "--"), i < index { return false }
52 | let argument = args[index]
53 | if argument.first == "-",
54 | let second = argument.dropFirst().first, !("0"..."9").contains(second) {
55 | return true
56 | }
57 | return false
58 | }
59 |
60 | static func option(names: [String], description: String? = nil) -> Argument {
61 | for illegalcharacter in [" ","-","="] {
62 | precondition(!names.contains(where: {$0.contains(illegalcharacter)}), "Option names cannot contain '\(illegalcharacter)'")
63 | }
64 | for digit in 0...9 {
65 | precondition(!names.contains(where: {$0.hasPrefix(String(digit))}), "Option names cannot begin with a number.")
66 | }
67 | precondition(!names.contains("W"), "Option '-W' is reserved for system use.")
68 |
69 | let names = names.map { ($0.count==1 ? "-" : "--") + $0 }
70 | let usage = description.map { (names.joined(separator: ","), $0) }
71 | return Argument(usage: usage) { args in
72 | var args = args
73 | guard let index = args.index(where: names.contains), isOption(index: index, args: args) else {
74 | return (false, args)
75 | }
76 | args.remove(at: index)
77 | return (true, args)
78 | }
79 | }
80 |
81 | public static func option(_ names: String..., description: String? = nil) -> Argument {
82 | return option(names: names, description: description)
83 | }
84 |
85 | /// Parses arguments like '--opt=value' into '--opt value'.
86 | internal static func joinedOptionAndArgumentParser() -> Argument {
87 | return Argument() { args in
88 | return ((), args.enumerated().flatMap { (index, arg) in
89 | isOption(index: index, args: args) ?
90 | arg.split(separator: "=", maxSplits: 1, omittingEmptySubsequences: true).map(String.init) :
91 | [arg]
92 | })
93 | }
94 | }
95 | }
96 |
97 |
98 | extension Array where Element: Equatable {
99 | public func indexOfFirstDifference (_ other: Array) -> Index? {
100 | for i in self.indices {
101 | if i >= other.endIndex || self[i] != other[i] { return i }
102 | }
103 | return nil
104 | }
105 | }
106 |
107 | extension Argument {
108 | public static func optionWithValue
109 | (_ names: String..., name valuename: String? = nil, description: String? = nil)
110 | -> Argument {
111 |
112 | let option = Argument.option(names: names, description: description)
113 | let usage = option.usage.map { usage in
114 | return (usage.title + " <\(valuename ?? "arg")>", usage.description)
115 | }
116 |
117 | return Argument(usage: usage) { args in
118 | var optionresult = try option.parse(args)
119 | guard optionresult.value else {
120 | return (nil, args)
121 | }
122 | guard let firstchange = optionresult.remainder.indexOfFirstDifference(args) else {
123 | throw ArgumentError(errormessage: "Expected value for '\(args.last!)'.",
124 | usagetext: format(usagetext: usage))
125 | }
126 | guard !isOption(index: firstchange, args: optionresult.remainder) else {
127 | throw ArgumentError(
128 | errormessage: "Expected value for '\(args[firstchange])', got option '\(optionresult.remainder[firstchange])'.",
129 | usagetext: format(usagetext: usage))
130 | }
131 | let value = optionresult.remainder.remove(at: firstchange)
132 | return (value, optionresult.remainder)
133 | }
134 | }
135 |
136 | /// Parses the next argument, if it is not an option.
137 | ///
138 | /// - Parameters:
139 | /// - name: The placeholder in the help text.
140 | /// - description: The description of this argument.
141 | /// - Returns: The next argument, or nil if there are no more arguments or the next argument is an option.
142 | public static func singleArgument (name: String, description: String? = nil) -> Argument {
143 | return Argument(usage: description.map { ("<"+name+">", $0) }) { args in
144 | let index = args.first == "--" ? args.index(after: args.startIndex) : args.startIndex
145 | guard index != args.endIndex, !isOption(index: index, args: args) else { return (nil, args) }
146 | var args = args
147 | return (args.remove(at: index), args)
148 | }
149 | }
150 | }
151 |
152 |
153 | public protocol OptionalType {
154 | associatedtype Wrapped
155 | func toOptional() -> Wrapped?
156 | }
157 |
158 | extension Optional: OptionalType {
159 | public func toOptional() -> Optional {
160 | return self
161 | }
162 | }
163 |
164 | extension Argument where Value: OptionalType {
165 | public func `default`(_ defaultvalue: Value.Wrapped) -> Argument {
166 | let newusage = self.usage.map { ($0.title, $0.description + " Default = '\(defaultvalue)'.") }
167 | return Argument(usage: newusage) { args in
168 | let result = try self.parse(args)
169 | return (result.value.toOptional() ?? defaultvalue, result.remainder)
170 | }
171 | }
172 |
173 | /// Makes this optional argument required. An error is thrown during argument parsing if it is missing.
174 | ///
175 | /// - Parameter errormessage: The error message to display if the argument is missing.
176 | /// If no error message is provided one will be automatically generated.
177 | /// - Returns: A new argument parser with a non-optional value.
178 | public func required(errormessage: String? = nil) -> Argument {
179 | return Argument(usage: self.usage) { args in
180 | let result = try self.parse(args)
181 | guard let value = result.value.toOptional() else {
182 | let errormessage = errormessage ?? "Missing argument" + (self.usage == nil ? "." : ":")
183 | throw ArgumentError(errormessage: errormessage, usagetext: format(usagetext: self.usage))
184 | }
185 | return (value, result.remainder)
186 | }
187 | }
188 |
189 | /// Looks for multiple occurrences of an argument,
190 | /// by repeating an optional parser until it returns nil.
191 | ///
192 | /// - Returns: An array of the values the parser returned.
193 | public func `repeat`() -> Argument<[Value.Wrapped]> {
194 | return Argument<[Value.Wrapped]>(usage: self.usage) { args in
195 | var args = args
196 | var values = Array()
197 | while true {
198 | let result = try self.parse(args)
199 | guard let value = result.value.toOptional() else {
200 | return (values, result.remainder)
201 | }
202 | values.append(value)
203 | args = result.remainder
204 | }
205 | }
206 | }
207 | }
208 |
209 | extension Argument where Value == Bool {
210 | /// Counts the number of times an option argument occurs.
211 | public func count() -> Argument {
212 | return Argument(usage: self.usage) { args in
213 | let result = try self.map { $0 ? true : nil }.repeat().parse(args)
214 | return (result.value.count, result.remainder)
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/Sources/SwiftCompat.swift:
--------------------------------------------------------------------------------
1 | #if !swift(>=4.0)
2 | extension String {
3 | func dropFirst(_ n: Int = 1) -> String.CharacterView {
4 | return self.characters.dropFirst(n)
5 | }
6 |
7 | var first: Character? {
8 | return self.characters.first
9 | }
10 |
11 | var count: Int {
12 | return self.characters.count
13 | }
14 |
15 | func split(separator: Character, maxSplits: Int = Int.max, omittingEmptySubsequences: Bool = true) -> [String.CharacterView] {
16 | return self.characters.split(separator: separator, maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences)
17 | }
18 | }
19 |
20 | extension Sequence {
21 | func compactMap(_ transform: (Iterator.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
22 | return try flatMap(transform)
23 | }
24 | }
25 | #endif
26 |
27 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 |
2 | import XCTest
3 |
4 | import ModeratorTests
5 |
6 | let tests: [XCTestCaseEntry] = [
7 | testCase(Moderator_Tests.allTests),
8 | ]
9 |
10 | XCTMain(tests)
11 |
--------------------------------------------------------------------------------
/Tests/ModeratorTests/Moderator_Tests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Moderator_Tests
3 | //
4 | // Created by Kåre Morstøl on 03.11.15.
5 | // Copyright 2015 NotTooBad Software. All rights reserved.
6 | //
7 |
8 | import XCTest
9 | import Moderator
10 | import Foundation
11 |
12 | extension Array {
13 | var toStrings: [String] {
14 | return map {String(describing: $0)}
15 | }
16 | }
17 |
18 | public class Moderator_Tests: XCTestCase {
19 |
20 | func testOptionAndArgumentJoinedWithEqualSign () {
21 | let m = Moderator()
22 | let arguments = ["lskdfj", "--verbose", "--this=that=", "-b", "--", "--c=1"]
23 |
24 | do {
25 | try m.parse(arguments, strict: false)
26 | XCTAssertEqual(m.remaining, ["lskdfj", "--verbose", "--this", "that=", "-b", "--", "--c=1"])
27 | } catch {
28 | XCTFail(String(describing: error))
29 | }
30 | }
31 |
32 | /*
33 | func testPreprocessorHandlesJoinedFlags () {
34 | let arguments = ["-abc", "delta", "--echo", "-f"]
35 |
36 | let result = Argument().preprocess(arguments)
37 | XCTAssertEqual(result.toStrings, ["-a", "-b", "-c", "delta", "--echo", "-f"])
38 | }
39 | */
40 |
41 | func testParsingOption () {
42 | let m = Moderator()
43 | let arguments = ["--ignored", "-a", "b", "bravo", "--charlie"]
44 | let parsedlong = m.add(Argument.option("c", "charlie"))
45 | let parsedshort = m.add(Argument.option("a", "alpha"))
46 | let unparsed = m.add(Argument.option("b", "bravo"))
47 |
48 | do {
49 | try m.parse(arguments, strict: false)
50 | XCTAssertEqual(parsedshort.value, true)
51 | XCTAssertEqual(unparsed.value, false)
52 | XCTAssertEqual(parsedlong.value, true)
53 | XCTAssertEqual(m.remaining, ["--ignored", "b", "bravo"])
54 | } catch {
55 | XCTFail(String(describing: error))
56 | }
57 | }
58 |
59 | func testParsingOptionWithValue () {
60 | let m = Moderator()
61 | let arguments = ["--charlie", "sheen", "ignored", "-a", "alphasvalue"]
62 | let parsedshort = m.add(Argument.optionWithValue("a", "alpha"))
63 | let unparsed = m.add(Argument.option("b", "bravo"))
64 | let parsedlong = m.add(Argument.optionWithValue("c", "charlie"))
65 |
66 | do {
67 | try m.parse(arguments, strict: false)
68 | XCTAssertEqual(parsedshort.value, "alphasvalue")
69 | XCTAssertEqual(parsedlong.value, "sheen")
70 | XCTAssertEqual(unparsed.value, false)
71 | XCTAssertEqual(m.remaining, ["ignored"])
72 | } catch {
73 | XCTFail(String(describing: error))
74 | }
75 | }
76 |
77 | func testParsingOptionWithMissingValueThrows () {
78 | let m = Moderator()
79 | let arguments = ["--verbose", "--alpha"]
80 | _ = m.add(Argument.optionWithValue("a", "alpha"))
81 |
82 | XCTAssertThrowsError( try m.parse(arguments) ) { error in
83 | XCTAssertTrue(String(describing: error).contains("--alpha"))
84 | }
85 | }
86 |
87 | func testParsingMissingOptionWithValue () {
88 | let m = Moderator()
89 | let arguments = ["arg1", "arg2", "arg3"]
90 | let parsed = m.add(Argument.optionWithValue("a", "alpha").default("default"))
91 |
92 | do {
93 | try m.parse(arguments, strict: false)
94 | XCTAssertEqual(parsed.value, "default")
95 | } catch {
96 | XCTFail(String(describing: error))
97 | }
98 | }
99 |
100 | func testParsingStringArgumentWithOptionValueThrows () {
101 | let m = Moderator()
102 | let arguments = ["--verbose", "-a", "-b"]
103 | _ = m.add(Argument.optionWithValue("a", "alpha"))
104 |
105 | XCTAssertThrowsError( try m.parse(arguments) ) { error in
106 | XCTAssert(String(describing: error).contains("-a"))
107 | }
108 | }
109 |
110 | func testSingleArgument () {
111 | let m = Moderator()
112 | let arguments = ["-a", "argument", "--ignored", "--charlie"]
113 | let parsedlong = m.add(Argument.option("c", "charlie"))
114 | let parsedshort = m.add(Argument.option("a", "alpha"))
115 | let single = m.add(Argument.singleArgument(name: "argumentname"))
116 |
117 | do {
118 | try m.parse(arguments, strict: false)
119 | XCTAssertEqual(parsedshort.value, true)
120 | XCTAssertEqual(parsedlong.value, true)
121 | XCTAssertEqual(single.value, "argument")
122 | XCTAssertEqual(m.remaining, ["--ignored"])
123 |
124 | try m.parse(["-a", "--charlie", "argument2", "--ignored"], strict: false)
125 | XCTAssertEqual(single.value, "argument2")
126 |
127 | try m.parse(["-a", "--charlie", "--", "--argument3", "--ignored"], strict: false)
128 | XCTAssertEqual(single.value, "--argument3")
129 |
130 | try m.parse(["-a", "--charlie", "--"], strict: false)
131 | XCTAssertEqual(single.value, nil)
132 |
133 | try m.parse(["-a", "--charlie"], strict: false)
134 | XCTAssertEqual(single.value, nil)
135 | } catch {
136 | XCTFail(String(describing: error))
137 | }
138 | }
139 |
140 | func testMissingSingleArgument() {
141 | let m = Moderator()
142 | _ = m.add(Argument.option("c", "charlie"))
143 | _ = m.add(Argument.option("a", "alpha"))
144 | let single = m.add(Argument.singleArgument(name: "argumentname"))
145 |
146 | do {
147 | try m.parse(["-a", "-b"], strict: false)
148 | XCTAssertNil(single.value)
149 | } catch {
150 | XCTFail(String(describing: error))
151 | }
152 | }
153 |
154 | func testDefaultValue() {
155 | let m = Moderator()
156 | _ = m.add(Argument.option("c", "charlie"))
157 | _ = m.add(Argument.option("a", "alpha"))
158 | let defaultsingle = m.add(Argument.singleArgument(name: "argumentname").default("defaultvalue"))
159 |
160 | do {
161 | try m.parse(["-a", "-b"], strict: false)
162 | XCTAssertEqual(defaultsingle.value, "defaultvalue")
163 | try m.parse(["-a", "notdefaultvalue"], strict: false)
164 | XCTAssertEqual(defaultsingle.value, "notdefaultvalue")
165 | } catch {
166 | XCTFail(String(describing: error))
167 | }
168 | }
169 |
170 | func testMissingRequiredValueThrows() {
171 | let m = Moderator()
172 | _ = m.add(Argument.option("c", "charlie"))
173 | _ = m.add(Argument.optionWithValue("v", name: "optionv", description: "the value for v.").required())
174 | _ = m.add(Argument.singleArgument(name: "Argumentname", description: "Argumentname's description").required(errormessage: "Argumentname is required."))
175 |
176 | XCTAssertThrowsError( try m.parse(["-a", "-b"]) )
177 |
178 | do { try m.parse(["-v", "-b"]) } catch { print(error) }
179 | do { try m.parse(["-a", "-b"]) } catch { print(error) }
180 | do { try m.parse(["-a", "-v"]) } catch { print(error) }
181 | do { try m.parse(["-a", "-v", "vvvv"]) } catch { print(error) }
182 | }
183 |
184 | func testRepeat () {
185 | let m = Moderator()
186 | let options = m.add(Argument.optionWithValue("b").repeat())
187 | let multiple = m.add(Argument.singleArgument(name: "multiple").repeat())
188 |
189 | do {
190 | try m.parse(["-b", "b1", "-b", "b2", "notb", "-b", "b3"], strict: false)
191 | XCTAssertEqual(options.value, ["b1", "b2", "b3"])
192 | try m.parse(["one", "two", "three"], strict: true)
193 | XCTAssertEqual(multiple.value, ["one", "two", "three"])
194 | try m.parse(["one", "-a", "two", "three"], strict: false)
195 | XCTAssertEqual(multiple.value, ["one"])
196 | try m.parse([], strict: true)
197 | XCTAssertEqual(multiple.value, [])
198 | } catch {
199 | XCTFail(String(describing: error))
200 | }
201 | }
202 |
203 | func testCount() {
204 | let m = Moderator()
205 | let option = m.add(Argument.option("b").count())
206 |
207 | do {
208 | try m.parse(["-b", "-b", "b2", "notb", "-b", "b3"], strict: false)
209 | XCTAssertEqual(option.value, 3)
210 | try m.parse(["one", "two", "three"], strict: false)
211 | XCTAssertEqual(option.value, 0)
212 | try m.parse(["-b", "-b"], strict: true)
213 | XCTAssertEqual(option.value, 2)
214 | try m.parse(["one", "-b", "two", "three"], strict: false)
215 | XCTAssertEqual(option.value, 1)
216 | try m.parse([], strict: true)
217 | XCTAssertEqual(option.value, 0)
218 | } catch {
219 | XCTFail(String(describing: error))
220 | }
221 | }
222 |
223 | func testStrictParsingThrowsErrorOnUnknownArguments () {
224 | let m = Moderator()
225 | let arguments = ["--alpha", "-c"]
226 | _ = m.add(Argument.option("a", "alpha", description: "The leader."))
227 | _ = m.add(Argument.option("b", "bravo", description: "Well done!"))
228 |
229 | XCTAssertThrowsError( try m.parse(arguments, strict: true) ) { error in
230 | XCTAssertTrue(String(describing: error).contains("Unknown arguments"))
231 | XCTAssertTrue(String(describing: error).contains("The leader."), "Error should have contained usage text.")
232 | XCTAssertTrue(String(describing: error).contains("Well done!"), "Error should have contained usage text.")
233 | }
234 | }
235 |
236 | func testStrictParsing () {
237 | let m = Moderator()
238 | let arguments = ["--alpha", "-b"]
239 | _ = m.add(Argument.option("a", "alpha", description: "The leader."))
240 | _ = m.add(Argument.option("b", "bravo", description: "Well done!"))
241 |
242 | do {
243 | try m.parse(arguments, strict: true)
244 | } catch {
245 | XCTFail(String(describing: error))
246 | }
247 | }
248 |
249 | func testRemoveDoubleDashIfAlone () {
250 | let m = Moderator()
251 | let arguments = ["--"]
252 |
253 | do {
254 | try m.parse(arguments, strict: true)
255 | try m.parse(arguments, strict: false)
256 | XCTAssert(m.remaining.isEmpty)
257 | } catch {
258 | XCTFail(String(describing: error))
259 | }
260 | }
261 |
262 | func testUsageText () {
263 | let m = Moderator(description: "A very thorough and informative description.")
264 | _ = m.add(.option("a", "alpha", description: "The leader."))
265 | _ = m.add(Argument.optionWithValue("b", "bravo", description: "Well done!").default("default value"))
266 | _ = m.add(Argument.option("x", "hasnohelptext"))
267 |
268 | let usagetext = m.usagetext
269 | print(usagetext)
270 | XCTAssert(usagetext.contains("alpha"))
271 | XCTAssert(usagetext.contains("The leader"))
272 | XCTAssert(usagetext.contains("bravo"))
273 | XCTAssert(usagetext.contains("Well done"))
274 | XCTAssert(usagetext.contains("default value"))
275 |
276 | XCTAssertFalse(m.usagetext.contains("hasnohelptext"))
277 | }
278 | }
279 |
280 | extension Moderator_Tests {
281 | public static var allTests = [
282 | ("testOptionAndArgumentJoinedWithEqualSign", testOptionAndArgumentJoinedWithEqualSign),
283 | //("testPreprocessorHandlesJoinedFlags", testPreprocessorHandlesJoinedFlags),
284 | ("testParsingOption", testParsingOption),
285 | ("testParsingOptionWithValue", testParsingOptionWithValue),
286 | ("testParsingOptionWithMissingValueThrows", testParsingOptionWithMissingValueThrows),
287 | ("testParsingMissingOptionWithValue", testParsingMissingOptionWithValue),
288 | ("testParsingStringArgumentWithOptionValueThrows", testParsingStringArgumentWithOptionValueThrows),
289 | ("testSingleArgument", testSingleArgument),
290 | ("testMissingSingleArgument", testMissingSingleArgument),
291 | ("testDefaultValue", testDefaultValue),
292 | ("testMissingRequiredValueThrows", testMissingRequiredValueThrows),
293 | ("testRepeat", testRepeat),
294 | ("testCount", testCount),
295 | ("testStrictParsingThrowsErrorOnUnknownArguments", testStrictParsingThrowsErrorOnUnknownArguments),
296 | ("testStrictParsing", testStrictParsing),
297 | ("testRemoveDoubleDashIfAlone", testRemoveDoubleDashIfAlone),
298 | ("testUsageText", testUsageText),
299 | ]
300 | }
301 |
--------------------------------------------------------------------------------