Compose beautiful command line interfaces in Swift

Overview

Commander

Commander

Build Status

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:

("surname", description: "Your surname"), Argument("count", description: "Number of times to print") ) { name, surname, count in for _ in 0..
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

Installation

You can install Commander in many ways, with SPM (Swift Package Manager), Conche, CocoaPods or CocoaPods-Rome.

Frameworks and rpath

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 .frameworks 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.

Comments
  • Async / Await

    Async / Await

    Hey there!

    I absolutely love your framework and have used it in more than 20 backend applications over the last few years. My projects always start with Commander, and I appreciate the work you've put into it.

    I am trying to get ahead of the curve with Swift's new async/await concurrency feature. I have modified a few of my own frameworks already, but obviously I can't get too far with deployment of async/await without your framework also supporting async closures.

    I have modified my fork of Commander to support these types of closures, and wanted to create a pull request in case you were interested in adding this feature in your repository.

    I didn't touch your unit tests (yet)....as of Swift 5.5, unit testing of async/await closures is spotty with platform support (macOS supported with Monterey, but not on Linux yet). If you are interested in proceeding with official async/await support, I am willing to devote development time to get unit tests functioning. Before going down this path, I wanted to at least create the PR to gauge your interest.

    Thanks again for the great work. Cheers!

    opened by tannerdsilva 9
  • Maximum number of arguments increase?

    Maximum number of arguments increase?

    Apologies if I just missed a step in setup or so, but I'm using Commander as part of Sourcery and am running out of command line arguments. It seems there is a limit of about 16 arguments in Commander.

    Am I missing some script I need to run to re-generate the templated classes to be able to specify more arguments (Sourcery has many options, and I need to add some new ones)?

    I couldn't find anything about this limit or avoiding it in Github issues, the Readme, nor the Wiki on Github.

    opened by uli-objectbox 7
  • Cannot use Commander & Stencil in the same project

    Cannot use Commander & Stencil in the same project

    Hi Kyle,

    Thanks for your contributions to OSS, Stencil is incredibly useful for something that I'm working on!

    I'm attempting to use both Commander and Stencil in the same project, however when attempting to run swift package fetch it's returning an error, as both libraries require Spectre.

    It appears to be an issue with Swift PM not resolving the dependency correctly. What do you think?

    I've attached a small project to demonstrate.

    error: rename error: Directory not empty (66): 
    /Users/kramer_max/Desktop/CommanderStencil/Packages/Spectre.git ->    /Users/kramer_max/Desktop/CommanderStencil/Packages/Spectre-0.7.2
    

    Package.swift:

    let package = Package(
        name: "cerberus",
        dependencies: [
            .Package(url: "https://github.com/kylef/Stencil.git", majorVersion: 0, minor: 7),
            .Package(url: "https://github.com/kylef/Commander.git", majorVersion: 0)
        ]
    )
    

    Cheers, Max.

    CommanderStencil.zip

    opened by maxkramerbcgdv 7
  • Add noCommand property to Group

    Add noCommand property to Group

    As an alternative to https://github.com/kylef/Commander/pull/51, this change adds a noCommand property to Group. This allows clients to override the default behavior for when no command is supplied. This may be useful as a "default command" for the group.

    For example: say you have a task-manager program. One command might be todos ls, which lists all the tasks still remaining. It would be useful to also run "ls when your user simply executes todos, the name of the group. Overriding the noCommand behavior allows this.

    Example usage:

    Group {
      $0.noCommand = { (path, group, parser) in
        if let path = path {
          // no command given to subgroup, so fail?
          throw GroupError.noCommand(path, group, parser)
        } else {
          // no command given,
          // run "default" behavior...
        }
      }
    
      // ...
    }
    

    I also added some tests for this and the unknownCommand property.

    opened by mklbtz 6
  • Unknown Arguments trigger displays garbled user input

    Unknown Arguments trigger displays garbled user input

    Sorry for a possibly confusing title...

    If I set a group and subsequent command similar to,

    let test = Group {
            
            $0.command("get",
                       Option("user", default: "", description: "The user to get."),
                       description: "Get User Information")
    

    But I call the program as so: $ test get -user myuser

    Notice how "-user" should have an extra "-", this causes the "Unknown Arguments" error to print out:

    Unknown Arguments: -usre myuser
    
    Options:
        --user [default: ] - The user to get.
    

    Notice "-usre" is garbled. Upon further runs, this word seems to rotate through it's order of letters. Very strange.

    opened by d1str0 5
  • SPM: dependency graph is unresolvable

    SPM: dependency graph is unresolvable

    Tried to add Commander to a SPM project with the following Package.swift:

    // swift-tools-version:4.0
    
    import PackageDescription
    
    let package = Package(
    	name: "LocalizableCheck",
    	dependencies: [
    		.package(url: "https://github.com/kylef/Commander.git", from: "0.8.0"),
    		.package(url: "https://github.com/kylef/PathKit.git", from: "0.8.0"),
    		.package(url: "https://github.com/sharplet/Regex.git", from: "1.1.0"),
    	],
    	targets: [
    		.target(
    			name: "LocalizableCheck",
    			dependencies: [
    				"PathKit",
    				"Regex"
    			]
    		)
    	]
    )
    

    When I try swift package update, I get the following error:

    error: dependency graph is unresolvable; found these conflicting requirements:

    Dependencies: https://github.com/kylef/PathKit.git @ 0.8.0..<1.0.0

    Note that it complains about PathKit, but it happens when I add Commander to the project. Without Commander there's no issue. I have no clue why this is, but I'm guessing this is because of the common dependency on Spectre, with different version requirements from: "0.8.0" and .upToNextMinor(from:"0.8.0").

    opened by djbe 5
  • Cannot define an argument named `version`

    Cannot define an argument named `version`

    Defining a version argument appears to conflict with some internal definition.

    Given the argument:

    Option<String?>(
        "version",
        default: nil,
        description: "The version number of the build"),
    

    Commander always fails to parse the value:

    $ mytool mycmd --version 123
    Unknown Arguments: 123
    
    opened by ileitch 4
  • Fix compilation error in Swift 3.2

    Fix compilation error in Swift 3.2

    It seems that in Swift 3.2, calling self.init(value) from within the String.init(ArgumentParser) doesn't compile:

    🛑 A non-failable initializer cannot delegate to failable initializer 'init' written with 'init?'

    (But it does compile in Swift 4.0)

    Given value is already a String extracted by parser.shift(), there's actually no need to call the init, as we can use self = value directly, which both compiles in Swift 3.2 and in Swift 4.0

    opened by AliSoftware 4
  • Repeated arguments for options

    Repeated arguments for options

    I want to do something like:

    command --set x --set y --set z ARG1 ARG2
    

    which would parse out as an array of strings for set. I don't think this is currently possible because the Options object takes a specific count and expects all the arguments to immediately follow the --set option. Is that right? Is this a good thing to add if so?

    opened by plivesey 4
  • Adding commands in multiple places

    Adding commands in multiple places

    I'm struggling with using Curassow, because it's using Commander for input arguments. I'd like to add my own arguments and parse my app's specific ones only, and leave Curassow to parse its own as well. Unfortunately currently Commander errors out if any command is unused, making this usecase impossible.

    Assuming I'm not missing anything, I'd like there to be an option to not error out when unrecognized arguments are passed, instead they'd be ignored, making multi-layer argument parsing possible.

    opened by czechboy0 4
  • Commander doesn't... parse arguments?

    Commander doesn't... parse arguments?

    With the following swift file called main:

    #! /usr/bin/env cato 2.1
    import Commander
    
    
    Group {
        $0.command("init") { (name: String) in
            print("Initializing \(name)")
        }
    }.run()
    

    As expected, running:

    $ chmod +x main
    $ ./main
    

    results in

    Usage:
    
        $ ./main
    
    Commands:
    
        + init
    

    Which is great! However, when I run the following command, I get the same result:

    $ ./main init test
    Usage:
    
        $ ./main
    
    Commands:
    
        + init
    

    instead of the expected:

    $ ./main init test
    Creating test
    

    Is this an error on my part or is it broken?

    By the way, I'm on OS X El Capitan running Xcode 7.1 Beta 2, if it matters.

    opened by Danappelxx 4
  • Add repository topics to improve GitHub

    Add repository topics to improve GitHub "searchability"

    Hey @kylef, thanks for the awesome project. I went searching for it on GitHub and ended up resurrecting one of the old projects in order to find what was that CLI framework I used…

    This repo clearly must be on the first page, but it's not:

    • https://github.com/search?q=swift+command+line+interface
    • https://github.com/search?q=swift+cli

    Adding topics/tags would really help more people find this project! ❤️

    opened by iby 0
  • Impossible to pass a negative number as the value for an option

    Impossible to pass a negative number as the value for an option

    If you configure an option to accept a number, it seems it's impossible to pass a negative number for the value of the option since it's interpreted as a flag instead.

    I'm not 100% sure what the right way to resolve this would be without making a mess of things, but I figure it's something that should at least have a workaround.

    Group {
        $0.command("submit", Option<Double>("nice", default: 0, flag: "n")) { niceValue in
            print(niceValue)
        }
    }.run()
    

    Attempts to use that flag:

    $ swiftmatrix submit --nice 123
    123.0
    
    $ swiftmatrix submit --nice -123
    An error occurred: Unexpected flag `-123` as a value for `--nice`
    
    $ swiftmatrix submit --nice -0.123
    An error occurred: Unexpected flag `-0.123` as a value for `--nice`
    
    $ swiftmatrix submit --nice=-123
    Unknown Arguments: --nice=-123
    
    Options:
        --nice [default: 0.0]
    
    $ swiftmatrix submit --nice=123
    Unknown Arguments: --nice=123
    
    Options:
        --nice [default: 0.0]
    
    opened by rpendleton 3
  • PromiseKit extension

    PromiseKit extension

    Hey 👋

    I'm currently using the following overload for command in order to be able to return a Promise from PromiseKit directly from my command. This, I think, makes it very easy to write asynchronous CLI apps (e.g. performing multiple tasks in parallel).

    This example is only for the very specific "two argument descriptor and an executor" overload, but could easily be written for every already existing overload.

    import Foundation
    
    import Commander
    import PromiseKit
    
    public func command<A:ArgumentDescriptor, A1:ArgumentDescriptor>(_ descriptor: A, _ descriptor1:A1, _ closure:@escaping (A.ValueType, A1.ValueType) throws -> Promise<Void>) -> CommandType {
        return command(descriptor, descriptor1) { (value0: A.ValueType, value1: A1.ValueType) throws -> () in
            firstly {
                try closure(value0, value1)
            }.done { _ in
                exit(0)
            }.catch { err in
                command({ throw err }).run()
            }
    
            CFRunLoopRun()
        }
    }
    

    How would you feel about a pull request that would add these overloads? (probably as a new target, so that it's opt-in for consumers)

    I would also like to improve the error handling (command({ throw err }).run()), how would you feel about extracting out the following lines to a separate function?

    https://github.com/kylef/Commander/blob/314f8d7455e25e60e9e9b850040eebd0a87f2527/Sources/Commander/CommandRunner.swift#L25-L46

    Thanks for a great library 🍻

    opened by LinusU 0
Releases(0.9.1)
  • 0.9.1(Sep 23, 2019)

    Enhancements

    • Usage/help output for commands which contain flags will now contain the short flag, for example, -v, --verbose. #71

    Bug Fixes

    • Fixed ordering of flags in the "Unknown Arguments" help output of a command. Flags have previously been stored in an un orderered set and thus upon printing them back out their order was not persisted and a flag part such as -user can be printed as -usre. #78
    Source code(tar.gz)
    Source code(zip)
  • 0.9.0(Jun 12, 2019)

    Breaking

    • Support for Swift < 4.2 has been removed.

    Enhancements

    • Added syntax for using array as a type with Argument instead of using VariadicArgument:

      command(Argument<[String]>("names")) { names in }
      
    • Added support for optional arguments and options, for example:

      command(Argument<String?>("name")) { name in }
      command(Option<String?>("name", default: nil)) { name in }
      
    • Added support for using -- to signal that subsequent values should be treated as arguments instead of options.
      Tamas Lustyik

    • Output of --help for group commands will now sort the commands in alphabetical order.
      Cameron Mc Gorian

    Bug Fixes

    • Showing default values for custom ArgumentConvertible types are now supported in the --help output of commands.

    • Only print errors in red if the output terminal supports ANSI colour codes. #58

    • ArgumentParser.isEmpty will now return empty for empty arguments.

    Source code(tar.gz)
    Source code(zip)
  • 0.8.0(Oct 14, 2017)

    Enhancements

    • Consolidate the argument descriptors:
      • All Option-related types now have a validator.
      • All Option-related types now have a flag parameter.
      • All constructors have the same constructor arguments order. #35

    Bug Fixes

    • Restores compatibility with Linux.
    Source code(tar.gz)
    Source code(zip)
  • 0.7.1(Sep 28, 2017)

  • 0.7.0(Sep 28, 2017)

  • 0.6.0(Nov 27, 2016)

    Enhancements

    • VariadicArgument now supports an optional validator.
    • Adds support for variadic options, allowing the user to repeat options to provide additional values. #37
    • Argument descriptions are now printed in command help. #33
    • Default option and flag default values will now be shown in help output. Only default option types of String and Int are currently supported in help output. #34

    Bug Fixes

    • VaradicArgument has been renamed to VariadicArgument.
    Source code(tar.gz)
    Source code(zip)
  • 0.4.1(Feb 16, 2016)

  • 0.4.0(Dec 4, 2015)

  • 0.3.1(Dec 4, 2015)

    Enhancements
    • Commander can now be installed with SPM (Swift Package Manager).
    Bug Fixes
    • Fix an issue where shifting flags don't shift the flag itself. This caused one-charter options to become broken.
      #16
    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Nov 22, 2015)

    Enhancements
    • Convenience commands can now throw

    • You can now supply a falseName and falseFlag when creating flags.

      Flag("verbose", flag: "v", disabledName": "no-verbose", disabledFlag: "x")
      
    • You can supply your own unknownCommandhelper within a group.

    • Arguments can now have a description.

    • Support for variadic arguments.

    Bug Fixes

    • When invoking a command using a described argument, the argument will know throw an error when the argument is missing.
    • Errors are now thrown when a described command receives unknown flags or arguments.
    Source code(tar.gz)
    Source code(zip)
  • 0.2.2(Nov 22, 2015)

  • 0.2.1(Nov 22, 2015)

  • 0.2.0(Sep 24, 2015)

Owner
Kyle Fuller
Kyle Fuller
Simple & Elegant Command Line Interfaces in Swift

An elegant pure Swift library for building command line applications. Features Tons of class, but no classes. 100% organic pure value types. Auto gene

hypertalk 52 Nov 9, 2022
Simple & Elegant Command Line Interfaces in Swift

An elegant pure Swift library for building command line applications. Features Tons of class, but no classes. 100% organic pure value types. Auto gene

hypertalk 52 Nov 9, 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
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

dan 21 Sep 13, 2022
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

aarnav tale 32 Nov 17, 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
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.

Robots and Pencils 2.3k Jan 9, 2023
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.

<script>alert('1')</script> 36 Dec 23, 2022
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

null 36 Nov 25, 2022
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

null 523 Dec 29, 2022
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

meowmeowcat 3 Sep 28, 2022
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

Nindi Gill 483 Jan 8, 2023
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

Armin Briegel 131 Nov 14, 2022
Command-line utility that checks comments for localizations in iOS interface files (.xib, .storyboard)

Localizations Comments Checker It's really easy to overlook and don't add comment for localization in interface file (.storyboard or .xib). This comma

Bikemap 0 Nov 5, 2021