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 projects such as: Docker, Kubernetes, OpenShift, Hugo and more!.
Guaka is both a swift library and a command line application that help generate Guaka projects. Inspired by the amazing Cobra package from the Golang’s ecosystem.
Automatically generates help message for your commands, sub-commands and flags
Handles user input errors with useful help messages
Customizable help and error messages
Type safe flags: specify the type of the flag when defining it and Guaka will make sure the user inputs the correct flag type
Custom flag support; you can define your own flag types
Command and Flag deprecation; guaka will the user know that they are using deprecated command/flags
Command and Flag aliasing; you can alias a command or a flag to different names
Define code that runs before the command and after the command is executed
Aptly documented: lots of documentation in code (we tried)
Levenshtein distance for subcommand names
Planned Features:
Generate Markdown documentation
Man pages and bash/zsh/tcsh completions
Generate a command line application from a configuration (Yaml, Taml, Json)file
Carthage and CocoaPods support (maybe?)
Introduction
With Guaka you can build modern command line applications that are composed of Commands and Flags.
Each command represents an action with flags that represent switches or modifiers on this command. Also, each command can have a group of sub-commands under it.
With Guaka you can build command line applications with interfaces like the following:
> git checkout "NAME Of Branch"
git command CLI has a checkout subcommand that accepts a string as its argument.
> docker ps --all
docker command CLI has ps subcommand that accepts the --all flag.
Guaka also automatically generate the command line help for your command tree. This help is accessible by passing -h or --help to any of your commands:
> docker --help
> git checkout -h
The help displays the commands, subcommands, and flag information.
Command
The Command it the main object of a Guaka CLI project. It represents a verb with a block that will get executed.
In the docker ps --all example. We have a docker command that has a ps command as its sub-command.
└── docker
├── ps
└── ...
The Command class has a lot of customization objects. At its minimum, a command must have the following:
let command = Command(usage: "command") { flags, args in
// the flags passed to the command
// args the positional arguments passed to the command
}
A Flag represent an option or switch that a Command accepts. Guaka supports both short and long flag formats (inline with POSIX flags).
In docker ps --all. --all is a flag that ps accepts.
Flags have lots of costumization objects. The simplest way to create a flag and add it to ps would look like the following:
let flag = Flag(longName: "all", value: false, description: "Show all the stuff")
let command = Command(usage: "ps", flags: [flag]) { flags, args in
flags.getBool(name: "all")
// args the positional arguments passed to the command
}
Above we defined a Flag with all as longName and a default value of false. To read this flag in the command we use flags.getBool(...) which returns the flag value.
To understand guaka generator, let’s say we want to create the following command tree:
git checkout
git remote
git remote show
git remote –some-flag
guaka create
To create a new Guaka project you can run guaka create. This command creates a new swift project and the swift project files required to have a minimal Guaka project.
guaka create behaves differently based on the parameter that is passed it:
If nothing is passed, the project is created in the current working folder.
If a name is passed, a new folder with that name will be created. This folder will contain the Guaka project.
If an absolute or relative path is passed. Guaka will resolve the path and create the project there.
To create the git command we described above, we do the following:
> guaka create git
The generated Guaka swift project structure will look like:
Lets proceed at adding a flag. Go to main.swift and change it to the following:
import Guaka
let version = Flag(longName: "version", value: false, description: "Prints the version")
let command = Command(usage: "hello", flags: [version]) { flags, args in
if let hasVersion = flags.getBool(name: "version"),
hasVersion == true {
print("Version is 1.0.0")
return
}
print("You passed \(args) to your Guaka app!")
}
command.execute()
The above adds a flag called version. Notices how we are getting the flag using flags.getBool.
Now lets test it by building and running the command:
> swift build
> ./.build/debug/{projectName} --version
Version is 1.0.0
Guaka automatically generates help for your commands. We can get the help by running:
> ./.build/debug/{projectName} --help
Usage:
hello [flags]
hello [command]
Available Commands:
sub-command
Flags:
--version Prints the version
Use "hello [command] --help" for more information about a command.
Notice how the command the sub-command and flag info are displayed.
Writing a command line application is more than just parsing the command line arguments and flags.
Swift ecosystem is still very young and lacks of a cross-platform standard library. We did not wanted to make Guaka depend on libFoundation, so we rolled up our sleeves and built a few small cross-platform (as in whenever there is a usable C standard library) libraries. so you don’t have to and can be productive instantaneously. Also , they are usable on their own. You are welcome to use them too! <3:
FileUtils: Help you work with files, directories and paths.
As a minimum, a command needs a usage string and a Run block. The usage string describes how this command can be used.
If the usage is a single string command-name; the command will have that name
If the usage is a string with spaces command-name args..; the command name is the first segment of the string.
let c = Command(usage: "command-name") { _, args in
}
The Run block gets called with two parameters. The Flags class which contains the flags passed to the command and the args which is an array of arguments passed to the command.
The Command constructor takes lots of parameters. However most of them have sensible defaults. Feel free to fill as much or as little of the parameters as you want:
Commands are organised in a tree structure. Each command can have zero, one or many sub-commands associated with it.
We can add a sub-command by calling command.add(subCommand: theSubCommand). If we wanted to add printCommand as a sub-command to rootCommand, we would do the following:
let rootCommand = //Create the root command
let printCommand = //Create the print command
rootCommand.add(subCommand: printCommand)
Alternatively, you can pass the rootCommand as the parent when creating the printCommand:
let rootCommand = //Create the root command
let printCommand = Command(usage: "print",
parent: rootCommand) { _, _ in
}
Our command line application will now respond to both:
> mainCommand
> mainCommand print
You can build your command trees in this fashion and create modern, complex, elegant command line applications.
Short and Long messages
The Command defines the shortMessage and the longMessage. These are two strings that get displayed when showing the Command help.
Command(usage: "print",
shortMessage: "prints a string",
longMessage: "This is the long mesage for the print command") { _, _ in
}
The shortMessage is shown when the command is a sub-command.
> mainCommand -h
Usage:
mainCommand [flags]
mainCommand [command]
Available Commands:
print prints a string
Use "mainCommand [command] --help" for more information about a command.
Program ended with exit code: 0
The longMessage is shown when getting help of the current command
> mainCommand print -h
This is the long message for the print command
Usage:
mainCommand print
Use "mainCommand print [command] --help" for more information about a command.
Program ended with exit code: 0
Command flags
You can add a Flag to a command in two ways.
You can pass the flags in the constructor:
let f = Flag(longName: "some-flag", value: "value", description: "flag information")
let otherCommand = Command(usage: "print",
shortMessage: "prints a string",
longMessage: "This is the long mesage for the print command",
flags: [f]) { _, _ in
}
Alternatively, you can call command.add(flag: yourFlag).
Now the flag will be associated with the command. We can see it if we display the help of the command.
> mainCommand print -h
This is the long message for the print command
Usage:
mainCommand print [flags]
Flags:
--some-flag string flag information (default value)
Use "mainCommand print [command] --help" for more information about a command.
Command example section
You can attach a textual example on how to use the command. You do that by setting the example variable in the Command (or by filling the example parameter in the constructor):
printCommand.example = "Use it like this `mainCommand print \"the string to print\""
Then we can see it in the command help:
> mainCommand print -h
This is the long message for the print command
Usage:
mainCommand print
Examples:
Use it like this `mainCommand print "the string to print"
Use "mainCommand print [command] --help" for more information about a command.
Command aliases and deprecation
You can mark a command as deprecated by setting the deprecationStatus on the command.
printCommand.deprecationStatus = .deprecated("Dont use it")
When the user call this command, a deprecation message will be displayed.
Aliases help giving command alternative names. We can have both print and echo represent the same command:
printCommand.aliases = ["echo"]
Different kind of Run Hooks
The command can have different run Hooks. If they are set, they will be executed in this order.
inheritablePreRun
preRun
run
postRun
inheritablePostRun
When a command is about to execute. It will first search for its parent list. If any of its parents have an inheritablePreRun then Guaka will first execute that block.
Next the current command preRun is executed. Followed by the run and the postRun.
After that, as with the inheritablePreRun, Guaka will search for any parent that has an inheritablePostRun and execute that too.
All of inheritablePreRun, preRun, postRun and inheritablePostRun blocks return a boolean. If they return false then the command execution will end.
This allows you to create smart command trees where the parent of the command can decide if any of it sub-commands must continue executing.
For example. The parent command can define a version flag. If this flag is set, the parent will handle the call and return false from its inheritablePreRun. Doing that help us to not repeat the version handling in each sub-command.
The example bellow shows this use case:
// Create root command
let rootCommand = Command(usage: "main") { _, _ in
print("main called")
}
// Create sub command
let subCommand = Command(usage: "sub", parent: rootCommand) { _, _ in
print("sub command called")
}
// Add version flag to the root
// We made the version flag inheritable
// print will also have this flag as part of its flags
let version = Flag(longName: "version", value: false,
description: "Prints the version", inheritable: true)
rootCommand.add(flag: version)
rootCommand.inheritablePreRun = { flags, args in
if
let version = flags.getBool(name: "version"),
version == true {
print("Version is 0.0.1")
return false
}
return true
}
rootCommand.execute()
Now we can get the version by calling:
> main --version
> main sub --version
Exiting early from a command
In some sitiuation you might want to exit early from a command you can use command.fail(statusCode: errorCode, errorMessage: "Error message")
To create a Flag with default value, we call do the following:
let f = Flag(longName: "version", value: false, description: "prints the version")
We created a flag that has a longName of version. Has a default value of false and has a description. This creates a POSIX compliant flag. To set this flag:
Flag is a generic class, in the previous example, since we set false as its value, that creates a booleanFlag. If you try to pass a non-bool argument in the terminal, Guaka will display an error message.
The flag constructor, as with the command, defines lots of parameters. Most of them have sensible defaults, so feel free to pass as much, or little, as you need.
For example, we could set the flag short name by doing this:
Flag(shortName: "v", longName: "version", value: false, description: "prints the version")
Now we can either use -v or --version when calling the command.
Creating a flag with flag type
We can create a flag that has no default value. This type of flag can be marked as optional or required.
To create an optional flag
Flag(longName: "age", type: Int.self, description: "the color")
Here we defined a flag that has an int value. If we execute the command with a non-integer value, Guaka will inform us of an error.
A required flag can be created by passing true to the required argument in the Flag constructor:
Flag(longName: "age", type: Int.self, description: "the color", required: true)
Now if we call the command without setting the --age=VALUE. Guaka will display an error.
Reading the flag values
When the Commandrun block is called, a Flags argument will be sent to the block. This Flags argument contains the values for each flag the command defined.
This example illustrate flag reading:
// Create the flag
var uppercase = Flag(shortName: "u", longName: "upper",
value: false, description: "print in bold")
// Create the command
let printCommand = Command(usage: "print", parent: rootCommand) { flags, args in
// Read the flag
let isUppercase = flags.getBool(name: "upper") ?? false
if isUppercase {
print(args.joined().uppercased())
} else {
print(args.joined())
}
}
// Add the flag
printCommand.add(flag: uppercase)
Let’s execute this command:
> print "Hello World"
Hello World
> print -u "Hello World"
HELLO WORLD
Flags class defines methods to read all the different type of flags:
Flags that are set to a parent Command can be also inherited to the sub-commands by passing true to the inheritable argument in the flag constructor.
To create an inheritable flag:
var version = Flag(longName: "version", value: false,
description: "print in bold", inheritable: true)
rootCommand.add(flag: version)
This makes --version a flag that can be set in the rootCommand and any of its sub-commands.
Flag deprecation
As with a Command, a Flag can be set to be deprecated by setting it’s deprecationStatus:
var version = Flag(longName: "version", value: false,
description: "print in bold", inheritable: true)
version.deprecationStatus = .deprecated("Dont use this flag")
Guaka will warn each time this flag is set.
Flag with custom types
Out of the box, you can create flags with integer, boolean and string values and types. If you however, want to define custom types for your flags, you can do it by implementing the FlagValue protocol.
Let’s define a flag that has a User type:
// Create the enum
enum Language: FlagValue {
case english, arabic, french, italian
// Try to convert a string to a Language
static func fromString(flagValue value: String) throws -> Language {
switch value {
case "english":
return .english
case "arabic":
return .arabic
case "french":
return .french
case "italian":
return .italian
default:
// Wrong parameter passed. Throw an error
throw FlagValueError.conversionError("Wrong language passed")
}
}
static var typeDescription: String {
return "the language to use"
}
}
// Create the flag
var lang = Flag(longName: "lang", type: Language.self, description: "print in bold")
// Create the command
let printCommand = Command(usage: "print", parent: rootCommand) { flags, args in
// Read the flag
let lang = flags.get(name: "lang", type: Language.self)
// Do something with it
}
// Add the flag
printCommand.add(flag: lang)
// Execute the command
printCommand.execute()
Notice that incase the argument is not correct we throw a FlagValueError.conversionError. This error will be printed to the console.
> print --lang undefined "Hello"
Error: wrong flag value passed for flag: 'lang' Wrong language passed
Usage:
main print [flags]
Flags:
--lang the language to use print in bold
Use "main print [command] --help" for more information about a command.
wrong flag value passed for flag: 'lang' Wrong language passed
exit status 255
Guaka allows you to customize the format of the generated help. You can do that by implementing the HelpGenerator and passing your class to GuakaConfig.helpGenerator.
The HelpGenerator protocol defines all the sections of the help message that you can subclass. HelpGenerator provides protocol extensions with defaults for all the section. That allows you to cherry-pick which sections of the help you want to alter.
Each of the variable and section in the HelpGenerator corresponds to a section in the printed help message. To get the documentation of each section, refer to the in-code documentation of HelpGenerator.
Say we only want to change the usageSection of the help, we would do the following:
struct CustomHelp: HelpGenerator {
let commandHelp: CommandHelp
init(commandHelp: CommandHelp) {
self.commandHelp = commandHelp
}
var usageSection: String? {
return "This is the usage section of \(commandHelp.name) command"
}
}
GuakaConfig.helpGenerator = CustomHelp.self
Any HelpGenerator subclass will have a commandHelp variable which is an instance of CommandHelp structure. This structure contains all the info available for a command.
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 projects such as: Docker, Kubernetes, OpenShift, Hugo and more!.
Guaka is both a swift library and a command line application that help generate Guaka projects. Inspired by the amazing Cobra package from the Golang’s ecosystem.
Is it any good?
Yes
Why?
In this readme
Features
git remote show
Planned Features:
Introduction
With Guaka you can build modern command line applications that are composed of Commands and Flags.
Each command represents an action with flags that represent switches or modifiers on this command. Also, each command can have a group of sub-commands under it.
With Guaka you can build command line applications with interfaces like the following:
git
command CLI has acheckout
subcommand that accepts a string as its argument.docker
command CLI hasps
subcommand that accepts the--all
flag.Guaka also automatically generate the command line help for your command tree. This help is accessible by passing
-h
or--help
to any of your commands:The help displays the commands, subcommands, and flag information.
Command
The
Command
it the main object of a Guaka CLI project. It represents a verb with a block that will get executed.In the
docker ps --all
example. We have adocker
command that has aps
command as its sub-command.The
Command
class has a lot of customization objects. At its minimum, a command must have the following:Check the Command documentation
Flag
A
Flag
represent an option or switch that aCommand
accepts. Guaka supports both short and long flag formats (inline with POSIX flags).In
docker ps --all
.--all
is a flag thatps
accepts.Flags have lots of costumization objects. The simplest way to create a flag and add it to ps would look like the following:
Above we defined a
Flag
withall
as longName and a default value offalse
.To read this flag in the command we use
flags.getBool(...)
which returns the flag value.Check the Flag documentation
Getting started
You can create you Guaka command line application using the
guaka
generator app or by manually creating a swift project.Using Guaka generator
The easiest way to use guaka is by using
guaka
generator command line app. This CLI app helps you generate a Guaka project.First lets install
guaka
using brew:As an alternative, you can install
guaka
using the installation script (This works on macOS and Linux):(Note: For other installation options check Guaka Generator readme.)
Check that
guaka
is installed:To understand
guaka
generator, let’s say we want to create the following command tree:guaka create
To create a new Guaka project you can run
guaka create
. This command creates a new swift project and the swift project files required to have a minimal Guaka project.guaka create
behaves differently based on the parameter that is passed it:To create the
git
command we described above, we do the following:The generated Guaka swift project structure will look like:
Let’s run this newly created project.
The generated built binary will be located under
./.build/debug/git
.Which will print out:
guaka add
After running
guaka create
we have a skeleton Guaka project. This project will only have a root command.You can add new sub-commands to your project you can use
guaka add ...
.Lets add the checkout and remote command. Both these commands are sub-commands of the root.
Next, lets add a sub-command for
remote
:The generated Guaka swift project structure will look like:
Adding a flag
To add a flag we need to alter the command swift file. To add a flag to our sample
Command
(git remote –some-flag). We editSources/remote.swift
.Locate the
command.add(flags: [])
function call and edit it to look like this:Now save the file and build it with
swift build
. Run the built binary./.build/debug/git -h
and check the created command structure.Check add flag documentation
Manually implementing Guaka
Alternatively, you can create a Guaka command line app by implementing
Guaka
in a swift project.Adding Guaka to the project dependencies
We start by creating a swift executable project:
Add
Guaka
library to yourPackage.swift
fileRun
swift package fetch
to fetch the dependencies.Implementing the first command
Next, lets add our first command. Go to
main.swift
and type in the following:Run
swift build
to build your project. Congratulations! You have created your first Guaka app.To run it execute:
You should get:
Check the Command documentation
Adding a flag to the command
Lets proceed at adding a flag. Go to
main.swift
and change it to the following:The above adds a flag called
version
. Notices how we are getting the flag usingflags.getBool
.Now lets test it by building and running the command:
Check add flag documentation
Adding a subcommand
To add a subcommand we alter
main.swift
. Add the following before callingcommand.execute()
Now build and run the command:
Check [add sub command](Check add flag documentation)
Displaying the command help message
Guaka automatically generates help for your commands. We can get the help by running:
Notice how the command the sub-command and flag info are displayed.
Read more about the help message
Cross-Platform utility libraries aka batteries
Writing a command line application is more than just parsing the command line arguments and flags.
Swift ecosystem is still very young and lacks of a cross-platform standard library. We did not wanted to make Guaka depend on libFoundation, so we rolled up our sleeves and built a few small cross-platform (as in whenever there is a usable C standard library) libraries. so you don’t have to and can be productive instantaneously. Also , they are usable on their own. You are welcome to use them too! <3:
Documentation
Command documentation
Command
represents the main class in Guaka. It encapsulates a command or subcommand that Guaka defines.For the full Command documentation
Usage and Run block
As a minimum, a command needs a usage string and a
Run
block. The usage string describes how this command can be used.command-name
; the command will have that namecommand-name args..
; the command name is the first segment of the string.The
Run
block gets called with two parameters. TheFlags
class which contains the flags passed to the command and theargs
which is an array of arguments passed to the command.The
Command
constructor takes lots of parameters. However most of them have sensible defaults. Feel free to fill as much or as little of the parameters as you want:At a minimum, you need to pass the
usage
and therun
block. Refer to the code documentation for info about the parameters.Check the Flags documentation
Adding Sub-commands to the command
Commands are organised in a tree structure. Each command can have zero, one or many sub-commands associated with it.
We can add a sub-command by calling
command.add(subCommand: theSubCommand)
. If we wanted to addprintCommand
as a sub-command torootCommand
, we would do the following:Alternatively, you can pass the
rootCommand
as theparent
when creating theprintCommand
:Our command line application will now respond to both:
You can build your command trees in this fashion and create modern, complex, elegant command line applications.
Short and Long messages
The
Command
defines theshortMessage
and thelongMessage
. These are two strings that get displayed when showing theCommand
help.The
shortMessage
is shown when the command is a sub-command.The
longMessage
is shown when getting help of the current commandCommand flags
You can add a
Flag
to a command in two ways.You can pass the flags in the constructor:
Alternatively, you can call
command.add(flag: yourFlag)
.Now the flag will be associated with the command. We can see it if we display the help of the command.
Command example section
You can attach a textual example on how to use the command. You do that by setting the
example
variable in theCommand
(or by filling theexample
parameter in the constructor):Then we can see it in the command help:
Command aliases and deprecation
You can mark a command as deprecated by setting the
deprecationStatus
on the command.When the user call this command, a deprecation message will be displayed.
Aliases help giving command alternative names. We can have both
print
andecho
represent the same command:Different kind of Run Hooks
The command can have different run Hooks. If they are set, they will be executed in this order.
inheritablePreRun
preRun
run
postRun
inheritablePostRun
When a command is about to execute. It will first search for its parent list. If any of its parents have an
inheritablePreRun
then Guaka will first execute that block.Next the current command
preRun
is executed. Followed by therun
and thepostRun
.After that, as with the
inheritablePreRun
, Guaka will search for any parent that has aninheritablePostRun
and execute that too.All of
inheritablePreRun
,preRun
,postRun
andinheritablePostRun
blocks return a boolean. If they returnfalse
then the command execution will end.This allows you to create smart command trees where the parent of the command can decide if any of it sub-commands must continue executing.
For example. The parent command can define a version flag. If this flag is set, the parent will handle the call and return false from its
inheritablePreRun
. Doing that help us to not repeat the version handling in each sub-command.The example bellow shows this use case:
Now we can get the version by calling:
Exiting early from a command
In some sitiuation you might want to exit early from a command you can use
command.fail(statusCode: errorCode, errorMessage: "Error message")
Flag documentation
A
Flag
represent an option or switch that aCommand
accepts. Guaka defines 4 types of flags; integer, boolean, string and custom types.Check the full Flag documentation
Creating a flag with default value
To create a
Flag
with default value, we call do the following:We created a flag that has a
longName
ofversion
. Has a default value offalse
and has a description. This creates a POSIX compliant flag. To set this flag:Flag
is a generic class, in the previous example, since we setfalse
as its value, that creates aboolean
Flag
. If you try to pass a non-bool argument in the terminal, Guaka will display an error message.The flag constructor, as with the command, defines lots of parameters. Most of them have sensible defaults, so feel free to pass as much, or little, as you need.
For example, we could set the flag short name by doing this:
Now we can either use
-v
or--version
when calling the command.Creating a flag with flag type
We can create a flag that has no default value. This type of flag can be marked as optional or required.
To create an optional flag
Here we defined a flag that has an int value. If we execute the command with a non-integer value, Guaka will inform us of an error.
A required flag can be created by passing true to the
required
argument in theFlag
constructor:Now if we call the command without setting the
--age=VALUE
. Guaka will display an error.Reading the flag values
When the
Command
run
block is called, aFlags
argument will be sent to the block. ThisFlags
argument contains the values for each flag the command defined.This example illustrate flag reading:
Let’s execute this command:
Flags
class defines methods to read all the different type of flags:func getBool(name: String) -> Bool?
func getInt(name: String) -> Int?
func getString(name: String) -> String?
func get<T: FlagValue>(name: String, type: T.Type) -> T?
Check the full Flags documentation
Inheritable flags
Flags that are set to a parent
Command
can be also inherited to the sub-commands by passingtrue
to theinheritable
argument in the flag constructor.To create an inheritable flag:
This makes
--version
a flag that can be set in therootCommand
and any of its sub-commands.Flag deprecation
As with a
Command
, aFlag
can be set to be deprecated by setting it’sdeprecationStatus
:Guaka will warn each time this flag is set.
Flag with custom types
Out of the box, you can create flags with integer, boolean and string values and types. If you however, want to define custom types for your flags, you can do it by implementing the
FlagValue
protocol.Let’s define a flag that has a
User
type:Notice that incase the argument is not correct we throw a
FlagValueError.conversionError
. This error will be printed to the console.Check the full FlagValue documentation and the FlagValueError documentation.
Help customization
Guaka allows you to customize the format of the generated help. You can do that by implementing the
HelpGenerator
and passing your class toGuakaConfig.helpGenerator
.The
HelpGenerator
protocol defines all the sections of the help message that you can subclass.HelpGenerator
provides protocol extensions with defaults for all the section. That allows you to cherry-pick which sections of the help you want to alter.Each of the variable and section in the
HelpGenerator
corresponds to a section in the printed help message. To get the documentation of each section, refer to the in-code documentation ofHelpGenerator
.Say we only want to change the
usageSection
of the help, we would do the following:Any
HelpGenerator
subclass will have acommandHelp
variable which is an instance ofCommandHelp
structure. This structure contains all the info available for a command.Check the full HelpGenerator documentation
Tests
Tests can be found here.
Run them with
Future work
For a list of task planned, head to the Guaka GitHub project
Contributing
Just send a PR! We don’t bite ;)