Skip to main content
Version: 2.x

Args

The Args data type models command-line arguments. Arguments are name-less, position-based parameters passed to a command-line program that modify its behavior. For example, the Git CLI has a command named git clone that creates a copy of an existing repository. It has an argument called "repository" that represents the existing repository's path. To clone the ZIO CLI repository, it can be called this way:

git clone https://github.com/zio/zio-cli.git

where https://github.com/zio/zio-cli.git is the path of the repository.

In ZIO CLI, Arguments are represented by instances of class Args[_]. Args[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 argument for the CLI. In other words, an Args[A] defines a collection of valid arguments for a command and a way to construct a value A from them.

Construction of basic Args​

ZIO CLI offers a variety of methods to create basic Args that takes 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. Note that each type has its own validation function.

An Args instance of a basic type carries a name attribute. This can be specified when being created, but it can also be skipped. In this last case, the name will be that of the type. An example with Boolean type:

import zio.cli._

val name = "name"

Args.bool(name) // Boolean Args with name
Args.bool // Boolean Args named "boolean"

Boolean Args​

Produces an Args[Boolean]. It accepts as input true or false, while other pieces of text are not valid.

Args.bool(name)

Path Args​

They are used to produce an Args[Path].

  • Produces Args[java.nio.file.Path].
Args.file(name) // Used for a file
  • Produces Args[java.nio.file.Path].
Args.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 trait Exists:

// Path can point to both existing or non-existing files or directories
Args.file(name, exists = Exists.Either)
Args.directory(name, exists = Exists.Either)

// Path must point to existing files or directories
Args.file(name, exists = Exists.Yes)
Args.directory(name, exists = Exists.Yes)

// Path must point to non-existing files or directories
Args.file(name, exists = Exists.Yes)
Args.directory(name, exists = Exists.Yes)

Text Args​

Produces an Args[String].

Args.text(name)

Numeric Args​

Numeric Args 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 Args[BigDecimal].
Args.decimal(name)
  • Produces Args[BigInt].
Args.integer(name)

Date/Time Args​

The following methods produce Args whose type parameter is a Date/Time type of the java.time library.

  • Produces Args[java.time.Duration]. The input must be a time-based amount of time in the ISO-8601 format, such as 'P1DT2H3M'.
Args.duration(name)
  • Produces Args[java.time.Instant]. The input must be an instant in time in UTC format, such as 2007-12-03T10:15:30.00Z.
Args.instant(name)
  • Produces Args[java.time.LocalDate]. The input must be a date in ISO_LOCAL_DATE format, such as 2007-12-03.
Args.localDate(name)
  • Produces Args[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.
Args.localDateTime(name)
  • Produces Args[java.time.LocalTime]. The input must be a time without a time-zone in the ISO-8601 format, such as 10:15:30.
Args.localTime(name)
  • Produces Args[java.time.MonthDay]. The input must be a month-day in the ISO-8601 format such as 12-03.
Args.monthDay(name)
  • Produces Args[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.
Args.offsetDateTime(name)
  • Produces Args[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.
Args.offsetTime(name)
  • Produces Args[java.time.Period]. The input must be a date-based amount of time in the ISO-8601 format, such as 'P1Y2M3D'.
Args.period(name)
  • Produces Args[java.time.Year]. The input must be a year in the ISO-8601 format, such as 2007.
Args.year(name)
  • Produces Args[java.time.YearMonth]. The input must be a year-month in the ISO-8601 format, such as 2007-12.
Args.yearMonth(name)
  • Produces Args[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.
Args.zonedDateTime(name)
  • Produces Args[java.time.ZoneId]. The input must be a time-zone ID, such as Europe/Paris.
Args.zoneId(name)
  • Produces Args[java.time.ZoneOffset]. The input must be a time-zone offset from Greenwich/UTC, such as +02:00.
Args.zoneOffset(name)

Combining and transforming Args​

When constructing a command, you can specify only one argument. Thus, to create more complex Args it is necessary to use the following methods of the trait Args:

trait Args[A] {
def ++[B](that: Args[B]): Args[(A, B)] // Zip two args
def +[A1 >: A]: Args[::[A1]] // Requires a non-empty list of Args of type A1
def * : Args[List[A]] // Requires a list of Args of same type
def atLeast(min: Int): Args[List[A]] // Requires a list of Args with at least min elements
def atMost(max: Int): Args[List[A]] // Requires a list of Args with at most max elements
def between(min: Int, max: Int): Args[List[A]] // Requires a list of Args with a bound on the number of elements
def map[B](f: A => B): Args[B] // Applies a function f to the result of the Args
def ??(that: String): Args[A] // Adds a string to the HelpDoc of the Args
}

Adding Args​

Operator ++ can be used to zip two arguments. It can be use to chain two arguments in a tuple. For example, git clone command has arguments <repository> and <directory>. The argument of this command can be created in the following manner:

val cloneArgs = Args.text("repository") ++ Args.text("directory")

The output of the CLI help will be:

COMMANDS

clone <repository> <directory>

Repeating Args​

If we need an argument a repeated number of times, we can use the following operators:

  • Method *

It creates a new Args that accepts a list of arguments of the same type. There are no restrictions on length. Take into account that there is no limit, so if there are arguments or options after using this methods, the CLI app will not read them unless they are not valid!

// Accepts a list, possibly empty, of texts.
Args.text.*
  • Method +

It works as method * but the list of arguments cannot be empty. Do not mistake with method ++!

  • Method between

It creates an argument accepting a list of arguments of the type before whose length must be between min and max parameters.

Args.text.between(2,5) // Creates an arguments accepting a list of String of length between 2 and 5.
  • Method atLeast

It creates an argument accepting a list of arguments of the type before whose length must be more than min parameter.

Args.text.atLeast(2) // Creates an arguments accepting a list of String of length more than 2.
  • Method atMost

It creates an argument accepting a list of arguments of the type before whose length must be less than maxparameter

Args.text.atMost(5) // Creates an arguments accepting a list of String of length less than 5.

Transforming Args​

Method map allows to transform the type parameter of Args[A]. It takes a function f: A => B as parameter that is applied when processing a user's input in a CLI app and returns Args[B]. This makes it easier to implement the business logic of a CLI app. For example, we will construct an Args that asks for a list of 12 decimals (one for each month) and a year. Then, it will create a new Args that store the year and the mean value in a custom type.

import zio.cli._

val data: Args[BigDecimal] = Args.decimal
val args: Args[(List[BigDecimal], BigInt)] = data.between(12, 12) ++ Args.integer

case class YearAndMean(year: BigInt, mean: BigDecimal)

val mappedArgs: Args[YearAndMean] = args.map {
case (months, year) => YearAndMean(year, months.sum/12)
}

Adding help​

Method ?? allows adding information about an argument. The string is added after the current HelpDoc of the Args. We are going to create the <repository> argument of git clone to observe the effect of using ??.

val repository = Args.text("repository")

/* HelpDoc of repository:
*
* <repository>
* A user-defined piece of text.
*
*/

Now we add a description of the argument:

val repositoryWithHelp = repository ?? "Path of the repository to be cloned."

/* HelpDoc of repositoryWithHelp:
*
* <repository>
* A user-defined piece of text.
*
* Path of the repository to be cloned.
*/