Home

Awesome

Command Cougar

An elegant pure Swift library for building command line applications.

Build Status

Features

Requirements


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

License

CommandCougar is released under the MIT license. See LICENSE for details.