SlideShare a Scribd company logo
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

More Related Content

Similar to 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
 
Rails OO views
Rails OO viewsRails OO views
Rails OO views
Elia Schito
 
groovy & grails - lecture 6
groovy & grails - lecture 6groovy & grails - lecture 6
groovy & grails - lecture 6
Alexandre Masselot
 
Vim Hacks
Vim HacksVim Hacks
Vim Hacks
Lin Yo-An
 
Vim Hacks
Vim HacksVim Hacks
Vim Hacks
Vagner Rodrigues
 
TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기
TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기
TypeScript와 Flow: 
자바스크립트 개발에 정적 타이핑 도입하기
Heejong Ahn
 
Git Distributed Version Control System
Git   Distributed Version Control SystemGit   Distributed Version Control System
Git Distributed Version Control System
Victor Wong
 
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
Jean Bisutti
 
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
 
Lecture4 php by okello erick
Lecture4 php by okello erickLecture4 php by okello erick
Lecture4 php by okello erick
okelloerick
 
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
confluent
 
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
 
How to eat Cucmber
How to eat CucmberHow to eat Cucmber
How to eat Cucmber
Naoki Nishiguchi
 
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
 
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
Puppet
 
Sprockets
SprocketsSprockets
Cleancode
CleancodeCleancode
Cleancode
hendrikvb
 

Similar to 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
 

More from Jorge Vásquez

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
Jorge Vásquez
 
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
Jorge Vásquez
 
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!
Jorge Vásquez
 
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
Jorge Vásquez
 
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
Jorge Vásquez
 
ZIO Prelude - ZIO World 2021
ZIO Prelude - ZIO World 2021ZIO Prelude - ZIO World 2021
ZIO Prelude - ZIO World 2021
Jorge Vásquez
 
Exploring type level programming in Scala
Exploring type level programming in ScalaExploring type level programming in Scala
Exploring type level programming in Scala
Jorge Vásquez
 
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
Jorge Vásquez
 
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
Jorge Vásquez
 
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
Jorge Vásquez
 

More from 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
 

Recently uploaded

UI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
UI5con 2024 - Keynote: Latest News about UI5 and it’s EcosystemUI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
UI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
Peter Muessig
 
Oracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptxOracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptx
Remote DBA Services
 
GreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-JurisicGreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-Jurisic
Green Software Development
 
Top 9 Trends in Cybersecurity for 2024.pptx
Top 9 Trends in Cybersecurity for 2024.pptxTop 9 Trends in Cybersecurity for 2024.pptx
Top 9 Trends in Cybersecurity for 2024.pptx
devvsandy
 
What next after learning python programming basics
What next after learning python programming basicsWhat next after learning python programming basics
What next after learning python programming basics
Rakesh Kumar R
 
Hand Rolled Applicative User Validation Code Kata
Hand Rolled Applicative User ValidationCode KataHand Rolled Applicative User ValidationCode Kata
Hand Rolled Applicative User Validation Code Kata
Philip Schwarz
 
WWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders AustinWWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders Austin
Patrick Weigel
 
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
mz5nrf0n
 
Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)
Julian Hyde
 
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
Łukasz Chruściel
 
Webinar On-Demand: Using Flutter for Embedded
Webinar On-Demand: Using Flutter for EmbeddedWebinar On-Demand: Using Flutter for Embedded
Webinar On-Demand: Using Flutter for Embedded
ICS
 
一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理
dakas1
 
zOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL DifferenceszOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL Differences
YousufSait3
 
Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Need for Speed: Removing speed bumps from your Symfony projects ⚡️Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Łukasz Chruściel
 
SQL Accounting Software Brochure Malaysia
SQL Accounting Software Brochure MalaysiaSQL Accounting Software Brochure Malaysia
SQL Accounting Software Brochure Malaysia
GohKiangHock
 
Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
Drona Infotech
 
Using Xen Hypervisor for Functional Safety
Using Xen Hypervisor for Functional SafetyUsing Xen Hypervisor for Functional Safety
Using Xen Hypervisor for Functional Safety
Ayan Halder
 
Fundamentals of Programming and Language Processors
Fundamentals of Programming and Language ProcessorsFundamentals of Programming and Language Processors
Fundamentals of Programming and Language Processors
Rakesh Kumar R
 
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdfTop Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
VALiNTRY360
 
Odoo ERP Vs. Traditional ERP Systems – A Comparative Analysis
Odoo ERP Vs. Traditional ERP Systems – A Comparative AnalysisOdoo ERP Vs. Traditional ERP Systems – A Comparative Analysis
Odoo ERP Vs. Traditional ERP Systems – A Comparative Analysis
Envertis Software Solutions
 

Recently uploaded (20)

UI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
UI5con 2024 - Keynote: Latest News about UI5 and it’s EcosystemUI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
UI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
 
Oracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptxOracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptx
 
GreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-JurisicGreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-Jurisic
 
Top 9 Trends in Cybersecurity for 2024.pptx
Top 9 Trends in Cybersecurity for 2024.pptxTop 9 Trends in Cybersecurity for 2024.pptx
Top 9 Trends in Cybersecurity for 2024.pptx
 
What next after learning python programming basics
What next after learning python programming basicsWhat next after learning python programming basics
What next after learning python programming basics
 
Hand Rolled Applicative User Validation Code Kata
Hand Rolled Applicative User ValidationCode KataHand Rolled Applicative User ValidationCode Kata
Hand Rolled Applicative User Validation Code Kata
 
WWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders AustinWWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders Austin
 
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
原版定制美国纽约州立大学奥尔巴尼分校毕业证学位证书原版一模一样
 
Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)Measures in SQL (SIGMOD 2024, Santiago, Chile)
Measures in SQL (SIGMOD 2024, Santiago, Chile)
 
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf2024 eCommerceDays Toulouse - Sylius 2.0.pdf
2024 eCommerceDays Toulouse - Sylius 2.0.pdf
 
Webinar On-Demand: Using Flutter for Embedded
Webinar On-Demand: Using Flutter for EmbeddedWebinar On-Demand: Using Flutter for Embedded
Webinar On-Demand: Using Flutter for Embedded
 
一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理
 
zOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL DifferenceszOS Mainframe JES2-JES3 JCL-JECL Differences
zOS Mainframe JES2-JES3 JCL-JECL Differences
 
Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Need for Speed: Removing speed bumps from your Symfony projects ⚡️Need for Speed: Removing speed bumps from your Symfony projects ⚡️
Need for Speed: Removing speed bumps from your Symfony projects ⚡️
 
SQL Accounting Software Brochure Malaysia
SQL Accounting Software Brochure MalaysiaSQL Accounting Software Brochure Malaysia
SQL Accounting Software Brochure Malaysia
 
Mobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona InfotechMobile App Development Company In Noida | Drona Infotech
Mobile App Development Company In Noida | Drona Infotech
 
Using Xen Hypervisor for Functional Safety
Using Xen Hypervisor for Functional SafetyUsing Xen Hypervisor for Functional Safety
Using Xen Hypervisor for Functional Safety
 
Fundamentals of Programming and Language Processors
Fundamentals of Programming and Language ProcessorsFundamentals of Programming and Language Processors
Fundamentals of Programming and Language Processors
 
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdfTop Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
 
Odoo ERP Vs. Traditional ERP Systems – A Comparative Analysis
Odoo ERP Vs. Traditional ERP Systems – A Comparative AnalysisOdoo ERP Vs. Traditional ERP Systems – A Comparative Analysis
Odoo ERP Vs. Traditional ERP Systems – A Comparative Analysis
 

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