Skip to content

ilyapuchka/cli-mate

Repository files navigation

CLImate

Define CLI (command line interface) commands using CommonParsers.

Usage

Functions

The easiest way to define commands with CLImate is to use builtin Command type. You start with defining a command as a function like this:

import CLImate
import CommonParsers
import Prelude

func hello(name: String, verbose: Bool) -> Command {
    return Command(args: (name, verbose)) {
        if verbose {
            print("Nice to see you, \(name)!")
        } else {
            print("Hello, \(name)!")
        }
    }
}

Note that arguments passed to the Command.init are grouped in a tuple and also are captured by its run closure. You can already use this function to create and run a command:

hello(name: "World", verbose: false).run()
// Hello, World!

but that's not very useful. Now using this command function you can create your application and describe this command:

let app: CLI<Command> = [
    Command.make(hello)
        <¢> command(
            name: "hello",
            description: "greeting"
        )
        -- arg(
            name: "name", short: "n",
            description: "a name",
            example: "World"
        )
        -- option(
            name: "verbose",
            description: "be verbose"
        )
]

This will define an app with a command named hello that accepts a name parameter and a verbose option and runs a command returned by hello function. Now you can run the app with app.run(). This will parse the command line arguments and invoke one of the registerred commands if it can match it, or throw an error.

The app and every command in it will automatically get a --help option. When provided the app will output usage instructions for the whole app or for particular command, compiled using description and example values of commands and their arguments:

app.run(["--help"])
app.run(["hello", "--help"])

Output:

hello: greeting
    --name (-n) String: a name
    --verbose: be verbose

Example:
    hello --name World --verbose

Enums or Structs

Alternatively you can define your commands using enums or structs:

// Define commands type
enum Commands {
    case hello(name: String, verbose: Bool)
}

// Define commands and their arguments
let app: CLI<Commands> = [
    Commands.hello
        <¢> command...
]

In this case app.run() will accept a closure to which it will pass the instance of your enum or struct that it created based on the passed arguments and command description:

try app.run() { cmd in
    // cmd == Commands.hello(name: "World", verbose: false) 
}

This enum or struct type should implement Matchable protocol that framework will use to match commands with arguments. For enums it's possible to use the default implementation provided by the library, so the only thing you would need to do is adding conformance to the Matchable protocol, but for structs you will need to implement it manually, which can look somehting like this:

extension StructCommand: Matchable, Equatable {
    func match<A>(_ constructor: (A) -> StructCommand) -> A? {
        if let values = (self.param1, self.param2, self.param3) as? A, self == constructor(values) {
            return values
        }
        return nil
    }
}

If you choose not to implement this protocol you can define partial isomorphisms for each command manually:

extension Commands {
    enum iso {
        static let hello = parenthesize(
            PartialIso(
                apply: Commands.hello,
                unapply: {
                    guard case let .hello(name, year, verbose) = $0 else { return nil }
                    return (name, year, verbose)
                }
            )
        )
    }
}

let app: CLI<Commands> = [
    Commands.iso.hello 
        <¢> command...

As you can see both approaches require quite a lot of boilerplate (you can generate it using Sourcery or SwiftSyntax). If it's not crucial for your app to represent commands as standalone types you should define them as functions and use Command.

See the playground for more examples of commands.

Extending number of arguments

You can use up to 10 arguments in your commands. If you wish to extend this number you can do that by adding Command.make function overload with required number of parameters. You may also want to add an overload of parenthesize free function to make implementation simpler. Check Command.swift to see how to implement these functions.

If you are not using Command type but use Matchable protocol you will need to add a new overload of iso free function and <¢> operator. You also still need to implement a new parenthesize free function. Check Matchable.swift to see how to implement these functions.

If you are not using Matchable protocol then you only need to implement a new overload of parenthesize free function. See Parenthesize.swift in CommonParsers module to see how to implement it.

Installation

import PackageDescription

let package = Package(
    dependencies: [
        .package(url: "https://github.com/ilyapuchka/cli-mate.git", .branch("master")),
    ]
)

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published