SwiftCLI - A powerful framework for developing CLIs in Swift

Overview

SwiftCLI

Build Status

A powerful framework for developing CLIs, from the simplest to the most complex, in Swift.

import SwiftCLI

class GreetCommand: Command {
    let name = "greet"
    
    @Param var person: String

    func execute() throws {
        stdout <<< "Hello \(person)!"
    }
}

let greeter = CLI(name: "greeter")
greeter.commands = [GreetCommand()]
greeter.go()
~ > greeter greet world
Hello world!

With SwiftCLI, you automatically get:

  • Command routing
  • Option parsing
  • Help messages
  • Usage statements
  • Error messages when commands are used incorrectly
  • Zsh completions

Table of Contents

Installation

Ice Package Manager

> ice add jakeheis/SwiftCLI

Swift Package Manager

Add SwiftCLI as a dependency to your project:

dependencies: [
    .package(url: "https://github.com/jakeheis/SwiftCLI", from: "6.0.0")
]

Carthage

5.2.2 ">
github "jakeheis/SwiftCLI" ~> 5.2.2

CocoaPods

pod 'SwiftCLI', '~> 6.0.0'

Creating a CLI

When creating a CLI, a name is required, and a version and description are both optional.

let myCli = CLI(name: "greeter", version: "1.0.0", description: "Greeter - a friendly greeter")

You set commands through the .commands property:

myCli.commands = [myCommand, myOtherCommand]

Finally, to run the CLI, you call one of the go methods.

// Use go if you want program execution to continue afterwards
myCli.go() 

// Use goAndExit if you want your program to terminate after the CLI has finished
myCli.goAndExit()

// Use go(with:) if you want to control the arguments which the CLI runs with
myCli.go(with: ["arg1", "arg2"])

Commands

In order to create a command, you must implement the Command protocol. All that's required is to implement a name property and an execute function; the other properties of Command are optional (though a shortDescription is highly recommended). A simple hello world command could be created as such:

class GreetCommand: Command {

    let name = "greet"
    let shortDescription = "Says hello to the world"

    func execute() throws  {
        stdout <<< "Hello world!"
    }

}

Parameters

A command can specify what parameters it accepts through certain instance variables. Using reflection, SwiftCLI will identify property wrappers of type @Param and @CollectedParam. These properties should appear in the order that the command expects the user to pass the arguments. All required parameters must come first, followed by any optional parameters, followed by at most one collected parameter.

class GreetCommand: Command {
    let name = "greet"

    @Param var first: String
    @Param var second: String?
    @CollectedParam var remaining: [String]
}

In this example, if the user runs greeter greet Jack Jill up the hill, first will contain the value Jack, second will contain the value Jill, and remaining will contain the value ["up", "the", "hill"].

@Param

Individual parameters take the form of the property wrapper @Param. Properties wrapped by @Param can be required or optional. If the command is not passed enough arguments to satisfy all required parameters, the command will fail.

class GreetCommand: Command {
    let name = "greet"

    @Param var person: String
    @Param var followUp: String

    func execute() throws {
        stdout <<< "Hey there, \(person)!"
        stdout <<< followUp
    }
}
~ > greeter greet Jack

Usage: greeter greet <person> <followUp> [options]

Options:
  -h, --help      Show help information

Error: command requires exactly 2 arguments

~ > greeter greet Jack "What's up?"
Hey there, Jack!
What's up?

If the user does not pass enough arguments to satisfy all optional parameters, the value of these unsatisfied parameters will be nil.

class GreetCommand: Command {
    let name = "greet"

    @Param var person: String
    @Param var followUp: String? // Note: String? in this example, not String

    func execute() throws {
        stdout <<< "Hey there, \(person)!"
        if let followUpText = followUp {
            stdout <<< followUpText
        }
    }
}
~ > greeter greet Jack
Hey there, Jack!
~ > greeter greet Jack "What's up?"
Hello, Jack!
What's up?

@CollectedParam

Commands may have a single collected parameter after all the other parameters called a @CollectedParam. This parameter allows the user to pass any number of arguments, and these arguments will be collected into the array wrapped by the collected parameter. The property wrapped by @CollectedParam must be an array. By default, @CollectedParam does not require the user to pass any arguments. The parameter can require a certain number of values by using the @CollectedParam(minCount:) initializer.

class GreetCommand: Command {
    let name = "greet"

    @CollectedParam(minCount: 1) var people: [String]

    func execute() throws {
        for person in people {
            stdout <<< "Hey there, \(person)!"
        }        
    }
}
~ > greeter greet Jack
Hey there, Jack!
~ > greeter greet Jack Jill Water
Hey there, Jack!
Hey there, Jill!
Hey there, Water!

Value type of parameter

With all of these parameter property wrappers, any type can be used so long as it conforms to ConvertibleFromString. Most primitive types (e.g. Int) conform to ConvertibleFromString already, as do enums with raw values that are primitive types.

class GreetCommand: Command {
    let name = "greet"

    @Param var number: Int

    func execute() throws {
        stdout <<< "Hey there, number \(number)!"     
    }
}
~ > greeter greet Jack

Usage: greeter greet <number> [options]

Options:
  -h, --help      Show help information

Error: invalid value passed to 'number'; expected Int

~ > greeter greet 4
Hey there, number 4!

Parameters with enum types which conform to CaseIterable have additional specialized behavior. In an error message, the allowed values for that parameter will be spelled out.

class GreetCommand: Command {
    
    let name = "greet"
    
    enum Volume: String, ConvertibleFromString, CaseIterable {
        case loud
        case quiet
    }
    
    @Param var volume: Volume
    
    func execute() throws {
        let greeting = "Hello world!"
        
        switch volume {
        case .loud: stdout <<< greeting.uppercased()
        case .quiet: stdout <<< greeting.lowercased()
        }
        
    }
}
~ > greeter greet Jack

Usage: greeter greet <volume> [options]

Options:
  -h, --help      Show help information

Error: invalid value passed to 'volume'; expected one of: loud, quiet

~ > greet greet loud
HELLO WORLD!

To conform a custom type to ConvertibleFromString, simply implement one function:

extension MyType: ConvertibleFromString {
    init?(input: String) {
        // Construct an instance of MyType from the String, or return nil if not possible
        ...
    }
}

Options

Commands have support for two types of options: flag options and keyed options. Both types of options can be denoted by either a dash followed by a single letter (e.g. git commit -a) or two dashes followed by the option name (e.g. git commit --all). Single letter options can be cascaded into a single dash followed by all the desired options: git commit -am "message" == git commit -a -m "message".

Options are specified with property wrappers on the command class, just like parameters:

class ExampleCommand: Command {
    ...
    @Flag("-a", "--all")
    var flag: Bool

    @Key("-t", "--times")
    var key: Int?
    ...
}

Flags

Flags are simple options that act as boolean switches. For example, if you were to implement git commit, -a would be a flag option. They take the form of booleans wrapped by @Flag.

The GreetCommand could take a "loudly" flag:

class GreetCommand: Command {

    ...

    @Flag("-l", "--loudly", description: "Say the greeting loudly")
    var loudly: Bool

    func execute() throws {
        if loudly {
             ...
        } else {
            ...
        }
    }

}

A related option type is @CounterFlag, which counts the nubmer of times the user passes the same flag. @CounterFlag can only wrap properties of type Int. For example, with a flag declaration like:

class GreetCommand: Command {
    ...
    @CounterFlag("-s", "--softly", description: "Say the greeting softly")
    var softly: Int
    ...
}

the user can write greeter greet -s -s, and softly.value will be 2.

Keys

Keys are options that have an associated value. Using "git commit" as an example, "-m" would be a keyed option, as it has an associated value - the commit message. They take the form of variables wrapped by '@Key`.

The GreetCommand could take a "number of times" option:

class GreetCommand: Command {

    ...

    @Key("-n", "--number-of-times", description: "Say the greeting a certain number of times")
    var numberOfTimes: Int?

    func execute() throws {
        for i in 0..<(numberOfTimes ?? 1) {
            ...
        }
    }

}

The variable wrapped by @Key can be any type conforming to ConvertibleFromString as described above. It must be optional, or the Swift compiler will crash.

A related option type is VariadicKey, which allows the user to pass the same key multiples times with different values. For example, with a key declaration like:

class GreetCommand: Command {
    ...
    @VariadicKey("-l", "--location", description: "Say the greeting in a certain location")
    var locations: [String]
    ...
}

the user can write greeter greet -l Chicago -l NYC, and locations.value will be ["Chicago", "NYC"]. The variable wrapped by @VariadicKey must be an array of a type conforming to ConvertibleFromString.

Option groups

The relationship between multiple options can be specified through option groups. Option groups allow a command to specify that the user must pass at most one option of a group (passing more than one is an error), must pass exactly one option of a group (passing zero or more than one is an error), or must pass one or more options of a group (passing zero is an error).

To add option groups, a Command should implement the property optionGroups. Option groups refer to options through the $ syntax. For example, if the GreetCommand had a loudly flag and a whisper flag but didn't want the user to be able to pass both, an OptionGroup could be used:

class GreetCommand: Command {

    ...

    @Flag("-l", "--loudly", description: "Say the greeting loudly")
    var loudly: Bool

    @Flag("-w", "--whisper", description: "Whisper the greeting")
    var whisper: Bool
    
    var optionGroups: [OptionGroup] {
        return [.atMostOne($loudly, $whipser)] // Note: $loudly and $whisper, not loudly and whisper
    }

    func execute() throws {
        if loudly {
            ...
        } else if whisper {
            ...
        } else {
            ...
        }
    }

}

Global options

Global options can be used to specify that every command should have a certain option. This is how the -h flag is implemented for all commands. Simply add an option to CLI's .globalOptions array (and optionally extend Command to make the option easy to access in your commands):

private let verboseFlag = Flag("-v")
extension Command {
    var verbose: Bool {
        return verboseFlag.value
    }
}

myCli.globalOptions.append(verboseFlag)

By default, every command has a -h flag which prints help information. You can turn this off by setting the CLI helpFlag to nil:

myCli.helpFlag = nil

Usage of options

As seen in the above examples, @Flag and @Key both take an optional description parameter. A concise description of what the option does should be included here. This allows the HelpMessageGenerator to generate a fully informative usage statement for the command.

A command's usage statement is shown in three situations:

  • The user passed an option that the command does not support -- greeter greet -z
  • The user passed the wrong number of arguments
  • The command's help was invoked -- greeter greet -h
~ > greeter greet -h

Usage: greeter greet <person> [options]

Options:
  -l, --loudly                          Say the greeting loudly
  -n, --number-of-times <value>         Say the greeting a certain number of times
  -h, --help                            Show help information for this command

Command groups

Command groups provide a way for related commands to be nested under a certain namespace. Groups can themselves contain other groups.

class ConfigGroup: CommandGroup {
    let name = "config"
    let children = [GetCommand(), SetCommand()]
}
class GetCommand: Command {
    let name = "get"
    func execute() throws {}
}
class SetCommand: Command {
    let name = "set"
    func execute() throws {}
}

You can add a command group to your CLI's .commands array just as add a normal command:

greeter.commands = [ConfigGroup()]
> greeter config

Usage: greeter config <command> [options]

Commands:
  get
  set

> greeter config set
> greeter config get

Shell completions

Zsh completions can be automatically generated for your CLI.

let myCli = CLI(...)

let generator = ZshCompletionGenerator(cli: myCli)
generator.writeCompletions()

Completions will be automatically generated for command names and options. Parameter completion mode can be specified:

@Param(completion: .none)
var noCompletions: String

@Param(completion: .filename)
var aFile: String

@Param(completion: .values([
    ("optionA", "the first available option"),
    ("optionB", "the second available option")
]))
var aValue: String

@Param(completion: .function("_my_custom_func"))
var aFunction: String

The default parameter completion mode is .filename. If you specify a custom function with .function, that function must be supplied when creating the completion generator:

class MyCommand {
    ...
    @Param(completion: .function("_list_processes"))
    var pid: String
    ...
}

let myCLI = CLI(...)
myCLI.commands [MyCommand()]
let generator = ZshCompletionGenerator(cli: myCli, functions: [
    "_list_processes": """
        local pids
        pids=( $(ps -o pid=) )
        _describe '' pids
        """
])

Built-in commands

CLI has two built-in commands: HelpCommand and VersionCommand.

Help Command

The HelpCommand can be invoked with myapp help. The HelpCommand first prints the app description (if any was given during CLI.init). It then iterates through all available commands, printing their name and their short description.

~ > greeter help

Usage: greeter <command> [options]

Greeter - your own personal greeter

Commands:
  greet        Greets the given person
  help         Prints this help information

If you don't want this command to be automatically included, set the helpCommand property to nil:

myCLI.helpCommand = nil

Version Command

The VersionCommand can be invoked with myapp version or myapp --version. The VersionCommand prints the version of the app given during init CLI(name:version:). If no version is given, the command is not available.

~ > greeter --version
Version: 1.0

If you don't want this command to be automatically included, set the versionCommand property to nil:

myCLI.versionCommand = nil

Input

The Input class makes it easy to read input from stdin. Several methods are available:

let str = Input.readLine()
let int = Input.readInt()
let double = Input.readDouble()
let bool = Input.readBool()

All read methods have four optional parameters:

  • prompt: the message to print before accepting input (e.g. "Input: ")
  • secure: if true, the input is hidden as the user types
  • validation: a closure which defines whether the input is valid, or if the user should be reprompted
  • errorResponse: a closure which is executed when the user enters input which is not valid

For example, you could write:

let percentage = Input.readDouble(
    prompt: "Percentage:",
    validation: [.within(0...100)],
    errorResponse: { (input, reason) in
        Term.stderr <<< "'\(input)' is invalid; must be a number between 0 and 100"
    }
)

which would result in an interaction such as:

Percentage: asdf
'asdf' is invalid; must be a number between 0 and 100
Percentage: 104
'104' is invalid; must be a number between 0 and 100
Percentage: 43.6

External tasks

SwiftCLI makes it easy to execute external tasks:

// Execute a command and print output:
try Task.run("echo", "hello")
try Task.run(bash: "while true; do echo hi && sleep 1; done")

// Execute a command and capture the output:
let currentDirectory = try Task.capture("pwd").stdout
let sorted = try Task.capture(bash: "cat Package.swift | sort").stdout

You can also use the Task class for more custom behavior:

let input = PipeStream()
let output = PipeStream()
let task = Task(executable: "sort", currentDirectory: "~/Ice", stdout: output, stdin: input)
task.runAsync()

input <<< "beta"
input <<< "alpha"
input.closeWrite()

output.readAll() // will be alpha\nbeta\n

See Sources/SwiftCLI/Task.swift for full documentation on Task.

Single command CLIs

If your CLI only contains a single command, you may want to execute the command simply by calling cli, rather than cli command. In this case, you can create your CLI as such:

class Ln: Command {
    let name = "ln"
    func execute() throws { ... }
}

let ln = CLI(singleCommand: Ln())
ln.go()

In this case, if the user writes ln myFile newLocation, rather than searching for a command with the name "myFile", SwiftCLI will execute the Ln command and pass on "myFile" as the first argument to that command.

Keep in mind that when creating a single command CLI, you lose the default VersionCommand. This means that cli -v will not work automatically, and that if you want to print your CLI version you will need to manually implement a Flag("-v") on your single command.

Customization

SwiftCLI was designed with sensible defaults but also the ability to be customized at every level. CLI has three properties that can be changed from the default implementations to customized implementations.

parser

The Parser steps through arguments to find the corresponding command, update its parameter values, and recognizes options. Each CLI has a parser property which has three mutable properties: routeBehavior, parseOptionsAfterCollectedParameter, and responders.

routeBehavior has three possible values. The default, .search, steps through the arguments the user passed and tries to find a command with a matching name. If it fails, a help message is printed. git is an example of a program which operates this way. The second option, .searchWithFallback(Command), also initially tries to find a command with a name matching the arguments passed by the user, but if it fails, rather than printing a help message it falls back to a certain command. 'bundle' is an example of a program which operates this way. The last option is .automatically(Command). In this route behavior, the CLI automatically routes to the given command without considering arguments. 'ln' is an example of a program which operates this way.

parseOptionsAfterCollectedParameter controls whether or not options are recognized once a collected parameter is encountered. It defaults to false. Given the following command:

class Run: Command {
    let name = "run"

    @Param var executable: String
    @CollectedParam var arguments: [String]

    @Flag("-a") var all: Bool
    ...
}

if the user calls swift run myExec -a and parseOptionsAfterCollectedParameter is false, the value of executable will be "myExec", the value of arguments will be ["-a"], and the value of all will be false. If parseOptionsAfterCollectedParameter were true in this situation, the value of executable would be "myExec", the value of arguments would be [], and the value of all would be true.

responders allows the parser to be completely customized. Check out Parser.swift for more information on how this functions by default and how it can be customized in any way.

aliases

Aliases can be made through the the aliases property on CLI. Parser will take these aliases into account while routing to the matching command. For example, if you write:

myCLI.aliases["-c"] = "command"

And the user makes the call myapp -c, the parser will search for a command with the name "command" because of the alias, not a command with the name "-c".

By default, "--version" is an alias for "version", but you can remove this if desired:

myCLI.aliases["--version"] = nil

argumentListManipulators

ArgumentListManipulators act before the Parser begins. They take in the arguments as given by the user and can change them slightly. By default, the only argument list manipulator used is OptionSplitter which splits options like -am into -a -m.

You can implement ArgumentListManipulator on your own type and update CLI's property:

cli.argumentListManipulators.append(MyManipulator())

helpMessageGenerator

The messages formed by SwiftCLI can also be customized:

cli.helpMessageGenerator = MyHelpMessageGenerator()

Running your CLI

Simply call swift run. In order to ensure your CLI gets the arguments passed on the command line, make sure to call CLI.go(), not CLI.go(with: []).

CLIs build with SwiftCLI

Comments
  • The reason why I actually wanted to be able to customize Command Signature.

    The reason why I actually wanted to be able to customize Command Signature.

    I really like SwiftCLI but there's a couple non-trivial features missing in order to make truly complex CLIs.

    1. The ability to group parameters (and options) in groups, and subsequently specify how many members of a group we are expecting (i.e. Exactly one, At least one, At most one, all of them) or alternatively a way to specify that certain parameters or options conflict with each other.

    2. The ability to make parameters that also require a value, like keyed options do.

    For example, in my use case, before I began looking into the whole changing the Command Signature issue, I ended up using commands with signature "" and all of their functionality implemented through options and flags., since I found that was the only way I could avoid issues caused by mismatching signature parameter counts. Although what I did to aliases would probably greatly offset this issue, it's still not really an elegant solution to use extensively.

    opened by Lord-Kamina 14
  • Add support for redirecting captured output as it's being written

    Add support for redirecting captured output as it's being written

    This allows printing or redirecting output as it's being captured, analogous to the tee command. For example:

    NSUnbufferedIO=YES softwareupdate --list | tee /dev/tty | grep -Fq $needle
    

    Can be written as:

    let result = try Task.capture(
        "softwareupdate",
        arguments: ["--list"],
        outputStream: Term.stdout,
        env: [
            "NSUnbufferedIO": "YES",
        ]
    )
    
    if result.output.contains(needle) { /* ... */ }
    

    Currently to do this without a fork requires re-implementing Task.capture, CaptureStream, CaptureResult, and CaptureError.

    I've also added support for passing in forwardInterrupt and env properties as arguments, can open a separate PR for that if necessary.

    For CaptureStream, I used a block instead of another WritableStream parameter as that was more consistent with the surrounding code and similar to the readabilityHandler block on FileHandle.

    opened by msanders 8
  • Removed invalid override keyword (Swift 5.1)

    Removed invalid override keyword (Swift 5.1)

    Hi @jakeheis, I just tried to install SwiftCLI via the SPM integration in Xcode 11 beta 2 with Swift 5.1 and I couldn't build my target because of an error with invalid usage of the override keyword. I removed it and tried compiling it with both Swift 5.1 & Swift 4.2 which worked fine.

    opened by lukaskubanek 8
  • Stream observer

    Stream observer

    In regards to the new stream functionality in SwiftCLI 4.2.0, what's the best way to filter/forward a stream? For example taking what is written to stdout in swift build and then somehow filtering it and re-outputting it?

    Would that be a subclass of WriteStream that overrides write and forwards it on to another stream, or some sort of observation mechanism on Pipe, or is there a better way?

    opened by yonaskolb 8
  • Arbitrary arguments

    Arbitrary arguments

    This library looks fantastic, nice work @jakeheis!

    Does it support the ability to parse arbitrary arguments? I'll give an example:

    mint run jakeheis/ice ice add RxSwift
    

    mint is the executable run is a command in mint jakeheis/ice is an argument in run The rest are arbitrary arguments that will need to be parsed somehow for later use

    If there's some way to do that, I'd love to integrate this

    opened by yonaskolb 7
  • Fix strdup optional check

    Fix strdup optional check

    This fixes the build of SwiftCLI on Xcode 13 RC.

    strdup returns an implicitly unwrapped optional which is breaking the build. The change is quite straightforward and it'd be great if we can merge and make a release as soon as possible.

    opened by TheCoordinator 6
  • Question: CLIs without

    Question: CLIs without "command"

    Is there support for CLIs without a specific command? If so, some hints to that in the README would be helpful.

    In detail, if I want to develop a simple command line tool, which takes some flags and some files as input, e.g.

    foo -a bar.txt baz.log -f
    

    I do not have a command. Instead I have just some flags -a and -f, plus and arbitrary number of optional collected parameters. I good real world example of such a tool is the famous rm from the GNU core utils, which, unlike the macOS version of rm accepts flags at any position.

    opened by dastrobu 6
  • Linux build and test compliance changes

    Linux build and test compliance changes

    Tested with Swift 3.0 RELEASE, Swift 3.0.1 PREVIEW 3 on Ubuntu 16.04

    I believe that class RegularExpression is on Mac as well (it should be per Swift Foundation Library 3.0), so we could ditch NSRegularExpression on Mac and use pure Swift RegularExpression class. But that's up to you @jakeheis

    BTW Good job on SwiftCLI 2.0!

    opened by rbukovansky 6
  • "too many arguments (4468) -- limit is 4096" error

    I'm just rebasing my tool BartyCrouch to use SwiftCLI but after making the switch on first try on one of our larger projects, I came across this error:

    2018-12-13 17:34:49.112 bartycrouch[12963:3427202] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'too many arguments (4468) -- limit is 4096'
    *** First throw call stack:
    (
    	0   CoreFoundation                      0x00007fff35bb6e65 __exceptionPreprocess + 256
    	1   libobjc.A.dylib                     0x00007fff61c12720 objc_exception_throw + 48
    	2   CoreFoundation                      0x00007fff35bb6c97 +[NSException raise:format:] + 201
    	3   Foundation                          0x00007fff37eabaf3 -[NSConcreteTask launchWithDictionary:error:] + 774
    	4   bartycrouch                         0x000000010371373a $S8SwiftCLI4TaskC6launch030_19E0ACB72F1C972020BFBD69850F9J1FLLyyF + 506
    	5   bartycrouch                         0x0000000103713249 $S8SwiftCLI4TaskC7runSyncs5Int32VyF + 25
    	6   bartycrouch                         0x0000000103716626 $S8SwiftCLI3run_9arguments9directoryySS_SaySSGSSSgtKFTf4xnn_n + 390
    	7   bartycrouch                         0x0000000103712669 $SAbort trap: 6
    

    Looks like there's an arguments limit to tasks executed in the command line. Can this be fixed within this library (easiliy) or do I need to build a wrapper around the run(:arguments:) method and run my command multiple times until it has worked through all arguments?

    The usage code is probably this line in case you're wondering.

    opened by Jeehut 5
  • [FeatReq] Subcommands

    [FeatReq] Subcommands

    @jakeheis For my project I will need to have what we can call subcommands or application areas. Most of the time user will need, due to complexity of application, to specify "area" he wants to work within and then command for that area. Similar to git remote command. Let me better explain in CLI commands:

    Code area:
    $ cx code add readme.md
    $ cx code commit "Init commit"
    $ cx code status
    
    Issues area:
    $ cx issue add "Something is broken" <-- different add to add in Code area
    $ cx issue show 1234
    $ cx issue resolve 1234
    
    Build area:
    $ cx build run
    $ cx build restart
    
    etc.
    

    Do you think it would be possible to add it to SwiftCLI? Thank you.

    opened by rbukovansky 5
  • Show usage description when using `help` for option commands as well

    Show usage description when using `help` for option commands as well

    It would be nice if prompting for the usage description was consistent for different types of commands. In some places you can type cmd help and other times you have to type cmd subCommand -h. It isn't consistent and can confuse the user IMO.

    opened by kdawgwilk 5
  • Use `readpassphrase` instead of `getpass`

    Use `readpassphrase` instead of `getpass`

    InputReader is using getpass to read secure values. Unfortunately, getpass is limited to 128 characters of input. The remaining input is silently truncated.

    readpassphrase, on the other hand, can accept an arbitrary amount of input (up to a specified maximum). With readpassphrase you could choose a large character limit (e.g. 1024), or you could allow clients of SwiftCLI to specify a limit.

    opened by paulhimes 0
  • 6.0.0 version is not available via cocoapods

    6.0.0 version is not available via cocoapods

    Hi there! At first, just want to say the library is just amazing.

    Seems latest version is not available via cocoapods, currently using 5.2.1

    [!] CocoaPods could not find compatible versions for pod "SwiftCLI":
      In Podfile:
        SwiftCLI (~> 6.0.0)
    
    opened by gennady-oliinyk 0
  • [Question] How to passthrough stdout/stdin for External Tasks

    [Question] How to passthrough stdout/stdin for External Tasks

    I'd like to run an external task from my command that may ask the user for input.

    What's the best way of passing through requests for input (I assume from the external task's stdout) to the user, and also passing the user's input to the external tasks stdin?

    A very simple example:

    I'd like to run sudo echo hello as an external task and have the user be notified when sudo asks for their password. How could I accomplish that with the Task API?

    opened by joshuawright11 1
  • Allow changing newline padding in usage messages

    Allow changing newline padding in usage messages

    It would be nice if there were a way to disable the newline padding in usage and error messages without re-implementing the default implementations in HelpMessageGenerator. I personally think it makes sense to turn it off by default, since that's more consistent with other command line tools. E.g.:

    
    Usage: cli <command> [options]
    
    Commands:
      help              Prints help information
      version           Prints the current version of this app
    
    

    would become:

    Usage: cli <command> [options]
    
    Commands:
      help              Prints help information
      version           Prints the current version of this app
    
    opened by msanders 1
  • Consider adding a public changelog

    Consider adding a public changelog

    As a user it's difficult now to keep track of what's changed between releases. This would be easier with a public changelog file, or alternatively notes on the GitHub releases page.

    opened by msanders 0
Owner
Jake Heiser
Jake Heiser
Guaka - Smart and beautiful POSIX compliant CLI framework for Swift.

Guaka - Smart and beautiful POSIX compliant CLI framework for Swift. It helps you create modern and familiar CLI apps in the vein of widely used proje

Omar Abdelhafith 1.1k Dec 24, 2022
SwiftShell - A Swift framework for shell scripting.

Run shell commands | Parse command line arguments | Handle files and directories Swift 5.1 - 5.3 | Swift 4 | Swift 3 | Swift 2 SwiftShell A library fo

Kare Morstol 973 Jan 2, 2023
Linenoise-Swift A pure Swift implementation of the Linenoise library. A minimal, zero-config readline replacement.

Linenoise-Swift A pure Swift implementation of the Linenoise library. A minimal, zero-config readline replacement. Supports Mac OS and Linux Line edit

Andy Best 114 Dec 14, 2022
Swift tool to generate Module Interfaces for Swift projects.

ModuleInterface Swift tool to generate Module Interfaces for Swift projects. What is a Module Interface A Module Interface is what we commonly get usi

Jorge Revuelta 75 Dec 21, 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
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
Progress.swift ⌛ Add beautiful progress bars to your loops.

Progress.swift ⌛ Just wrap the SequenceType in your loop with the Progress SequenceType and you'll automatically get beautiful progress bars. Updating

Justus Kandzi 304 Dec 1, 2022
Straightforward, type-safe argument parsing for Swift

Swift Argument Parser Usage Begin by declaring a type that defines the information that you need to collect from the command line. Decorate each store

Apple 2.9k Jan 7, 2023
SwiftyTextTable - A lightweight Swift library for generating text tables

SwiftyTextTable A lightweight Swift library for generating text tables. Swift Language Support SwiftyTextTable is now Swift 4.0 compatible! The last r

Scott Hoyt 283 Dec 23, 2022
Shell scripting in Swift

Shwift Shell-scripting in Swift DISCLAIMER: Shwift depends on Swift's incoming concurrency features. As such, it requires a recent Swift toolchain, an

George Lyon 32 Sep 15, 2022
Swift utilities for running commands.

Swift Commands Swift utilities for running commands. The Commands module allows you to take a system command as a string and return the standard outpu

Phil 41 Jan 2, 2023
A CLI too powered by Swift to provision environments using an up.toml manifest file

tuist-up tuist up was originally a Tuist built-in command to provision environments by reading the requirements in a manifest file Setup.swift. Althou

Tuist 5 Mar 31, 2022
Terminal string styling for Swift.

Terminal string styling for Swift. Integration Swift Package Manager (SPM) You can use The Swift Package Manager to install ColorizeSwift by adding it

Michał Tynior 281 Dec 22, 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
✏️Expressive styling on terminal string. (chalk for swift)

Chalk Expressive styling on terminal string. Highlights Expressive API 256/TrueColor support Nest styles Auto downgrading to terminal supported color

Luo Xiu 59 Jun 10, 2022
A starting point to create CLI utilities with swift

cli tuist template A starting point to create CLI utilities with swift Installation Just create a Tuist folder and a Templates folder inside it. Creat

humdrum 6 May 3, 2022
A Tuist Template to quickly create CLI apps in Swift

macOS CLI Template Motivation I'm writing more and more Swift CLI apps these days. And as I solve more problems with this litte tools, I find that I'm

Diego Freniche 21 Dec 28, 2022
A cli program written in swift (with async/await) that removes the unnecessary parts of xcframeworks.

xctrim A cli program written in swift (with async/await) that removes the unnecessary parts of xcframeworks. Usecase Say you downloaded firebase sdk a

Mustafa Yusuf 32 Jul 18, 2022