As developers, we often have to create command-line applications, which expose APIs, workflows, or data processing functionality in a way that is accessible to scripts and non-developers. Although a simple command-line application can be created by only using Array[String], such applications lack features that users take for granted, including flexible subcommands, options, arguments, validations and documentation pages.
In this presentation, Jorge Vásquez will show you how to easily build beautiful command-line applications that your users will love. Using ZIO CLI, your command-line applications will support all types of command-line parameters, perform both Pure and Impure validation, generate beautiful short and long documentation pages, and support auto-correction plus auto-completion.
Discover how you can have fun creating beautiful command-line apps that delight your users!
4. Scalac ZIO Trainings
If your team is interested in our ZIO
trainings:
✓ Visit https://scalac.io/services/
training/
5. Scalac ZIO Trainings
If your team is interested in our ZIO
trainings:
✓ Visit https://scalac.io/services/
training/
✓ Or, if you are onsite at the
conference you can visit our
booth!
8. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
9. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
10. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
11. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
✓Others
12. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
✓Others
✓ Examples:
13. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
✓Others
✓ Examples:
✓AWS CLI
14. Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
✓Others
✓ Examples:
✓AWS CLI
✓Git
28. Options should be Order-insensitive!
# This command...
$ git commit –-message "My first commit" --author "Jorge Vasquez"
# Should be the same as...
$ git commit --author "Jorge Vasquez" –-message "My first commit"
29. Options should be Order-insensitive!
# This command...
$ git commit –-message "My first commit" --author "Jorge Vasquez"
# Should be the same as...
$ git commit --author "Jorge Vasquez" –-message "My first commit"
30. Options should be Order-insensitive!
# This command...
$ git commit –-message "My first commit" --author "Jorge Vasquez"
# Should be the same as...
$ git commit --author "Jorge Vasquez" –-message "My first commit"
31. We need to handle Flags!
$ git commit –-message "My first commit" --verbose
32. We need to handle Variations in
Options!
# This command...
$ git commit –-message "My first commit"
# Should be the same as...
$ git commit –-message="My first commit"
# And the same as...
$ git commit –m "My first commit"
33. We need to handle Variations in
Options!
# This command...
$ git commit –-message "My first commit"
# Should be the same as...
$ git commit –-message="My first commit"
# And the same as...
$ git commit –m "My first commit"
34. We need to handle Variations in
Options!
# This command...
$ git commit –-message "My first commit"
# Should be the same as...
$ git commit –-message="My first commit"
# And the same as...
$ git commit –m "My first commit"
35. We need to handle Variations in
Options!
# This command...
$ git commit –-message "My first commit"
# Should be the same as...
$ git commit –-message="My first commit"
# And the same as...
$ git commit –m "My first commit"
36. We need to handle Variations in
Options!
# This command...
$ git commit -v –m "My first commit"
# Should be the same as...
$ git commit -vm "My first commit"
37. We need to handle Variations in
Options!
# This command...
$ git commit -v –m "My first commit"
# Should be the same as...
$ git commit -vm "My first commit"
38. We need to handle Variations in
Options!
# This command...
$ git commit -v –m "My first commit"
# Should be the same as...
$ git commit -vm "My first commit"
63. Impure Validation
We need to verify a connection between an option/
argument and the Real World
✓ Does the given file actually exist?
64. Impure Validation
We need to verify a connection between an option/
argument and the Real World
✓ Does the given file actually exist?
✓ Is the given URL valid?
79. What is ZIO CLI?
✓ It's a library that allows you to rapidly build Powerful
Command-line Applications powered by ZIO
80. What is ZIO CLI?
✓ It's a library that allows you to rapidly build Powerful
Command-line Applications powered by ZIO
✓ https://github.com/zio/zio-cli
85. Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
✓ Delete a column
86. Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
✓ Delete a column
✓ Move a column to a different position
87. Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
✓ Delete a column
✓ Move a column to a different position
✓ Change the separator string
88. Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
✓ Delete a column
✓ Move a column to a different position
✓ Change the separator string
✓ Replicate the given CSV file into several
others with the given separators
89. Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
✓ Delete a column
✓ Move a column to a different position
✓ Change the separator string
✓ Replicate the given CSV file into several
others with the given separators
✓ Convert to JSON, allowing pretty
printing
91. Create the CLI application entrypoint
import zio.cli._
object CsvUtil extends ZIOCliDefault {
val cliApp = ??? // We need to implement this
}
92. Create the CLI application entrypoint
import zio.cli._
object CsvUtil extends ZIOCliDefault {
val cliApp = ??? // We need to implement this
}
93. Create the CLI application entrypoint
import zio.cli._
object CsvUtil extends ZIOCliDefault {
val cliApp = ??? // We need to implement this
}
94. Create the CLI application entrypoint
import zio.cli._
object CsvUtil extends ZIOCliDefault {
val cliApp = ??? // We need to implement this
}
95. Define a Pure Data-
Structure that models
inputs to all possible
Subcommands
96. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
97. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
98. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
99. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
100. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
101. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
102. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
103. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
sealed trait Subcommand
object Subcommand {
final case class RenameColumn(column: String, newName: String, separator: String, inputCsv: Path) extends Subcommand
final case class DeleteColumn(column: String, separator: String, inputCsv: Path) extends Subcommand
final case class MoveColumn(column: String, newIndex: BigInt, separator: String, inputCsv: Path) extends Subcommand
final case class ChangeSeparator(oldSeparator: String, newSeparator: String, inputCsv: Path) extends Subcommand
final case class Replicate(newSeparators: ::[String], separator: String, inputCsv: Path) extends Subcommand
final case class ToJson(pretty: Boolean, separator: String, inputCsv: Path, outputJson: Path) extends Subcommand
}
104. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
// Factor commonalities out (Functional Design in action!
!
)
final case class Subcommand(separator: String, inputCsv: Path, info: Subcommand.Info)
object Subcommand {
sealed trait Info
object Info {
final case class RenameColumn(column: String, newName: String) extends Info
final case class DeleteColumn(column: String) extends Info
final case class MoveColumn(column: String, newIndex: BigInt) extends Info
final case class ChangeSeparator(newSeparator: String) extends Info
final case class Replicate(newSeparators: ::[String]) extends Info
final case class ToJson(pretty: Boolean, outputJson: Path) extends Info
}
}
105. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
// Factor commonalities out (Functional Design in action!
!
)
final case class Subcommand(separator: String, inputCsv: Path, info: Subcommand.Info)
object Subcommand {
sealed trait Info
object Info {
final case class RenameColumn(column: String, newName: String) extends Info
final case class DeleteColumn(column: String) extends Info
final case class MoveColumn(column: String, newIndex: BigInt) extends Info
final case class ChangeSeparator(newSeparator: String) extends Info
final case class Replicate(newSeparators: ::[String]) extends Info
final case class ToJson(pretty: Boolean, outputJson: Path) extends Info
}
}
106. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
// Factor commonalities out (Functional Design in action!
!
)
final case class Subcommand(separator: String, inputCsv: Path, info: Subcommand.Info)
object Subcommand {
sealed trait Info
object Info {
final case class RenameColumn(column: String, newName: String) extends Info
final case class DeleteColumn(column: String) extends Info
final case class MoveColumn(column: String, newIndex: BigInt) extends Info
final case class ChangeSeparator(newSeparator: String) extends Info
final case class Replicate(newSeparators: ::[String]) extends Info
final case class ToJson(pretty: Boolean, outputJson: Path) extends Info
}
}
107. Define a Pure Data-Structure that
models inputs to all possible
Subcommands
// Factor commonalities out (Functional Design in action!
!
)
final case class Subcommand(separator: String, inputCsv: Path, info: Subcommand.Info)
object Subcommand {
sealed trait Info
object Info {
final case class RenameColumn(column: String, newName: String) extends Info
final case class DeleteColumn(column: String) extends Info
final case class MoveColumn(column: String, newIndex: BigInt) extends Info
final case class ChangeSeparator(newSeparator: String) extends Info
final case class Replicate(newSeparators: ::[String]) extends Info
final case class ToJson(pretty: Boolean, outputJson: Path) extends Info
}
}
109. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
110. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
111. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
112. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
113. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
114. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
115. Define Subcommands themselves
import zio.cli._
// We'll define the `rename-column` subcommand first
// Which needs the following options:
// - column
// - new-name
// - separator
// Let's create the Options we'll need
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string." // Optional parameters with default values!
116. Define Subcommands themselves
import zio.cli._
// The `rename-column` subcommand needs the following args:
// - input-csv
// Let's create the Args we'll need
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file." // Impure Validation in action!
117. Define Subcommands themselves
import zio.cli._
// The `rename-column` subcommand needs the following args:
// - input-csv
// Let's create the Args we'll need
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file." // Impure Validation in action!
118. Define Subcommands themselves
import zio.cli._
// The `rename-column` subcommand needs the following args:
// - input-csv
// Let's create the Args we'll need
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file." // Impure Validation in action!
119. Define Subcommands themselves
import zio.cli._
// The `rename-column` subcommand needs the following args:
// - input-csv
// Let's create the Args we'll need
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file." // Impure Validation in action!
120. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
121. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
122. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
123. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
124. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
125. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
126. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
127. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
128. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
129. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
130. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
131. Define Subcommands themselves
import zio.cli._
// Options
val columnOption: Options[String] = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newNameOption: Options[String] = Options.text(name = "new-name").alias("n") ?? "New column name."
val separatorOption: Options[String] = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[Path] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the rename-column Command
val renameColumn: Command[((String, String, String), Path)] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption, // It's very easy to compose Options!
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
132. Define Subcommands themselves
// Improve the rename-column Command
val renameColumn: Command[Subcommand] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption,
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
.map { case ((column, newName, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.RenameColumn(column, newName))
}
133. Define Subcommands themselves
// Improve the rename-column Command
val renameColumn: Command[Subcommand] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption,
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
.map { case ((column, newName, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.RenameColumn(column, newName))
}
134. Define Subcommands themselves
// Improve the rename-column Command
val renameColumn: Command[Subcommand] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption,
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
.map { case ((column, newName, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.RenameColumn(column, newName))
}
135. Define Subcommands themselves
// Improve the rename-column Command
val renameColumn: Command[Subcommand] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption,
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
.map { case ((column, newName, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.RenameColumn(column, newName))
}
136. Define Subcommands themselves
// Improve the rename-column Command
val renameColumn: Command[Subcommand] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption,
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
.map { case ((column, newName, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.RenameColumn(column, newName))
}
137. Define Subcommands themselves
// Improve the rename-column Command
val renameColumn: Command[Subcommand] =
Command(
name = "rename-column",
options = columnOption ++ newNameOption ++ separatorOption,
args = inputCsvArg
)
.withHelp("Rename a column of the given CSV file.")
.map { case ((column, newName, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.RenameColumn(column, newName))
}
138. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the delete-column Command
val deleteColumn =
Command(name = "delete-column", options = columnOption ++ separatorOption, args = inputCsvArg)
.withHelp("Delete a column of the given CSV file.")
.map { case ((column, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.DeleteColumn(column))
}
139. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the delete-column Command
val deleteColumn =
Command(name = "delete-column", options = columnOption ++ separatorOption, args = inputCsvArg)
.withHelp("Delete a column of the given CSV file.")
.map { case ((column, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.DeleteColumn(column))
}
140. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the delete-column Command
val deleteColumn =
Command(name = "delete-column", options = columnOption ++ separatorOption, args = inputCsvArg)
.withHelp("Delete a column of the given CSV file.")
.map { case ((column, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.DeleteColumn(column))
}
141. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the delete-column Command
val deleteColumn =
Command(name = "delete-column", options = columnOption ++ separatorOption, args = inputCsvArg)
.withHelp("Delete a column of the given CSV file.")
.map { case ((column, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.DeleteColumn(column))
}
142. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the delete-column Command
val deleteColumn =
Command(name = "delete-column", options = columnOption ++ separatorOption, args = inputCsvArg)
.withHelp("Delete a column of the given CSV file.")
.map { case ((column, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.DeleteColumn(column))
}
143. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the delete-column Command
val deleteColumn =
Command(name = "delete-column", options = columnOption ++ separatorOption, args = inputCsvArg)
.withHelp("Delete a column of the given CSV file.")
.map { case ((column, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.DeleteColumn(column))
}
144. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newIndexOption = Options.integer("new-index").alias("i") ?? "New column index." // Pure Validation in action!
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the move-column Command
val moveColumn =
Command(name = "move-column", options = columnOption ++ newIndexOption ++ separatorOption, args = inputCsvArg)
.withHelp("Move a column of the given CSV file.")
.map { case ((column, newIndex, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.MoveColumn(column, newIndex))
}
145. Define Subcommands themselves
import zio.cli._
// Options
val columnOption = Options.text(name = "column").alias("c") ?? "Name of the input column."
val newIndexOption = Options.integer("new-index").alias("i") ?? "New column index." // Pure Validation in action!
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the move-column Command
val moveColumn =
Command(name = "move-column", options = columnOption ++ newIndexOption ++ separatorOption, args = inputCsvArg)
.withHelp("Move a column of the given CSV file.")
.map { case ((column, newIndex, separator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.MoveColumn(column, newIndex))
}
146. Define Subcommands themselves
import zio.cli._
// Options
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
val newSeparatorOption = Options.text("new-separator") ?? "New separator string." // You don't always need to define an alias!
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the change-separator Command
val changeSeparator =
Command(name = "change-separator", options = separatorOption ++ newSeparatorOption, args = inputCsvArg)
.withHelp("Change the separator string of the given CSV file.")
.map { case ((separator, newSeparator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.ChangeSeparator(newSeparator))
}
147. Define Subcommands themselves
import zio.cli._
// Options
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
val newSeparatorOption = Options.text("new-separator") ?? "New separator string." // You don't always need to define an alias!
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
// Create the change-separator Command
val changeSeparator =
Command(name = "change-separator", options = separatorOption ++ newSeparatorOption, args = inputCsvArg)
.withHelp("Change the separator string of the given CSV file.")
.map { case ((separator, newSeparator), inputCsv) =>
Subcommand(separator, inputCsv, Subcommand.Info.ChangeSeparator(newSeparator))
}
148. Define Subcommands themselves
import zio.cli._
// Options
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[String] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
val newSeparatorsArg: Args[::[String]] = Args.text("new-separator").repeat1 ?? "The new separators for the CSV replicas." // Varargs in action!
// Create the replicate Command
val replicate =
Command(
name = "replicate",
options = separatorOption,
args = inputCsvArg ++ newSeparatorsArg // It's very easy to compose Args!
)
.withHelp("Replicate the given CSV file with the given separators")
.map { case (separator, (inputCsv, newSeparators)) =>
Subcommand(separator, inputCsv, Subcommand.Info.Replicate(newSeparators))
}
149. Define Subcommands themselves
import zio.cli._
// Options
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[String] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
val newSeparatorsArg: Args[::[String]] = Args.text("new-separator").repeat1 ?? "The new separators for the CSV replicas." // Varargs in action!
// Create the replicate Command
val replicate =
Command(
name = "replicate",
options = separatorOption,
args = inputCsvArg ++ newSeparatorsArg // It's very easy to compose Args!
)
.withHelp("Replicate the given CSV file with the given separators")
.map { case (separator, (inputCsv, newSeparators)) =>
Subcommand(separator, inputCsv, Subcommand.Info.Replicate(newSeparators))
}
150. Define Subcommands themselves
import zio.cli._
// Options
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg: Args[String] = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
val newSeparatorsArg: Args[::[String]] = Args.text("new-separator").repeat1 ?? "The new separators for the CSV replicas." // Varargs in action!
// Create the replicate Command
val replicate =
Command(
name = "replicate",
options = separatorOption,
args = inputCsvArg ++ newSeparatorsArg // It's very easy to compose Args!
)
.withHelp("Replicate the given CSV file with the given separators")
.map { case (separator, (inputCsv, newSeparators)) =>
Subcommand(separator, inputCsv, Subcommand.Info.Replicate(newSeparators))
}
151. Define Subcommands themselves
import zio.cli._
// Options
val prettyOption = Options.boolean(name = "pretty").alias("p") ?? "Pretty format output JSON." // Flags in action!
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
val outputJsonArg = Args.file("output-json", Exists.No) ?? "The output JSON file." // You can require that a file does not exist!
// Create the to-json Command
val toJson =
Command(name = "to-json", options = prettyOption ++ separatorOption, args = inputCsvArg ++ outputJsonArg)
.withHelp("Convert the given CSV file to JSON.")
.map { case ((pretty, separator), (inputCsv, outputJson)) =>
Subcommand(separator, inputCsv, Subcommand.Info.ToJson(pretty, outputJson))
}
152. Define Subcommands themselves
import zio.cli._
// Options
val prettyOption = Options.boolean(name = "pretty").alias("p") ?? "Pretty format output JSON." // Flags in action!
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
val outputJsonArg = Args.file("output-json", Exists.No) ?? "The output JSON file." // You can require that a file does not exist!
// Create the to-json Command
val toJson =
Command(name = "to-json", options = prettyOption ++ separatorOption, args = inputCsvArg ++ outputJsonArg)
.withHelp("Convert the given CSV file to JSON.")
.map { case ((pretty, separator), (inputCsv, outputJson)) =>
Subcommand(separator, inputCsv, Subcommand.Info.ToJson(pretty, outputJson))
}
153. Define Subcommands themselves
import zio.cli._
// Options
val prettyOption = Options.boolean(name = "pretty").alias("p") ?? "Pretty format output JSON." // Flags in action!
val separatorOption = Options.text("separator").alias("s").withDefault(",") ?? "Separator string."
// Args
val inputCsvArg = Args.file("input-csv", Exists.Yes) ?? "The input CSV file."
val outputJsonArg = Args.file("output-json", Exists.No) ?? "The output JSON file." // You can require that a file does not exist!
// Create the to-json Command
val toJson =
Command(name = "to-json", options = prettyOption ++ separatorOption, args = inputCsvArg ++ outputJsonArg)
.withHelp("Convert the given CSV file to JSON.")
.map { case ((pretty, separator), (inputCsv, outputJson)) =>
Subcommand(separator, inputCsv, Subcommand.Info.ToJson(pretty, outputJson))
}
174. Installing the application
✓ For installing your CLI application, ZIO CLI includes an
installer script that:
✓Generates a GraalVM native image for you
175. Installing the application
✓ For installing your CLI application, ZIO CLI includes an
installer script that:
✓Generates a GraalVM native image for you
✓Installs this as an executable on your machine
216. Summary
✓ CLI apps are very useful for non-devs to interact with
your applications
217. Summary
✓ CLI apps are very useful for non-devs to interact with
your applications
✓ However, writing production-grade CLI apps is hard
218. Summary
✓ CLI apps are very useful for non-devs to interact with
your applications
✓ However, writing production-grade CLI apps is hard
✓ Actually, it doesn't have to be hard, not with ZIO CLI!!!