Awesome
An elegant pure Swift library for building command line applications.
Features
- Tons of class, but no classes. 100% organic pure value types.
- Auto generated help menus for main command and sub-commands.
- Help menu format is based on swift package manager
- Supports multi command callbacks.
- Swift 4 compatibility
- Zero dependency
- 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
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
.
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.
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.
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.
try helloEvaluation.performCallbacks()
Help menu automatically generated
The help menu is auto generated and the option is added to the command option set.
$ 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
.
// 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 or the 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:
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:
/// 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.
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
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:
func callback(evaluation: CommandEvaluation) throws {
guard let first = evaluation.parameters.first else {
throw CommandCougar.Errors.parameterAccessError("Parameter not found.")
}
}
Into:
func callback(evaluation: CommandEvaluation) throws {
let first = try evaluation.parameter(at: 0)
}
Help menu different for subcommands
Help is also generated for subcommands
$ 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
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
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.