Simple & Elegant Command Line Interfaces in Swift

Overview

Command Cougar

An elegant pure Swift library for building command line applications.

Build Status

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.

You might also like...
iOS command-line tool that allows searching and downloading ipa files from the iOS App Store

ipatool for iOS This is a port of Majd Alfhaily's ipatool adapted to run on iOS Build / Installation To build this, make sure you have AppSync install

A nifty command-line tool to customize macOS icons

iconset A nifty command line tool to manage macOS icons iconset is a new command line tool for macOS that allows you to change icons for macOS apps (e

The best command-line tool to install and switch between multiple versions of Xcode.
The best command-line tool to install and switch between multiple versions of Xcode.

The best command-line tool to install and switch between multiple versions of Xcode.

Command Line Tool for interacting with MachO binaries on OSX/iOS

inject inject is a tool which interfaces with MachO binaries in order to insert load commands. Below is its help. ➜ ./inject -h OVERVIEW: inject v1.0.

ips2crash is a macOS command line too to convert a .ips file to a legacy .crash log file.

Synopsis ips2crash is a macOS command line too to convert a .ips file to a legacy .crash log file. Motivation It should be possible to read .ips file

CookCLI is provided as a command-line tool to make Cook recipe management easier

CookCLI is provided as a command-line tool to make Cook recipe management easier, and enable automation and scripting workflows for the CookLa

Adjust the volume from the command line on macOS.

volume Adjust the volume from the command line on macOS. Installation Using Mint: mint install meowmeowmeowcat/[email protected] Usage USAGE: volume numb

A Mac command-line tool that automatically downloads macOS Installers / Firmwares.
A Mac command-line tool that automatically downloads macOS Installers / Firmwares.

MIST - macOS Installer Super Tool A Mac command-line tool that automatically downloads macOS Installers / Firmwares: Features List all available macOS

macOS command line tool to return the available disk space on APFS volumes

diskspace Returns available disk space With the various APFS features the value for free disk space returned from tools such as du or df will not be a

Comments
  • Fix unreliable detection of “help” option

    Fix unreliable detection of “help” option

    The console output of this program:

    import CommandCougar
    
    func execution(evaluation: CommandEvaluation) throws {
        print(
            "\(evaluation.name) evaluated with " +
                "options: \(evaluation.options) " +
            "and parameters \(evaluation.parameters)"
        )
    }
    
    var dummyCommand = Command(
        name: "dummy",
        overview: "Dummy command",
        callback: execution,
        options: [],
        parameters: []
    )
    
    do {
        let args = ["dummy", "-h"]
        let evaluation = try dummyCommand.evaluate(arguments: args)
        try evaluation.performCallbacks()
    } catch {
        print(error)
        print(dummyCommand.help())
    }
    

    … is this:

    dummy evaluated with options: [-h] and parameters []
    

    … while the expected console output would have been this:

    OVERVIEW: Dummy command
    
    USAGE: dummy [options] 
    
    OPTIONS:
       -h, --help                    The help menu
    

    There are four occurrences of <option>.flag == "help" in the source, which fail to match when providing -h, instead of --help. A quick fix for this is to replace "help" with Option.help.flag.

    opened by regexident 4
  • It not run

    It not run

    Hi, I create a Xcode project of command tool, and I use the commandCougar by SPM.

    import Foundation
    import CommandCougar
    import ColorizeSwift
    
    print("Hi,see again!".red())
    
    var helloCommand = Command(
        name: "aa",
        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")]
    )
    
    

    but ,it looks not work. Only print Hi,see again on terminal, how can I let the hellocommand run?

    opened by LONGDDSF 1
Releases(1.0.0)
Owner
hypertalk
Hypertalk
hypertalk
Compose beautiful command line interfaces in Swift

Commander is a small Swift framework allowing you to craft beautiful command line interfaces in a composable way. Usage Simple Hello World i

Kyle Fuller 1.5k Dec 29, 2022
CommandLineKit - A pure Swift library for creating command-line interfaces

CommandLineKit A pure Swift library for creating command-line interfaces. Note: This project is no longer maintained. It's preserved here for historic

Ben Gollmer 1.1k Dec 1, 2022
Simple command line interactive game to practice German (or English) with simple short stories.

Gif With GIF you can practice German or English with short stories. The stories are written in an easy language, and you can interact with the charact

Pedro Muniz 8 Nov 3, 2022
🕳 A simple command line tool to punch hole to reduce disk usage on APFS volume for such as a raw disk image.

HolePunch NAME holepunch -- A simple command line tool to punch hole to reduce disk usage on APFS volume for such as a raw disk image. SYNOPSIS holepu

Yoshimasa Niwa 15 Nov 24, 2022
Swift-cli - Example of building command-line tools in Swift

swift-cli Example of building command-line tools in Swift Step 1: Create CLI wit

Noah Gift 2 Jan 17, 2022
Command line utility to profile compilation time of Swift project.

xcprofiler Command line utility to profile compilation time of Swift project. This tool is developed in working time for Cookpad. Installation gem ins

Kohki Miki 334 Jan 3, 2023
Josephus - A command line tool to solve Josephus problem in Swift

josephus A command line tool to solve Josephus problem in Swift

Masahiro Oono 0 Jan 25, 2022
Swiftline is a set of tools to help you create command line applications

Swiftline is a set of tools to help you create command line applications. Swiftline is inspired by highline Swiftline contains the following: Colorize

Omar Abdelhafith 1.2k Dec 29, 2022
A Mac command-line tool that generates kick-ass Jamf Pro reports.

KMART - Kick-Ass Mac Admin Reporting Tool A command-line utility generating kick-ass Jamf Pro reports: Features Reporting on the following Jamf Pro ob

Nindi Gill 86 Dec 15, 2022
ipatool is a command line tool that allows you to search for iOS apps on the App Store and download a copy of the app package, known as an ipa file.

ipatool is a command line tool that allows you to search for iOS apps on the App Store and download a copy of the app package, known as an ipa file.

Majd Alfhaily 3k Dec 30, 2022