Commander
Commander is a small Swift framework allowing you to craft beautiful command line interfaces in a composable way.
Usage
Simple Hello World
import Commander
let main = command { (filename:String) in
print("Reading file \(filename)...")
}
main.run()
Type-safe argument handling
The closure passed to the command function takes any arguments that conform to ArgumentConvertible
, Commander will automatically convert the arguments to these types. If they can't be converted the user will receive a nice error message informing them that their argument doesn't match the expected type.
String
, Int
, Double
, and Float
are extended to conform to ArgumentConvertible
, you can easily extend any other class or structure so you can use it as an argument to your command.
command { (hostname:String, port:Int) in
print("Connecting to \(hostname) on port \(port)...")
}
Grouping commands
You can group a collection of commands together.
Group {
$0.command("login") { (name:String) in
print("Hello \(name)")
}
$0.command("logout") {
print("Goodbye.")
}
}
Usage:
$ auth
Usage:
$ auth COMMAND
Commands:
+ login
+ logout
$ auth login Kyle
Hello Kyle
$ auth logout
Goodbye.
Describing arguments
You can describe positional arguments and options for a command to auto-generate help. This is done by passing in descriptors of these arguments.
For example, for fixed positional arguments with descriptions, you can use:
command( Argument<String>("name", description: "Your name"), Argument<String>("surname", description: "Your surname"), Argument<Int>("count", description: "Number of times to print") ) { name, surname, count in for _ in 0..<count { print("Hello \(name) \(surname)") } }.run()
Keep in mind you have to pass 3 required arguments.
Another example, to describe a command which takes two (optional) options, --name
and --count
where the default value for name is world
and the default value for count is 1
.
command(
Option("name", default: "world"),
Option("count", default: 1, description: "The number of times to print.")
) { name, count in
for _ in 0..<count {
print("Hello \(name)")
}
}
$ hello --help
Usage:
$ hello
Options:
--name
--count - The number of times to print.
$ hello
Hello world
$ hello --name Kyle
Hello Kyle
$ hello --name Kyle --count 4
Hello Kyle
Hello Kyle
Hello Kyle
Hello Kyle
Types of descriptors
- Option - An optional option with a value.
- Options - A option with a value which can be used multiple times, your command is passed an array containing all option values. You need to specify ahead of time how many values you expect. Example:
--myOption value1 value2 value3
- VariadicOption - Same as options, but instead of a fixed
count
of values, the user can just repeat the option with additional values. Example:--myOption value1 --myOption value2
- Flag - A boolean, on/off flag.
- Argument - A positional argument.
- VariadicArgument - A variadic argument
NOTE: It's important to describe your arguments after options and flags so the parser can differentiate between --option value
and --flag argument
.
Using the argument parser
NOTE: ArgumentParser
itself is ArgumentConvertible
so you can also get hold of the raw argument parser to perform any custom parsing.
command { (name:String, parser:ArgumentParser) in
if parser.hasOption("verbose") {
print("Verbose mode enabled")
}
print("Hello \(name)")
}
$ tool Kyle --verbose
Verbose mode enabled
Hello Kyle
Examples tools using Commander
- QueryKit via CocoaPods Rome
Installation
You can install Commander in many ways, with SPM (Swift Package Manager), Conche, CocoaPods or CocoaPods-Rome.
rpath
Frameworks and It's important to note that the .framework
or dynamic library file for Commander (and any other dependency) must be available at run-time for your command line tool. Unless you are using SPM.
Applications will look in their rpath
which contains paths of where it expects the .framework
s to be found at.
Using a Swift script, you can use the -F
flag for setting framework search paths, as follows:
#!/usr/bin/env xcrun swift -F Rome
import Commander
For compiled Swift code, you will need to add an rpath pointing to your dependency frameworks, as follows:
$ install_name_tool -add_rpath "@executable_path/../Frameworks/" "bin/querykit"
Where "../Frameworks" relative to the executable path is used to find the frameworks and bin/querykit
is the executable.
When installing your executable on other systems it's important to copy the frameworks and the binary.
Architecture
CommandType
CommandType
is the core protocol behind commands, it's an object or structure that has a run
method which takes an ArgumentParser
.
Both the command
functions and Group
return a command that conforms to CommandType
which can easily be interchanged.
protocol CommandType {
func run(parser:ArgumentParser) throws
}
ArgumentConvertible
The convenience command
function takes a closure for your command that takes arguments which conform to the ArgumentConvertible
protocol. This allows Commander to easily convert arguments to the types you would like to use for your command.
protocol ArgumentConvertible {
init(parser: ArgumentParser) throws
}
ArgumentParser
The ArgumentParser
is an object that allowing you to pull out options, flags and positional arguments.
License
Commander is available under the BSD license. See the LICENSE file for more info.