Options
The Options
data type models command-line options. Contrary to arguments, options are named, position-independent parameters passed to a command-line program that modify its behavior. Note that the user must specify the name of the option just before its content. As an example, the Git CLI has a command named git checkout
that allows changing between different development branches. It has different options that modify the functionality of the command like option quiet
. This option allows suppressing feedback messages. It can be specified in the following manners:
git checkout --quiet
git checkout -q
Both --quiet
and -q
refer to the same option. -q
is the alias of quiet
, a shorter form of the option. Note that the alias is preceded only by -
.
In ZIO CLI, Options are represented by instances of class Options[_]
. Options[A]
is a description of the process of constructing an instance of A
from a valid input of the CLI. It is not yet a specified option for the CLI. In other words, an instance of Options[A]
defines a collection of valid commands and a way to construct a value A
from them.
Construction of basic Options​
ZIO CLI offers a variety of methods to create basic Options
that take a string from the user as input and transform it into a value of the corresponding type. The benefit of using these methods is that it is automatically checked if they describe a value of the given type and, in that case, are transformed into it. In case of an invalid input, a ValidationError
will be thrown.
The following methods construct an Options
with a name
that requires an input of the corresponding type from the user. Observe that for Options
it is mandatory to specify the name
.
Boolean Options​
Produces an Options[Boolean]
. It accepts as input true
or false
, while other pieces of text are not valid.
import zio.cli._
val name = "name"
Options.boolean(name)
Enumeration Options​
If we desire to allow the user to choose between different options, we can use the enumeration
method. It must specify a name and tuples (String, A)
to create an instance of Options[A]
. A valid input from the user is a key of a tuple and it will produce the corresponding value. We can use this method if there are different ways to execute a command. As an example, the command git commit
allows choosing between options --all
, --interactive
and --patch
that control how the user specifies the changes to be committed. One way to implement this would use enumeration
:
sealed trait Mode
case class All() extends Mode
case class Interactive() extends Mode
case class Patch() extends Mode
Options.enumeration[Mode]("mode")(("all", All()), ("interactive", Interactive()), ("patch", Patch()))
The output produced by the HelpDoc is
OPTIONS
--mode all | interactive | patch
One of the following cases: all, interactive, patch.
Path Options​
They are used to produce an Options[Path]
.
- Produces
Options[java.nio.file.Path]
.
Options.file(name) // Used for a file
- Produces
Options[java.nio.file.Path]
.
Options.directory(name) // Used for a directory
By default, the CliApp
accepts paths pointing to files or directories that might or not exist. If you need to work with one of them exclusively you can use the type Exists
:
// Path can point to both existing or non-existing files or directories
Options.file(name, exists = Exists.Either)
Options.directory(name, exists = Exists.Either)
// Path must point to existing files or directories
Options.file(name, exists = Exists.Yes)
Options.directory(name, exists = Exists.Yes)
// Path must point to non-existing files or directories
Options.file(name, exists = Exists.Yes)
Options.directory(name, exists = Exists.Yes)
Text Options​
Produces an Options[String]
.
Options.text(name)
Numeric Options​
Numeric Options
produce BigInt
and BigDecimal
, so they can represent numbers of arbitrary size and precision. Note that the CLI checks that the input really represents a number.
- Produces
Options[BigDecimal]
.
Options.decimal(name)
- Produces
Options[BigInt]
.
Options.integer(name)
Date/Time Options​
The following methods produce Options
whose type parameter is a Date/Time type of the java.time
library.
- Produces
Options[java.time.Duration]
. The input must be a time-based amount of time in the ISO-8601 format, such as 'P1DT2H3M'.
Options.duration(name)
- Produces
Options[java.time.Instant]
. The input must be an instant in time in UTC format, such as 2007-12-03T10:15:30.00Z.
Options.instant(name)
- Produces
Options[java.time.LocalDate]
. The input must be a date in ISO_LOCAL_DATE format, such as 2007-12-03.
Options.localDate(name)
- Produces
Options[java.time.LocalDateTime]
. The input must be a date-time without a time-zone in the ISO-8601 format, such as 2007-12-03T10:15:30.
Options.localDateTime(name)
- Produces
Options[java.time.LocalTime]
. The input must be a time without a time-zone in the ISO-8601 format, such as 10:15:30.
Options.localTime(name)
- Produces
Options[java.time.MonthDay]
. The input must be a month-day in the ISO-8601 format such as 12-03.
Options.monthDay(name)
- Produces
Options[java.time.OffsetDateTime]
. The input must be a date-time with an offset from UTC/Greenwich in the ISO-8601 format, such as 2007-12-03T10:15:30+01:00.
Options.offsetDateTime(name)
- Produces
Options[java.time.OffsetTime]
. The input must be a time with an offset from UTC/Greenwich in the ISO-8601 format, such as 10:15:30+01:00.
Options.offsetTime(name)
- Produces
Options[java.time.Period]
. The input must be a date-based amount of time in the ISO-8601 format, such as 'P1Y2M3D'.
Options.period(name)
- Produces
Options[java.time.Year]
. The input must be a year in the ISO-8601 format, such as 2007.
Options.year(name)
- Produces
Options[java.time.YearMonth]
. The input must be a year-month in the ISO-8601 format, such as 2007-12.
Options.yearMonth(name)
- Produces
Options[java.time.ZonedDateTime]
. The input must be a date-time with a time-zone in the ISO-8601 format, such as 2007-12-03T10:15:30+01:00 Europe/Paris.
Options.zonedDateTime(name)
- Produces
Options[java.time.ZoneId]
. The input must be a time-zone ID, such as Europe/Paris.
Options.zoneId(name)
- Produces
Options[java.time.ZoneOffset]
. The input must be a time-zone offset from Greenwich/UTC, such as +02:00.
Options.zoneOffset(name)
Combining and transforming options​
We can use the following methods to create more complex Options:
trait Options[A] {
def ++[A1 >: A, B](that: Options[B]): Options[(A1, B)]
def |[A1 >: A](that: Options[A1]): Options[A1]
def orElse[A1 >: A](that: Options[A1]): Options[A1] // same as |
def orElseEither[B](that: Options[B]): Options[Either[A, B]]
def map[B](f: A => B): Options[B]
def optional: Options[Option[A]]
// Creates options with different aliases
def alias(name: String, names: String*): Options[A]
// Creates options that have a default value
def withDefault[A1 >: A](value: A1): Options[A1]
}
Adding Options​
Operator ++
can be used to zip two options. It can be used to chain two options in a tuple. For example, git checkout
command has options --quiet
and --force
. The options for this command can be created in the following manner:
val checkoutOptions = Options.boolean("quiet") ++ Options.boolean("force")
The output of the CLI help will be:
COMMANDS
checkout [--quiet] [--force]
Choosing between Options​
If we have more than one option and we need only one, we can create a new Options
that allows to choose between two options using methods |
, orElse
and orElseEither
. The difference between these three methods lies in the type parameter of the new Options
. The user will be able to enter only the Options
that he wants, triggering the desired functionality.
- Method
orElse
|
is just an alias for orElse
. In the Git CLI, it is possible to choose between different options. This can be realized using orElse
.
import zio.cli._
import java.nio.file.Path
val all = Options.boolean("all")
val interactive = Options.boolean("all")
val patch = Options.boolean("all")
val orElseOption: Options[Boolean] = all | interactive | patch
- Method
orElseEither
This method wraps the types in an Either
class. This will be preferred if the types are very different.
val orElseEitherOption: Options[Either[Either[Boolean, Boolean], Boolean]] = all orElseEither interactive orElseEither patch
This is another way to implement the [--all | --interactive | --patch]
option of command git commit
.
Transforming Options​
- Method
map
It allows to transform the type parameter of Options[A]
. It takes a function f: A => B
as parameter that is applied when processing the input of a user in a CLI app. This makes easier to implement the business logic of a CLI app. This allows to implement [--all | --interactive | --patch]
option of command git commit
using orElse
. This is not possible without method map
, because the result of using orElse
is an option that returns true if some of the options are employed.
import zio.cli._
sealed trait Mode
case class All() extends Mode
case class Interactive() extends Mode
case class Patch() extends Mode
val all = Options.boolean("all").map(_ => All())
val interactive = Options.boolean("all").map(_ => Interactive())
val patch = Options.boolean("all").map(_ => Patch())
val alternative: Options[Mode] = all | interactive | patch
- Method
optional
It wraps the option in an Option
class. The resulting Options
is now optional, so the user can choose to specify it or not. If it is specified, it will be wrapped in Some()
. Without input from the user, it will produce None
. For example, if we are designing a command-line application to manage databases, we might desire to retrieve data from a particular database. The user could specify an additional backup database. If there were an error connecting with the main database, the CLI app would try to retrieve data from the backup database.
val database = Options.file("database")
val backup = Options.file("backup").optional
val retrieve = Command("retrieve", database ++ backup)
The HelpDoc of optionsDatabase
will be
COMMANDS
retrieve --database file [--backup file]
The []
denotes that the parameter is optional.
- Method
withDefault
It creates an option that will have a default parameter in case that the user does not input the option. It can be used, for example, to specify a default directory in which a CLI application may perform some task if the user does not specify another one.
val directory = Options.directory("directory").withDefault("C:\\Users\\YourUser\\Documents")
The output produced by the HelpDoc is
OPTIONS
--directory directory
A directory.
This setting is optional. Default: 'C:\Users\YourUser\Documents'.
Making an alias​
Using method alias
it is possible to give a shorter form of the name of the Options
. This is used to make easier the input of options for the user. We will make an alias for option --directory
.
val directoryWithAlias = directory.alias("d")
The output produced by the HelpDoc in this case is
OPTIONS
(-d, --directory directory)
A directory.
This setting is optional. Default: 'C:\Users\YourUser\Documents'.
Adding help​
Method ??
allows to add information about an options. The string is added after the current HelpDoc
of the Options
. We are going to recreate the --quiet
option of git checkout
to observe the effect of using ??
.
val quiet = Options.boolean("quiet")
/* HelpDoc of quiet:
*
* --quiet
* A true or false value.
*
* This setting is optional. Default: 'false'.
*/
Now we add a description of the option:
val quietWithHelp = quiet ?? "Suppress feedback messages."
/* HelpDoc of quietWithHelp:
*
* --quiet
* A true or false value.
*
* Suppress feedback messages.
*
* This setting is optional. Default: 'false'.
*/
Example​
As an example, we are going to construct an instance of Options[Git]
that describes the options of the command git commit
. We are going to implement only an option --msg
that includes a commit message and alternatives between -a
, --interactive
and --patch
. First, we create the basic options. Observe how we add an alias to all
and msg
.
import zio.cli._
val all = Options.boolean("all").alias("a")
val interactive = Options.boolean("interactive")
val patch = Options.boolean("patch")
val msg = Options.text("msg").alias("m")
Then we combine options:
- using
orElse
to combineall
,interactive
andpatch
, - adding it to
msg
.
val alternative = all | interactive | patch
val options = alternative ++ msg
The output of the HelpDoc of options
is:
USAGE
$ commit [(-a, -all)]|[--interactive]|[--patch] (-m, --msg text)
OPTIONS
(-a, --all)
A true or false value.
This setting is optional. Default: 'false'.
--interactive
A true or false value.
This setting is optional. Default: 'false'.
--patch
A true or false value.
This setting is optional. Default: 'false'.
(-m, --msg text)
A user-defined piece of text.