SlideShare une entreprise Scribd logo
1  sur  223
Télécharger pour lire hors ligne
Behold! The Happy Path To
Captivate Your Users With
Stunning CLI Apps!
Functional Scala
December 2022
Jorge Vásquez
Scala Developer/ZIO Trainer
@Scalac
Scalac ZIO Trainings
If your team is interested in our ZIO
trainings:
Scalac ZIO Trainings
If your team is interested in our ZIO
trainings:
✓ Visit https://scalac.io/services/
training/
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!
Why CLI Apps?
Why CLI Apps?
Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
✓Others
Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
✓Others
✓ Examples:
Why CLI Apps?
✓ CLI Apps make your work scriptable/testable/usable by non-
devs
✓Around API
✓Around data processing task
✓Others
✓ Examples:
✓AWS CLI
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
Problem: Implementing
Production-Grade CLI Apps
Implementing Production-Grade CLI
Apps
There are several things we need to handle!
Implementing Production-Grade CLI
Apps
There are several things we need to handle!
✓ Command-line parameters
Implementing Production-Grade CLI
Apps
There are several things we need to handle!
✓ Command-line parameters
✓ Inputs validation
Implementing Production-Grade CLI
Apps
There are several things we need to handle!
✓ Command-line parameters
✓ Inputs validation
✓ Documentation
Handling Parameters
Handling Parameters
There are several types of Command-line Parameters
Handling Parameters
There are several types of Command-line Parameters
✓ Options: Named parameters
Handling Parameters
There are several types of Command-line Parameters
✓ Options: Named parameters
✓ Arguments: Unnamed parameters
Handling Parameters
There are several types of Command-line Parameters
✓ Options: Named parameters
✓ Arguments: Unnamed parameters
✓ Subcommands
Handling Options
$ git commit –-message "My first commit"
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"
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"
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"
We need to handle Flags!
$ git commit –-message "My first commit" --verbose
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"
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"
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"
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"
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"
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"
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"
Handling Arguments
$ cat foo.txt
Arguments are Order-sensitive!
# This command...
$ cp foo1.txt foo2.txt
# Is NOT the same as...
$ cp foo2.txt foo1.txt
Arguments are Order-sensitive!
# This command...
$ cp foo1.txt foo2.txt
# Is NOT the same as...
$ cp foo2.txt foo1.txt
Arguments are Order-sensitive!
# This command...
$ cp foo1.txt foo2.txt
# Is NOT the same as...
$ cp foo2.txt foo1.txt
We need to handle Varargs!
$ cat foo1.txt foo2.txt foo3.txt
Handling Subcommands
$ git clone https://github.com/zio/zio-cli.git
$ git add .
$ git commit –-message "My first commit"
Handling Subcommands
$ git clone https://github.com/zio/zio-cli.git
$ git add .
$ git commit –-message "My first commit"
Handling Subcommands
$ git clone https://github.com/zio/zio-cli.git
$ git add .
$ git commit –-message "My first commit"
Handling Subcommands
$ git clone https://github.com/zio/zio-cli.git
$ git add .
$ git commit –-message "My first commit"
Inputs Validation
Inputs Validation
Inputs Validation
✓ We need to validate inputs the user provides as options/
arguments
Inputs Validation
✓ We need to validate inputs the user provides as options/
arguments
✓ Two types of validation
Inputs Validation
✓ We need to validate inputs the user provides as options/
arguments
✓ Two types of validation
✓Pure
Inputs Validation
✓ We need to validate inputs the user provides as options/
arguments
✓ Two types of validation
✓Pure
✓Impure
Pure Validation
We need to validate whether an option/argument is a:
Pure Validation
We need to validate whether an option/argument is a:
✓ String
Pure Validation
We need to validate whether an option/argument is a:
✓ String
✓ Integer
Pure Validation
We need to validate whether an option/argument is a:
✓ String
✓ Integer
✓ Datetime
Pure Validation
We need to validate whether an option/argument is a:
✓ String
✓ Integer
✓ Datetime
✓ Other data type
Pure Validation
$ tail -n xyz foo.txt
# tail: illegal offset -- xyz
Pure Validation
$ tail -n xyz foo.txt
# tail: illegal offset -- xyz
Pure Validation
$ tail -n xyz foo.txt
# tail: illegal offset -- xyz
Impure Validation
We need to verify a connection between an option/
argument and the Real World
Impure Validation
We need to verify a connection between an option/
argument and the Real World
✓ Does the given file actually exist?
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?
Impure Validation
$ tail -n 500 foo.txt
# tail: foo.txt: No such file or directory
Impure Validation
$ tail -n 500 foo.txt
# tail: foo.txt: No such file or directory
Impure Validation
$ tail -n 500 foo.txt
# tail: foo.txt: No such file or directory
Documentation
Documentation
Documentation
✓ We need basically two types of documentation:
Documentation
✓ We need basically two types of documentation:
✓Synopsis
Documentation
✓ We need basically two types of documentation:
✓Synopsis
✓Rich documentation
Documentation
✓ We need basically two types of documentation:
✓Synopsis
✓Rich documentation
✓ We have to consider docs for subcommands as well!
Wouldn't it be dreamy if we had a
ZIO Library that handles all of this
stuff for us, so we can focus on
our Business Logic?
Presenting ZIO CLI!
What is ZIO CLI?
What is ZIO CLI?
✓ It's a library that allows you to rapidly build Powerful
Command-line Applications powered by ZIO
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
ZIO CLI Walkthrough
Sample app: CSV
Utils
Sample app: CSV
Utils
✓ The following subcommands should be
supported:
Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
✓ Delete a column
Sample app: CSV
Utils
✓ The following subcommands should be
supported:
✓ Rename a column
✓ Delete a column
✓ Move a column to a different position
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
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
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
Create the CLI
application entrypoint
Create the CLI application entrypoint
import zio.cli._
object CsvUtil extends ZIOCliDefault {
val cliApp = ??? // We need to implement this
}
Create the CLI application entrypoint
import zio.cli._
object CsvUtil extends ZIOCliDefault {
val cliApp = ??? // We need to implement this
}
Create the CLI application entrypoint
import zio.cli._
object CsvUtil extends ZIOCliDefault {
val cliApp = ??? // We need to implement this
}
Create the CLI application entrypoint
import zio.cli._
object CsvUtil extends ZIOCliDefault {
val cliApp = ??? // We need to implement this
}
Define a Pure Data-
Structure that models
inputs to all possible
Subcommands
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
}
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
}
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
}
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
}
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
}
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
}
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
}
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
}
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
}
}
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
}
}
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
}
}
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
}
}
Define Subcommands
themselves
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!
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!
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!
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!
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!
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!
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!
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!
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!
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!
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!
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.")
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.")
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.")
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.")
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.")
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.")
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.")
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.")
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.")
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.")
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.")
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.")
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
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))
}
Compose
Subcommands under
a Root Command
Compose Subcommands under a Root
Command
val csvUtil: Command[Subcommand] =
Command(name = "csv-util", options = Options.none, args = Args.none)
.subcommands(
renameColumn,
deleteColumn,
moveColumn,
changeSeparator,
replicate,
toJson
)
Compose Subcommands under a Root
Command
val csvUtil: Command[Subcommand] =
Command(name = "csv-util", options = Options.none, args = Args.none)
.subcommands(
renameColumn,
deleteColumn,
moveColumn,
changeSeparator,
replicate,
toJson
)
Compose Subcommands under a Root
Command
val csvUtil: Command[Subcommand] =
Command(name = "csv-util", options = Options.none, args = Args.none)
.subcommands(
renameColumn,
deleteColumn,
moveColumn,
changeSeparator,
replicate,
toJson
)
Create a CLIApp that
assigns
Subcommands to
Handlers
Create a CLIApp that assigns
Subcommands to Handlers
import zio.cli._
object CsvUtil extends ZIOCliDefault {
// Options, Args, Subcommands and Root Command
...
val cliApp =
CliApp.make(
name = "CSV Util",
version = "0.0.1",
summary = text("CLI to some CSV utilities"),
footer = HelpDoc.p("©Copyright 2022"),
command = csvUtil // Root command
) { case Subcommand(separator, inputCsv, info) =>
info match {
case Info.RenameColumn(column, newName) =>
Console.printLine {
s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv"
}
...
}
}
}
Create a CLIApp that assigns
Subcommands to Handlers
import zio.cli._
object CsvUtil extends ZIOCliDefault {
// Options, Args, Subcommands and Root Command
...
val cliApp =
CliApp.make(
name = "CSV Util",
version = "0.0.1",
summary = text("CLI to some CSV utilities"),
footer = HelpDoc.p("©Copyright 2022"),
command = csvUtil // Root command
) { case Subcommand(separator, inputCsv, info) =>
info match {
case Info.RenameColumn(column, newName) =>
Console.printLine {
s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv"
}
...
}
}
}
Create a CLIApp that assigns
Subcommands to Handlers
import zio.cli._
object CsvUtil extends ZIOCliDefault {
// Options, Args, Subcommands and Root Command
...
val cliApp =
CliApp.make(
name = "CSV Util",
version = "0.0.1",
summary = text("CLI to some CSV utilities"),
footer = HelpDoc.p("©Copyright 2022"),
command = csvUtil // Root command
) { case Subcommand(separator, inputCsv, info) =>
info match {
case Info.RenameColumn(column, newName) =>
Console.printLine {
s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv"
}
...
}
}
}
Create a CLIApp that assigns
Subcommands to Handlers
import zio.cli._
object CsvUtil extends ZIOCliDefault {
// Options, Args, Subcommands and Root Command
...
val cliApp =
CliApp.make(
name = "CSV Util",
version = "0.0.1",
summary = text("CLI to some CSV utilities"),
footer = HelpDoc.p("©Copyright 2022"),
command = csvUtil // Root command
) { case Subcommand(separator, inputCsv, info) =>
info match {
case Info.RenameColumn(column, newName) =>
Console.printLine {
s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv"
}
...
}
}
}
Create a CLIApp that assigns
Subcommands to Handlers
import zio.cli._
object CsvUtil extends ZIOCliDefault {
// Options, Args, Subcommands and Root Command
...
val cliApp =
CliApp.make(
name = "CSV Util",
version = "0.0.1",
summary = text("CLI to some CSV utilities"),
footer = HelpDoc.p("©Copyright 2022"),
command = csvUtil // Root command
) { case Subcommand(separator, inputCsv, info) =>
info match {
case Info.RenameColumn(column, newName) =>
Console.printLine {
s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv"
}
...
}
}
}
Create a CLIApp that assigns
Subcommands to Handlers
import zio.cli._
object CsvUtil extends ZIOCliDefault {
// Options, Args, Subcommands and Root Command
...
val cliApp =
CliApp.make(
name = "CSV Util",
version = "0.0.1",
summary = text("CLI to some CSV utilities"),
footer = HelpDoc.p("©Copyright 2022"),
command = csvUtil // Root command
) { case Subcommand(separator, inputCsv, info) =>
info match {
case Info.RenameColumn(column, newName) =>
Console.printLine {
s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv"
}
...
}
}
}
Create a CLIApp that assigns
Subcommands to Handlers
import zio.cli._
object CsvUtil extends ZIOCliDefault {
// Options, Args, Subcommands and Root Command
...
val cliApp =
CliApp.make(
name = "CSV Util",
version = "0.0.1",
summary = text("CLI to some CSV utilities"),
footer = HelpDoc.p("©Copyright 2022"),
command = csvUtil // Root command
) { case Subcommand(separator, inputCsv, info) =>
info match {
case Info.RenameColumn(column, newName) =>
Console.printLine {
s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv"
}
...
}
}
}
Create a CLIApp that assigns
Subcommands to Handlers
import zio.cli._
object CsvUtil extends ZIOCliDefault {
// Options, Args, Subcommands and Root Command
...
val cliApp =
CliApp.make(
name = "CSV Util",
version = "0.0.1",
summary = text("CLI to some CSV utilities"),
footer = HelpDoc.p("©Copyright 2022"),
command = csvUtil // Root command
) { case Subcommand(separator, inputCsv, info) =>
info match {
case Info.RenameColumn(column, newName) =>
Console.printLine {
s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv"
}
...
}
}
}
Create a CLIApp that assigns
Subcommands to Handlers
import zio.cli._
object CsvUtil extends ZIOCliDefault {
// Options, Args, Subcommands and Root Command
...
val cliApp =
CliApp.make(
name = "CSV Util",
version = "0.0.1",
summary = text("CLI to some CSV utilities"),
footer = HelpDoc.p("©Copyright 2022"),
command = csvUtil // Root command
) { case Subcommand(separator, inputCsv, info) =>
info match {
case Info.RenameColumn(column, newName) =>
Console.printLine {
s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv"
}
...
}
}
}
Create a CLIApp that assigns
Subcommands to Handlers
import zio.cli._
object CsvUtil extends ZIOCliDefault {
// Options, Args, Subcommands and Root Command
...
val cliApp =
CliApp.make(
name = "CSV Util",
version = "0.0.1",
summary = text("CLI to some CSV utilities"),
footer = HelpDoc.p("©Copyright 2022"),
command = csvUtil // Root command
) { case Subcommand(separator, inputCsv, info) =>
info match {
case Info.RenameColumn(column, newName) =>
Console.printLine {
s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv"
}
...
}
}
}
Create a CLIApp that assigns
Subcommands to Handlers
import zio.cli._
object CsvUtil extends ZIOCliDefault {
// Options, Args, Subcommands and Root Command
...
val cliApp =
CliApp.make(
name = "CSV Util",
version = "0.0.1",
summary = text("CLI to some CSV utilities"),
footer = HelpDoc.p("©Copyright 2022"),
command = csvUtil // Root command
) { case Subcommand(separator, inputCsv, info) =>
info match {
case Info.RenameColumn(column, newName) =>
Console.printLine {
s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv"
}
...
}
}
}
Create a CLIApp that assigns
Subcommands to Handlers
import zio.cli._
object CsvUtil extends ZIOCliDefault {
// Options, Args, Subcommands and Root Command
...
val cliApp =
CliApp.make(
name = "CSV Util",
version = "0.0.1",
summary = text("CLI to some CSV utilities"),
footer = HelpDoc.p("©Copyright 2022"),
command = csvUtil // Root command
) { case Subcommand(separator, inputCsv, info) =>
info match {
case Info.RenameColumn(column, newName) =>
Console.printLine {
s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv"
}
...
}
}
}
Installing the
application
Installing the application
Installing the application
✓ For installing your CLI application, ZIO CLI includes an
installer script that:
Installing the application
✓ For installing your CLI application, ZIO CLI includes an
installer script that:
✓Generates a GraalVM native image for you
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
Touring the CSV-Util
application!
Executing the root command with
no options/arguments
Executing the root command with
the --help flag
Executing a subcommand with
the --help flag
Executing a subcommand with
missing args
Executing a subcommand
successfully
Executing a subcommand with
different variations in options
Grouping several flags together
Pure validation of options/args
Impure validation of options/args
Varargs
Automatic misspellings detection
and correction!
Shell-integrated autocompletion!
Shell-integrated autocompletion!
For enabling autocompletion for your CLI app, a shell-
completion script is generated like this:
# Generates a shell-completion.sh script
$ csv-util --shell-completion-script `which csv-util` --shell-type bash
# Enables completions
$ source completion-script.sh
Shell-integrated autocompletion!
For enabling autocompletion for your CLI app, a shell-
completion script is generated like this:
# Generates a shell-completion.sh script
$ csv-util --shell-completion-script `which csv-util` --shell-type bash
# Enables completions
$ source completion-script.sh
Shell-integrated autocompletion!
For enabling autocompletion for your CLI app, a shell-
completion script is generated like this:
# Generates a shell-completion.sh script
$ csv-util --shell-completion-script `which csv-util` --shell-type bash
# Enables completions
$ source completion-script.sh
Shell-integrated autocompletion!
For enabling autocompletion for your CLI app, a shell-
completion script is generated like this:
# Generates a shell-completion.sh script
$ csv-util --shell-completion-script `which csv-util` --shell-type bash
# Enables completions
$ source completion-script.sh
Shell-integrated autocompletion!
For enabling autocompletion for your CLI app, a shell-
completion script is generated like this:
# Generates a shell-completion.sh script
$ csv-util --shell-completion-script `which csv-util` --shell-type bash
# Enables completions
$ source completion-script.sh
Wizard mode for the root
command!
Wizard mode for a subcommand!
Comparing ZIO CLI to
other libraries
Comparing ZIO CLI to other libraries
Annotations for
customization
Autocorrection Autocompletion Wizard mode
decline ❌ ❌ ❌ ❌
mainargs ✅ ❌ ❌ ❌
case-app ✅ ❌ ❌ ❌
scallop ❌ ❌ ❌ ❌
scopt ❌ ❌ ❌ ❌
optparse ❌ ❌ ❌ ❌
zio-cli ❌ (Coming soon!) ✅ ✅ ✅
Summary
Summary
Summary
✓ CLI apps are very useful for non-devs to interact with
your applications
Summary
✓ CLI apps are very useful for non-devs to interact with
your applications
✓ However, writing production-grade CLI apps is hard
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!!!
Special thanks
Special thanks
✓ Ziverge for organizing this
conference
Special thanks
✓ Ziverge for organizing this
conference
✓ Scalac for sponsoring
Special thanks
✓ Ziverge for organizing this
conference
✓ Scalac for sponsoring
✓ John De Goes for guidance and
support
Contact me
@jorvasquez2301
jorge-vasquez-2301
jorge.vasquez@scalac.io

Contenu connexe

Similaire à Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!

Perl.Hacks.On.Vim Perlchina
Perl.Hacks.On.Vim PerlchinaPerl.Hacks.On.Vim Perlchina
Perl.Hacks.On.Vim Perlchina
guestcf9240
 
Test driven infrastructure development (2 - puppetconf 2013 edition)
Test driven infrastructure development (2 - puppetconf 2013 edition)Test driven infrastructure development (2 - puppetconf 2013 edition)
Test driven infrastructure development (2 - puppetconf 2013 edition)
Tomas Doran
 
Git Distributed Version Control System
Git   Distributed Version Control SystemGit   Distributed Version Control System
Git Distributed Version Control System
Victor Wong
 
Perl.Hacks.On.Vim Perlchina
Perl.Hacks.On.Vim PerlchinaPerl.Hacks.On.Vim Perlchina
Perl.Hacks.On.Vim Perlchina
Lin Yo-An
 
C sharp fundamentals Part I
C sharp fundamentals Part IC sharp fundamentals Part I
C sharp fundamentals Part I
DevMix
 
Your own (little) gem: building an online business with Ruby
Your own (little) gem: building an online business with RubyYour own (little) gem: building an online business with Ruby
Your own (little) gem: building an online business with Ruby
Lindsay Holmwood
 
Effective codereview | Dave Liddament | CODEiD
Effective codereview | Dave Liddament | CODEiDEffective codereview | Dave Liddament | CODEiD
Effective codereview | Dave Liddament | CODEiD
CODEiD PHP Community
 
Perl.Hacks.On.Vim
Perl.Hacks.On.VimPerl.Hacks.On.Vim
Perl.Hacks.On.Vim
Lin Yo-An
 

Similaire à Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps! (20)

Perl.Hacks.On.Vim Perlchina
Perl.Hacks.On.Vim PerlchinaPerl.Hacks.On.Vim Perlchina
Perl.Hacks.On.Vim Perlchina
 
Test driven infrastructure development (2 - puppetconf 2013 edition)
Test driven infrastructure development (2 - puppetconf 2013 edition)Test driven infrastructure development (2 - puppetconf 2013 edition)
Test driven infrastructure development (2 - puppetconf 2013 edition)
 
Rails OO views
Rails OO viewsRails OO views
Rails OO views
 
groovy & grails - lecture 6
groovy & grails - lecture 6groovy & grails - lecture 6
groovy & grails - lecture 6
 
Vim Hacks
Vim HacksVim Hacks
Vim Hacks
 
Vim Hacks
Vim HacksVim Hacks
Vim Hacks
 
TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기
TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기
TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기
 
Git Distributed Version Control System
Git   Distributed Version Control SystemGit   Distributed Version Control System
Git Distributed Version Control System
 
Boîte à outils d'investigation des soucis de mémoire
Boîte à outils d'investigation des soucis de mémoireBoîte à outils d'investigation des soucis de mémoire
Boîte à outils d'investigation des soucis de mémoire
 
Perl.Hacks.On.Vim Perlchina
Perl.Hacks.On.Vim PerlchinaPerl.Hacks.On.Vim Perlchina
Perl.Hacks.On.Vim Perlchina
 
C sharp fundamentals Part I
C sharp fundamentals Part IC sharp fundamentals Part I
C sharp fundamentals Part I
 
Lecture4 php by okello erick
Lecture4 php by okello erickLecture4 php by okello erick
Lecture4 php by okello erick
 
Kafka on Kubernetes: Keeping It Simple (Nikki Thean, Etsy) Kafka Summit SF 2019
Kafka on Kubernetes: Keeping It Simple (Nikki Thean, Etsy) Kafka Summit SF 2019Kafka on Kubernetes: Keeping It Simple (Nikki Thean, Etsy) Kafka Summit SF 2019
Kafka on Kubernetes: Keeping It Simple (Nikki Thean, Etsy) Kafka Summit SF 2019
 
Your own (little) gem: building an online business with Ruby
Your own (little) gem: building an online business with RubyYour own (little) gem: building an online business with Ruby
Your own (little) gem: building an online business with Ruby
 
How to eat Cucmber
How to eat CucmberHow to eat Cucmber
How to eat Cucmber
 
Effective codereview | Dave Liddament | CODEiD
Effective codereview | Dave Liddament | CODEiDEffective codereview | Dave Liddament | CODEiD
Effective codereview | Dave Liddament | CODEiD
 
Perl.Hacks.On.Vim
Perl.Hacks.On.VimPerl.Hacks.On.Vim
Perl.Hacks.On.Vim
 
What we Learned Implementing Puppet at Backstop
What we Learned Implementing Puppet at BackstopWhat we Learned Implementing Puppet at Backstop
What we Learned Implementing Puppet at Backstop
 
Sprockets
SprocketsSprockets
Sprockets
 
Cleancode
CleancodeCleancode
Cleancode
 

Plus de Jorge Vásquez

Plus de Jorge Vásquez (10)

Programación Funcional 101 con Scala y ZIO 2.0
Programación Funcional 101 con Scala y ZIO 2.0Programación Funcional 101 con Scala y ZIO 2.0
Programación Funcional 101 con Scala y ZIO 2.0
 
A Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIOA Prelude of Purity: Scaling Back ZIO
A Prelude of Purity: Scaling Back ZIO
 
Be Smart, Constrain Your Types to Free Your Brain!
Be Smart, Constrain Your Types to Free Your Brain!Be Smart, Constrain Your Types to Free Your Brain!
Be Smart, Constrain Your Types to Free Your Brain!
 
Consiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIO
Consiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIOConsiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIO
Consiguiendo superpoderes para construir aplicaciones modernas en la JVM con ZIO
 
Functional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorldFunctional Programming 101 with Scala and ZIO @FunctionalWorld
Functional Programming 101 with Scala and ZIO @FunctionalWorld
 
ZIO Prelude - ZIO World 2021
ZIO Prelude - ZIO World 2021ZIO Prelude - ZIO World 2021
ZIO Prelude - ZIO World 2021
 
Exploring type level programming in Scala
Exploring type level programming in ScalaExploring type level programming in Scala
Exploring type level programming in Scala
 
The Terror-Free Guide to Introducing Functional Scala at Work
The Terror-Free Guide to Introducing Functional Scala at WorkThe Terror-Free Guide to Introducing Functional Scala at Work
The Terror-Free Guide to Introducing Functional Scala at Work
 
Exploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in ScalaExploring ZIO Prelude: The game changer for typeclasses in Scala
Exploring ZIO Prelude: The game changer for typeclasses in Scala
 
Introduction to programming with ZIO functional effects
Introduction to programming with ZIO functional effectsIntroduction to programming with ZIO functional effects
Introduction to programming with ZIO functional effects
 

Dernier

CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceCALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
anilsa9823
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
Health
 

Dernier (20)

Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptx
 
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceCALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 

Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps!

  • 1. Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps! Functional Scala December 2022
  • 3. Scalac ZIO Trainings If your team is interested in our ZIO trainings:
  • 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
  • 16.
  • 17.
  • 18. Implementing Production-Grade CLI Apps There are several things we need to handle!
  • 19. Implementing Production-Grade CLI Apps There are several things we need to handle! ✓ Command-line parameters
  • 20. Implementing Production-Grade CLI Apps There are several things we need to handle! ✓ Command-line parameters ✓ Inputs validation
  • 21. Implementing Production-Grade CLI Apps There are several things we need to handle! ✓ Command-line parameters ✓ Inputs validation ✓ Documentation
  • 23. Handling Parameters There are several types of Command-line Parameters
  • 24. Handling Parameters There are several types of Command-line Parameters ✓ Options: Named parameters
  • 25. Handling Parameters There are several types of Command-line Parameters ✓ Options: Named parameters ✓ Arguments: Unnamed parameters
  • 26. Handling Parameters There are several types of Command-line Parameters ✓ Options: Named parameters ✓ Arguments: Unnamed parameters ✓ Subcommands
  • 27. Handling Options $ git commit –-message "My first commit"
  • 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"
  • 40. Arguments are Order-sensitive! # This command... $ cp foo1.txt foo2.txt # Is NOT the same as... $ cp foo2.txt foo1.txt
  • 41. Arguments are Order-sensitive! # This command... $ cp foo1.txt foo2.txt # Is NOT the same as... $ cp foo2.txt foo1.txt
  • 42. Arguments are Order-sensitive! # This command... $ cp foo1.txt foo2.txt # Is NOT the same as... $ cp foo2.txt foo1.txt
  • 43. We need to handle Varargs! $ cat foo1.txt foo2.txt foo3.txt
  • 44. Handling Subcommands $ git clone https://github.com/zio/zio-cli.git $ git add . $ git commit –-message "My first commit"
  • 45. Handling Subcommands $ git clone https://github.com/zio/zio-cli.git $ git add . $ git commit –-message "My first commit"
  • 46. Handling Subcommands $ git clone https://github.com/zio/zio-cli.git $ git add . $ git commit –-message "My first commit"
  • 47. Handling Subcommands $ git clone https://github.com/zio/zio-cli.git $ git add . $ git commit –-message "My first commit"
  • 50. Inputs Validation ✓ We need to validate inputs the user provides as options/ arguments
  • 51. Inputs Validation ✓ We need to validate inputs the user provides as options/ arguments ✓ Two types of validation
  • 52. Inputs Validation ✓ We need to validate inputs the user provides as options/ arguments ✓ Two types of validation ✓Pure
  • 53. Inputs Validation ✓ We need to validate inputs the user provides as options/ arguments ✓ Two types of validation ✓Pure ✓Impure
  • 54. Pure Validation We need to validate whether an option/argument is a:
  • 55. Pure Validation We need to validate whether an option/argument is a: ✓ String
  • 56. Pure Validation We need to validate whether an option/argument is a: ✓ String ✓ Integer
  • 57. Pure Validation We need to validate whether an option/argument is a: ✓ String ✓ Integer ✓ Datetime
  • 58. Pure Validation We need to validate whether an option/argument is a: ✓ String ✓ Integer ✓ Datetime ✓ Other data type
  • 59. Pure Validation $ tail -n xyz foo.txt # tail: illegal offset -- xyz
  • 60. Pure Validation $ tail -n xyz foo.txt # tail: illegal offset -- xyz
  • 61. Pure Validation $ tail -n xyz foo.txt # tail: illegal offset -- xyz
  • 62. Impure Validation We need to verify a connection between an option/ argument and the Real World
  • 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?
  • 65. Impure Validation $ tail -n 500 foo.txt # tail: foo.txt: No such file or directory
  • 66. Impure Validation $ tail -n 500 foo.txt # tail: foo.txt: No such file or directory
  • 67. Impure Validation $ tail -n 500 foo.txt # tail: foo.txt: No such file or directory
  • 69.
  • 71. Documentation ✓ We need basically two types of documentation:
  • 72. Documentation ✓ We need basically two types of documentation: ✓Synopsis
  • 73. Documentation ✓ We need basically two types of documentation: ✓Synopsis ✓Rich documentation
  • 74. Documentation ✓ We need basically two types of documentation: ✓Synopsis ✓Rich documentation ✓ We have to consider docs for subcommands as well!
  • 75.
  • 76. Wouldn't it be dreamy if we had a ZIO Library that handles all of this stuff for us, so we can focus on our Business Logic?
  • 78. What is ZIO CLI?
  • 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
  • 83. Sample app: CSV Utils ✓ The following subcommands should be supported:
  • 84. Sample app: CSV Utils ✓ The following subcommands should be supported: ✓ Rename a column
  • 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)) }
  • 155. Compose Subcommands under a Root Command val csvUtil: Command[Subcommand] = Command(name = "csv-util", options = Options.none, args = Args.none) .subcommands( renameColumn, deleteColumn, moveColumn, changeSeparator, replicate, toJson )
  • 156. Compose Subcommands under a Root Command val csvUtil: Command[Subcommand] = Command(name = "csv-util", options = Options.none, args = Args.none) .subcommands( renameColumn, deleteColumn, moveColumn, changeSeparator, replicate, toJson )
  • 157. Compose Subcommands under a Root Command val csvUtil: Command[Subcommand] = Command(name = "csv-util", options = Options.none, args = Args.none) .subcommands( renameColumn, deleteColumn, moveColumn, changeSeparator, replicate, toJson )
  • 158. Create a CLIApp that assigns Subcommands to Handlers
  • 159. Create a CLIApp that assigns Subcommands to Handlers import zio.cli._ object CsvUtil extends ZIOCliDefault { // Options, Args, Subcommands and Root Command ... val cliApp = CliApp.make( name = "CSV Util", version = "0.0.1", summary = text("CLI to some CSV utilities"), footer = HelpDoc.p("©Copyright 2022"), command = csvUtil // Root command ) { case Subcommand(separator, inputCsv, info) => info match { case Info.RenameColumn(column, newName) => Console.printLine { s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv" } ... } } }
  • 160. Create a CLIApp that assigns Subcommands to Handlers import zio.cli._ object CsvUtil extends ZIOCliDefault { // Options, Args, Subcommands and Root Command ... val cliApp = CliApp.make( name = "CSV Util", version = "0.0.1", summary = text("CLI to some CSV utilities"), footer = HelpDoc.p("©Copyright 2022"), command = csvUtil // Root command ) { case Subcommand(separator, inputCsv, info) => info match { case Info.RenameColumn(column, newName) => Console.printLine { s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv" } ... } } }
  • 161. Create a CLIApp that assigns Subcommands to Handlers import zio.cli._ object CsvUtil extends ZIOCliDefault { // Options, Args, Subcommands and Root Command ... val cliApp = CliApp.make( name = "CSV Util", version = "0.0.1", summary = text("CLI to some CSV utilities"), footer = HelpDoc.p("©Copyright 2022"), command = csvUtil // Root command ) { case Subcommand(separator, inputCsv, info) => info match { case Info.RenameColumn(column, newName) => Console.printLine { s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv" } ... } } }
  • 162. Create a CLIApp that assigns Subcommands to Handlers import zio.cli._ object CsvUtil extends ZIOCliDefault { // Options, Args, Subcommands and Root Command ... val cliApp = CliApp.make( name = "CSV Util", version = "0.0.1", summary = text("CLI to some CSV utilities"), footer = HelpDoc.p("©Copyright 2022"), command = csvUtil // Root command ) { case Subcommand(separator, inputCsv, info) => info match { case Info.RenameColumn(column, newName) => Console.printLine { s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv" } ... } } }
  • 163. Create a CLIApp that assigns Subcommands to Handlers import zio.cli._ object CsvUtil extends ZIOCliDefault { // Options, Args, Subcommands and Root Command ... val cliApp = CliApp.make( name = "CSV Util", version = "0.0.1", summary = text("CLI to some CSV utilities"), footer = HelpDoc.p("©Copyright 2022"), command = csvUtil // Root command ) { case Subcommand(separator, inputCsv, info) => info match { case Info.RenameColumn(column, newName) => Console.printLine { s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv" } ... } } }
  • 164. Create a CLIApp that assigns Subcommands to Handlers import zio.cli._ object CsvUtil extends ZIOCliDefault { // Options, Args, Subcommands and Root Command ... val cliApp = CliApp.make( name = "CSV Util", version = "0.0.1", summary = text("CLI to some CSV utilities"), footer = HelpDoc.p("©Copyright 2022"), command = csvUtil // Root command ) { case Subcommand(separator, inputCsv, info) => info match { case Info.RenameColumn(column, newName) => Console.printLine { s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv" } ... } } }
  • 165. Create a CLIApp that assigns Subcommands to Handlers import zio.cli._ object CsvUtil extends ZIOCliDefault { // Options, Args, Subcommands and Root Command ... val cliApp = CliApp.make( name = "CSV Util", version = "0.0.1", summary = text("CLI to some CSV utilities"), footer = HelpDoc.p("©Copyright 2022"), command = csvUtil // Root command ) { case Subcommand(separator, inputCsv, info) => info match { case Info.RenameColumn(column, newName) => Console.printLine { s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv" } ... } } }
  • 166. Create a CLIApp that assigns Subcommands to Handlers import zio.cli._ object CsvUtil extends ZIOCliDefault { // Options, Args, Subcommands and Root Command ... val cliApp = CliApp.make( name = "CSV Util", version = "0.0.1", summary = text("CLI to some CSV utilities"), footer = HelpDoc.p("©Copyright 2022"), command = csvUtil // Root command ) { case Subcommand(separator, inputCsv, info) => info match { case Info.RenameColumn(column, newName) => Console.printLine { s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv" } ... } } }
  • 167. Create a CLIApp that assigns Subcommands to Handlers import zio.cli._ object CsvUtil extends ZIOCliDefault { // Options, Args, Subcommands and Root Command ... val cliApp = CliApp.make( name = "CSV Util", version = "0.0.1", summary = text("CLI to some CSV utilities"), footer = HelpDoc.p("©Copyright 2022"), command = csvUtil // Root command ) { case Subcommand(separator, inputCsv, info) => info match { case Info.RenameColumn(column, newName) => Console.printLine { s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv" } ... } } }
  • 168. Create a CLIApp that assigns Subcommands to Handlers import zio.cli._ object CsvUtil extends ZIOCliDefault { // Options, Args, Subcommands and Root Command ... val cliApp = CliApp.make( name = "CSV Util", version = "0.0.1", summary = text("CLI to some CSV utilities"), footer = HelpDoc.p("©Copyright 2022"), command = csvUtil // Root command ) { case Subcommand(separator, inputCsv, info) => info match { case Info.RenameColumn(column, newName) => Console.printLine { s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv" } ... } } }
  • 169. Create a CLIApp that assigns Subcommands to Handlers import zio.cli._ object CsvUtil extends ZIOCliDefault { // Options, Args, Subcommands and Root Command ... val cliApp = CliApp.make( name = "CSV Util", version = "0.0.1", summary = text("CLI to some CSV utilities"), footer = HelpDoc.p("©Copyright 2022"), command = csvUtil // Root command ) { case Subcommand(separator, inputCsv, info) => info match { case Info.RenameColumn(column, newName) => Console.printLine { s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv" } ... } } }
  • 170. Create a CLIApp that assigns Subcommands to Handlers import zio.cli._ object CsvUtil extends ZIOCliDefault { // Options, Args, Subcommands and Root Command ... val cliApp = CliApp.make( name = "CSV Util", version = "0.0.1", summary = text("CLI to some CSV utilities"), footer = HelpDoc.p("©Copyright 2022"), command = csvUtil // Root command ) { case Subcommand(separator, inputCsv, info) => info match { case Info.RenameColumn(column, newName) => Console.printLine { s"Handling rename-column command with column=$column, new-name=$newName, separator=$separator, input-csv=$inputCsv" } ... } } }
  • 173. Installing the application ✓ For installing your CLI application, ZIO CLI includes an installer script that:
  • 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
  • 176.
  • 178. Executing the root command with no options/arguments
  • 179.
  • 180. Executing the root command with the --help flag
  • 181.
  • 182. Executing a subcommand with the --help flag
  • 183.
  • 184. Executing a subcommand with missing args
  • 185.
  • 187.
  • 188. Executing a subcommand with different variations in options
  • 189.
  • 191.
  • 192. Pure validation of options/args
  • 193.
  • 194. Impure validation of options/args
  • 195.
  • 197.
  • 198.
  • 200.
  • 202. Shell-integrated autocompletion! For enabling autocompletion for your CLI app, a shell- completion script is generated like this: # Generates a shell-completion.sh script $ csv-util --shell-completion-script `which csv-util` --shell-type bash # Enables completions $ source completion-script.sh
  • 203. Shell-integrated autocompletion! For enabling autocompletion for your CLI app, a shell- completion script is generated like this: # Generates a shell-completion.sh script $ csv-util --shell-completion-script `which csv-util` --shell-type bash # Enables completions $ source completion-script.sh
  • 204. Shell-integrated autocompletion! For enabling autocompletion for your CLI app, a shell- completion script is generated like this: # Generates a shell-completion.sh script $ csv-util --shell-completion-script `which csv-util` --shell-type bash # Enables completions $ source completion-script.sh
  • 205. Shell-integrated autocompletion! For enabling autocompletion for your CLI app, a shell- completion script is generated like this: # Generates a shell-completion.sh script $ csv-util --shell-completion-script `which csv-util` --shell-type bash # Enables completions $ source completion-script.sh
  • 206. Shell-integrated autocompletion! For enabling autocompletion for your CLI app, a shell- completion script is generated like this: # Generates a shell-completion.sh script $ csv-util --shell-completion-script `which csv-util` --shell-type bash # Enables completions $ source completion-script.sh
  • 207.
  • 208. Wizard mode for the root command!
  • 209.
  • 210. Wizard mode for a subcommand!
  • 211.
  • 212. Comparing ZIO CLI to other libraries
  • 213. Comparing ZIO CLI to other libraries Annotations for customization Autocorrection Autocompletion Wizard mode decline ❌ ❌ ❌ ❌ mainargs ✅ ❌ ❌ ❌ case-app ✅ ❌ ❌ ❌ scallop ❌ ❌ ❌ ❌ scopt ❌ ❌ ❌ ❌ optparse ❌ ❌ ❌ ❌ zio-cli ❌ (Coming soon!) ✅ ✅ ✅
  • 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!!!
  • 220. Special thanks ✓ Ziverge for organizing this conference
  • 221. Special thanks ✓ Ziverge for organizing this conference ✓ Scalac for sponsoring
  • 222. Special thanks ✓ Ziverge for organizing this conference ✓ Scalac for sponsoring ✓ John De Goes for guidance and support