hypertalk/commandcougar
An elegant pure Swift library for building command line applications.
Features
- [x] Tons of class, but no classes. 100% organic pure value types.
- [x] Auto generated help menus for main command and sub-commands.
- [x] Help menu format is based on swift package manager
- [x] Supports multi command callbacks.
- [x] Swift 4 compatibility
- [x] Zero dependency
- [x] Supports Linux and
swift build
Requirements
- Mac OS X 10.10+ / Ubuntu 14.10
- Xcode 8
- Swift 4
Installation
Swift Package Manager
dependencies: [
.Package(url: "https://github.com/surfandneptune/CommandCougar.git", from: "1.0.0")
]Usage
CommandCougar supports a main command as well as subcommands. This is much like
the swift package manager interface.
### `Command`
A command is a `struct` that is used to outline the structure of your command line interface. It can have either a list of subcommands or a list of (`.required` | `.optional`) parameters.
#### Creating a `Command`
``` swift
var helloCommand = Command(
name: "hello",
overview: "Say Hello",
callback: { print($0.options, $0.parameters) },
options: [
Option(flag: .short("v"), overview: "Increase verbosity"),
Option(flag: .short("w"), overview: "Wave hello")
],
parameters:[.optional("Name")])
```
#### Evaluating a `Command`
Once a command has been created, it can be evaluated against a list of
arguments, usually taken from CommandLine.arguments. The evaluate function
creates and returns a `CommandEvaluation`.
``` swift
let arguments = ["hello", "-v", "Mr.Rogers"]
let helloEvaluation = helloCommand.evaluate(arguments: arguments)
```
Typically, the input of the arguments will be supplied by CommandLine.arguments. Please note that CommandCougar automatically drops the first argument.
``` swift
let helloEvaluation = helloCommand.evaluate(arguments: CommandLine.arguments)
```
#### Reading a `CommandEvaluation`
A `CommandEvaluation` is a `struct` for representing the results of evaluating a `Command` against a list of arguments.
``` swift
helloCommand.options // ['-v', '-w']
helloEvaluation.options // ['-v']
helloEvaluation.parameters // ['Mr.Rogers']
```
Notice the evaluation only includes the options which were seen in the arguments list.
#### Performing callbacks
Callbacks pass the `CommandEvaluation` as an input to the function
that was set in the `Command` before evaluation.
``` swift
try helloEvaluation.performCallbacks()
```
---
#### Help menu automatically generated
The help menu is auto generated and the option is added
to the command option set.
``` shell
$ hello --help
OVERVIEW: Say Hello
USAGE: hello [option] <command>
COMMANDS:
OPTIONS:
-h, --help The help menu
-v Increase verbosity
-w Wave hello
```
### `Options`
Options can have either a short flag ie `-v` or a long flag ie `--verbose`.
Options are allowed to have a single optional parameter. The flag and parameter must be joined with an `=` ie `--path=/tmp`.
``` swift
// will match -v
Option(flag: .short("v"), overview: "verbose")
// will match -v | --verbose
Option(flag: .both(short: "v", long: "verbose"), overview: "verbose")
// will match --path=/etc
Option(flag: .long("path"), overview: "File path", parameterName: "/etc")
```
---
### `Subcommands`
Many command line interfaces like [git](https://github.com/git/git) or the [swift package manager](https://github.com/apple/swift-package-manager) allow for subcommands. CommandCougar also allows this to be expressed. A rule to notice is that a command that has subcommands is not allowed to also have parameters.
##### Consider this command:
``` shell
swift package -v update --repin
```
`swift` is the main command.
`package` is a subcommand of the `swift` command with `-v` as an option.
`update` is a subcommand of the `package` command with `--repin` as an option.
A command to express this list of arguments would be as follows:
``` swift
/// Used for callback
func echo(evaluation: Command.Evaluation) throws {
print(
"\(evaluation.name) evaluated with " +
"options: \(evaluation.options) " +
"and parameters \(evaluation.parameters)"
)
}
let swiftCommand =
Command(
name: "swift",
overview: "Swift Program",
callback: echo,
options: [],
subCommands: [
Command(
name: "package",
overview: "Perform operations on Swift packages",
callback: echo,
options: [
Option(
flag: .both(short: "v", long: "verbose"),
overview: "Increase verbosity of informational output"),
Option(
flag: .long("enable-prefetching"),
overview: "Increase verbosity of informational output")
],
subCommands: [
Command(
name: "update",
overview: "Update package dependencies",
callback: echo,
options: [
Option(
flag: .long("repin"),
overview: "Update without applying pins and repin the updated versions.")
],
subCommands: [])
])
])
```
### Evaluating `Subcommands`
When evaluating the root command all subcommands will also be evaluated and their callbacks will be fired.
``` swift
do {
// normally CommandLine.arguments
let args = ["swift", "package", "-v", "update", "--repin"]
let evaluation: Command.Evaluation = try swiftCommand.evaluate(arguments: args)
try evaluation.performCallbacks()
} catch {
print(error)
}
// Output
// swift evaluated with options: [] and parameters []
// package evaluated with options: [-v] and parameters []
// update evaluated with options: [--repin] and parameters []
```
### Accessing the values of the `CommandEvaluation`
To directly access the values of the returned `CommandEvaluation`
``` swift
evaluation["package"]?.name // results in "package"
evaluation["package"]?.options["v"] // results in Option.Evaluation
evaluation["package"]?.options["v"]?.flag.shortName // results in "v"
evaluation["package"]?.options["enable-prefetching"] // results in nil
evaluation["package"]?["update"]?.options["repin"]?.flag.longName // results in "repin"
```
### Access with throw
To access parameters by index you may use `parameter(at: Int) throws -> String`. If the parameter does
not exist a `parameterAccessError` will be thrown.
This will turn:
``` swift
func callback(evaluation: CommandEvaluation) throws {
guard let first = evaluation.parameters.first else {
throw CommandCougar.Errors.parameterAccessError("Parameter not found.")
}
}
```
Into:
``` swift
func callback(evaluation: CommandEvaluation) throws {
let first = try evaluation.parameter(at: 0)
}
```
### Help menu different for subcommands
Help is also generated for subcommands
``` shell
$ swift package --help
OVERVIEW: Perform operations on Swift packages
USAGE: swift package [option] <command>
COMMANDS:
update Update package dependencies
OPTIONS:
-v, --verbose Increase verbosity of informational output
--enable-prefetching Enable prefetching in resolver
-h, --help The help menu
```
---
### [EBNF](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form)
A EBNF of the language supported by CommandCougar is as follows
```
<command> ::= <word> {<option>} ([<command>] | {<parameter>})
<option> ::= <single> | <double>
<single> ::= -<letter>=[<parameter>]
<double> ::= --<word>=[<parameter>]
<parameter> ::= <word>
<word> ::= <letter>+
<letter> ::= a | b | c | d | e...
```
### [CLOC](https://github.com/AlDanial/cloc)
A line count breakdown to show overall size of the project
```
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Swift 11 133 411 451
-------------------------------------------------------------------------------
SUM: 11 133 411 451
-------------------------------------------------------------------------------
```Communication
- If you found a bug, open an issue.
- If you have a feature request, open an issue.
- If you want to contribute, open an issue or submit a pull request.
License
CommandCougar is released under the MIT license. See LICENSE for details.
Package Metadata
Repository: hypertalk/commandcougar
Default branch: master
README: README.md