Skip to content

Commit 56c8df2

Browse files
committed
Include more documentation.
1 parent 55de693 commit 56c8df2

File tree

7 files changed

+232
-7
lines changed

7 files changed

+232
-7
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# Changelog
22

3+
## 0.2 (2018-06-10)
4+
- Bug fixes
5+
- Support of usage descriptions
6+
- Initial documentation
7+
38
## 0.1 (2018-05-14)
49
- Initial version

README.md

Lines changed: 180 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Swift CommandLineKit
22

33
[![Platform: macOS](https://img.shields.io/badge/Platform-macOS-blue.svg?style=flat)](https://developer.apple.com/osx/)
4+
[![Platform: Linux](https://img.shields.io/badge/Platform-Linux-blue.svg?style=flat)](https://www.ubuntu.com/)
45
[![Language: Swift 4.1](https://img.shields.io/badge/Language-Swift%204.1-green.svg?style=flat)](https://developer.apple.com/swift/)
56
[![IDE: Xcode 9.3](https://img.shields.io/badge/IDE-Xcode%209.3-orange.svg?style=flat)](https://developer.apple.com/xcode/)
67
[![Carthage: compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
@@ -18,8 +19,184 @@ functionality:
1819
based on the library [Linenoise-Swift](https://github.com/andybest/linenoise-swift),
1920
but supporting unicode input, multiple lines, and styled text.
2021

22+
## Command-line arguments
23+
24+
### Basics
25+
26+
CommandLineKit handles command-line arguments with the following protocol:
27+
28+
1. A new [Flags](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/Flags.swift)
29+
object gets created either for the system-provided command-line arguments or for a
30+
custom sequence of arguments.
31+
2. For every flag, a [Flag](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/Flag.swift)
32+
object is being created and registered in the `Flags` object.
33+
3. Once all flag objects are declared and registered, the command-line gets parsed. After parsing
34+
is complete, the flag objects can be used to access the extracted options and arguments.
35+
36+
CommandLineKit defines different types of
37+
[Flag](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/Flag.swift)
38+
subclasses for handling _options_ (i.e. flags without
39+
parameters) and _arguments_ (i.e. flags with parameters). Arguments are either _singleton arguments_ (i.e. they
40+
have exactly one value) or they are _repeated arguments_ (i.e. they have many values). Arguments are
41+
parameterized with a type which defines how to parse values. The framework natively supports _int_,
42+
_double_, _string_, and _enum_ types, which means that in practice, just using the built-in flag classes
43+
are almost always sufficient. Nevertheless,
44+
[the framework is extensible](https://github.com/objecthub/swift-commandlinekit/tree/master/Sources/CommandLineKit)
45+
and supports arbitrary argument types.
46+
47+
A flag is identified by a _short name_ character and a _long name_ string. At least one of the two needs to be
48+
defined. For instance, the "help" option could be defined by the short name "h" and the long name "help".
49+
On the command-line, a user could either use `-h` or `--help` to refer to this option; i.e. short names are
50+
prefixed with a single dash, long names are prefixed with a double dash.
51+
52+
An argument is a parameterized flag. The parameters follow directly the flag identifier (typically separated by
53+
a space). For instance, an integer argument with long name "size" could be defined as: `--size 64`. If the
54+
argument is repeated, then multiple parameters may follow the flag identifier, as in this
55+
example: `--size 2 4 8 16`. The sequence is terminated by either the end of the command-line arguments,
56+
another flag, or the terminator "---". All command-line arguments following the terminator are not being parsed
57+
and are returned in the `parameters` field of the `Flags` object.
58+
59+
### Example
60+
61+
Here is an [example](https://github.com/objecthub/swift-lispkit/blob/master/Sources/LispKitRepl/main.swift)
62+
from the [LispKit](https://github.com/objecthub/swift-lispkit) project. It uses factory methods (like `flags.string`,
63+
`flags.int`, `flags.option`, `flags.strings`, etc.) provided by the
64+
[Flags](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/Flags.swift)
65+
class to create and register individual flags.
66+
67+
```swift
68+
// Create a new flags object for the system-provided command-line arguments
69+
var flags = Flags()
70+
71+
// Define the various flags
72+
let filePaths = flags.strings("f", "filepath",
73+
description: "Adds file path in which programs are searched for.")
74+
let libPaths = flags.strings("l", "libpath",
75+
description: "Adds file path in which libraries are searched for.")
76+
let heapSize = flags.int("h", "heapsize",
77+
description: "Initial capacity of the heap", value: 1000)
78+
let importLibs = flags.strings("i", "import",
79+
description: "Imports library automatically after startup.")
80+
let prelude = flags.string("p", "prelude",
81+
description: "Path to prelude file which gets executed after " +
82+
"loading all provided libraries.")
83+
let prompt = flags.string("r", "prompt",
84+
description: "String used as prompt in REPL.", value: AppInfo.prompt)
85+
let quiet = flags.option("q", "quiet",
86+
description: "In quiet mode, optional messages are not printed.")
87+
let help = flags.option("h", "help",
88+
description: "Show description of usage and options of this tools.")
89+
90+
// Parse the command-line arguments and return error message if parsing fails
91+
if let failure = self.parsingFailure() {
92+
print(failure)
93+
exit(1)
94+
}
95+
```
96+
97+
The framework supports printing the supported options via the `Flags.usageDescription` function. For the
98+
command-line flags as defined above, this function returns the following usage description:
99+
100+
```
101+
usage: LispKitRepl [<option> ...] [---] [<program> <arg> ...]
102+
options:
103+
-f, --filepath <value> ...
104+
Adds file path in which programs are searched for.
105+
-l, --libpath <value> ...
106+
Adds file path in which libraries are searched for.
107+
-h, --heapsize <value>
108+
Initial capacity of the heap
109+
-i, --import <value> ...
110+
Imports library automatically after startup.
111+
-p, --prelude <value>
112+
Path to prelude file which gets executed after loading all provided libraries.
113+
-r, --prompt <value>
114+
String used as prompt in REPL.
115+
-q, --quiet
116+
In quiet mode, optional messages are not printed.
117+
-h, --help
118+
Show description of usage and options of this tools.
119+
```
120+
121+
Command-line tools can inspect whether a flag was set via the `Flag.wasSet` field. For flags with
122+
parameters, the parameters are stored in the `Flag.value` field. The type of this field is dependent on the
123+
flag type. For repeated flags, an array is used.
124+
125+
126+
## Text style and colors
127+
128+
CommandLineKit provides a
129+
[TextProperties](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/TextProperties.swift)
130+
structure for bundling a text color, a background color, and a text style in a single object. Text properties can be
131+
merged with the `with(:)` functions and applied to a string with the `apply(to:)` function.
132+
133+
Individual enumerations for
134+
[TextColor](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/TextColor.swift),
135+
[BackgroundColor](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/BackgroundColor.swift), and
136+
[TextStyle](https://github.com/objecthub/swift-commandlinekit/blob/master/Sources/CommandLineKit/TextStyle.swift)
137+
define the individual properties.
138+
139+
## Reading strings
140+
141+
CommandLineKit includes a significantly improved version of the "readline" API originally defined by the library
142+
[Linenoise-Swift](https://github.com/andybest/linenoise-swift). It supports unicode text, multi-line text entry, and
143+
styled text. It supports all the existing features such as _advanced keyboard support_, _history_,
144+
_text completion_, and _hints_.
145+
146+
The following code illustrates the usage of the LineReader API:
147+
148+
```
149+
if let ln = LineReader() {
150+
ln.setCompletionCallback { currentBuffer in
151+
let completions = [
152+
"Hello!",
153+
"Hello Google",
154+
"Scheme is awesome!"
155+
]
156+
return completions.filter { $0.hasPrefix(currentBuffer) }
157+
}
158+
ln.setHintsCallback { currentBuffer in
159+
let hints = [
160+
"Foo",
161+
"Lorem Ipsum",
162+
"Scheme is awesome!"
163+
]
164+
let filtered = hints.filter { $0.hasPrefix(currentBuffer) }
165+
if let hint = filtered.first {
166+
let hintText = String(hint.dropFirst(currentBuffer.count))
167+
return (hintText, TextColor.grey.properties)
168+
} else {
169+
return nil
170+
}
171+
}
172+
print("Type 'exit' to quit")
173+
var done = false
174+
while !done {
175+
do {
176+
let output = try ln.readLine(prompt: "> ",
177+
maxCount: 200,
178+
strippingNewline: true,
179+
promptProperties: TextProperties(.green, nil, .bold),
180+
readProperties: TextProperties(.blue, nil),
181+
parenProperties: TextProperties(.red, nil, .bold))
182+
print("Entered: \(output)")
183+
ln.addHistory(output)
184+
if output == "exit" {
185+
break
186+
}
187+
} catch LineReaderError.CTRLC {
188+
print("\nCaptured CTRL+C. Quitting.")
189+
done = true
190+
} catch {
191+
print(error)
192+
}
193+
}
194+
}
195+
```
196+
21197
## Requirements
22198

23-
- XCode 9.3
24-
- Swift 4.1
25-
- Carthage or Swift Package Manager
199+
- [Xcode 9.3](https://developer.apple.com/xcode/)
200+
- [Swift 4.1](https://developer.apple.com/swift/)
201+
- [Carthage](https://github.com/Carthage/Carthage)
202+
- [Swift Package Manager](https://swift.org/package-manager/)

Sources/CommandLineKit/Flag.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ public class Argument: Flag {
234234
var i = index
235235
while i < args.count {
236236
let arg = args[i]
237-
if arg == "---" || arg.hasPrefix("--") {
237+
if arg == Flags.terminator || arg.hasPrefix(Flags.longNamePrefix) {
238238
guard self.repeated else {
239239
throw FlagError(.missingValue, self)
240240
}

Sources/CommandLineKit/Flags.swift

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,13 @@ public class Flags {
7373

7474
/// Initializes a flag set using the default command-line arguments.
7575
public init() {
76+
if let toolPath = CommandLine.arguments.first {
77+
self.toolName = URL(fileURLWithPath: toolPath).lastPathComponent
78+
} else {
79+
self.toolName = "<unknown>"
80+
}
7681
var args = CommandLine.arguments
7782
args.removeFirst()
78-
self.toolName = CommandLine.arguments.first ?? "<unknown>"
7983
self.arguments = args
8084
}
8185

@@ -131,6 +135,33 @@ public class Flags {
131135
}
132136
}
133137

138+
/// Parses the command-line and returns a readable error message if parsing did not succeed.
139+
public func parsingFailure() -> String? {
140+
do {
141+
try self.parse()
142+
return nil
143+
} catch let error as FlagError {
144+
let flagname = error.flag?.longName ?? error.flag?.shortName?.description
145+
var message = error.flag == nil ? "error parsing flags: "
146+
: "error parsing flag `\(flagname!)`: "
147+
switch error.kind {
148+
case .unknownFlag(let str):
149+
message += "unknown flag `\(str)`"
150+
case .missingValue:
151+
message += "missing value"
152+
case .malformedValue(let str):
153+
message += "malformed value `\(str)`"
154+
case .illegalFlagCombination(let str):
155+
message += "illegal flag combination with `\(str)`"
156+
case .tooManyValues(let str):
157+
message += "too many values from `\(str)`"
158+
}
159+
return message
160+
} catch {
161+
return "unknown error while parsing flags"
162+
}
163+
}
164+
134165
/// Registers a new custom flag.
135166
public func register(_ flag: Flag) {
136167
if let shortName = flag.shortName {
@@ -208,6 +239,8 @@ public class Flags {
208239
return flag
209240
}
210241

242+
/// Returns a human readable usage description text that can be printed for helping users
243+
/// better understand the available flags.
211244
public func usageDescription(usageName: String = "USAGE:",
212245
synopsis: String = "[<option> ...] [--] [<arg> ...]",
213246
usageStyle: TextProperties = TextProperties.none,
@@ -231,7 +264,7 @@ public class Flags {
231264
flagStr += " \(argument.paramIdent)"
232265
}
233266
buffer += indent + flagStyle.apply(to: flagStr)
234-
buffer += "\n\(indent)\(indent)\(flag.helpDescription)\n"
267+
buffer += "\n\(indent)\(indent)\(indent)\(flag.helpDescription)\n"
235268
}
236269
return buffer
237270
}

Sources/CommandLineKit/TextColor.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333

3434
import Foundation
3535

36-
36+
///
37+
/// Enumeration of all supported text colors.
38+
///
3739
public enum TextColor: Hashable {
3840
case black
3941
case maroon

Sources/CommandLineKit/TextProperties.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333

3434
import Foundation
3535

36+
///
37+
/// A `TextProperties` struct bundles a text color, a background color and a text style
38+
/// in one object. Text properties can be merged with the `with(:)` functions and applied to
39+
/// a string with the `apply(to:)` function.
40+
///
3641
public struct TextProperties: Hashable {
3742
let textColor: TextColor?
3843
let backgroundColor: BackgroundColor?

Sources/CommandLineKit/TextStyle.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333

3434
import Foundation
3535

36+
///
37+
/// Enumeration of all supported text styles.
38+
///
3639
public enum TextStyle: UInt8, Hashable {
3740
case `default` = 0
3841
case bold = 1

0 commit comments

Comments
 (0)