SlideShare a Scribd company logo
1 of 223
Download to read offline
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 Perlchinaguestcf9240
 
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
 
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 SystemVictor 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émoireJean Bisutti
 
Perl.Hacks.On.Vim Perlchina
Perl.Hacks.On.Vim PerlchinaPerl.Hacks.On.Vim Perlchina
Perl.Hacks.On.Vim PerlchinaLin Yo-An
 
C sharp fundamentals Part I
C sharp fundamentals Part IC sharp fundamentals Part I
C sharp fundamentals Part IDevMix
 
Lecture4 php by okello erick
Lecture4 php by okello erickLecture4 php by okello erick
Lecture4 php by okello erickokelloerick
 
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 2019confluent
 
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 RubyLindsay Holmwood
 
Effective codereview | Dave Liddament | CODEiD
Effective codereview | Dave Liddament | CODEiDEffective codereview | Dave Liddament | CODEiD
Effective codereview | Dave Liddament | CODEiDCODEiD PHP Community
 
Perl.Hacks.On.Vim
Perl.Hacks.On.VimPerl.Hacks.On.Vim
Perl.Hacks.On.VimLin 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 BackstopPuppet
 

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.0Jorge 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 ZIOJorge 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 ZIOJorge 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 @FunctionalWorldJorge Vásquez
 
ZIO Prelude - ZIO World 2021
ZIO Prelude - ZIO World 2021ZIO Prelude - ZIO World 2021
ZIO Prelude - ZIO World 2021Jorge Vásquez
 
Exploring type level programming in Scala
Exploring type level programming in ScalaExploring type level programming in Scala
Exploring type level programming in ScalaJorge 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 WorkJorge 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 ScalaJorge 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 effectsJorge 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

CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceBrainSell Technologies
 
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full RecordingOpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full RecordingShane Coughlan
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Natan Silnitsky
 
Not a Kubernetes fan? The state of PaaS in 2024
Not a Kubernetes fan? The state of PaaS in 2024Not a Kubernetes fan? The state of PaaS in 2024
Not a Kubernetes fan? The state of PaaS in 2024Anthony Dahanne
 
A healthy diet for your Java application Devoxx France.pdf
A healthy diet for your Java application Devoxx France.pdfA healthy diet for your Java application Devoxx France.pdf
A healthy diet for your Java application Devoxx France.pdfMarharyta Nedzelska
 
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptxReal-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptxRTS corp
 
What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...Technogeeks
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Matt Ray
 
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...Angel Borroy López
 
Amazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilitiesAmazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilitiesKrzysztofKkol1
 
SoftTeco - Software Development Company Profile
SoftTeco - Software Development Company ProfileSoftTeco - Software Development Company Profile
SoftTeco - Software Development Company Profileakrivarotava
 
Sending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdfSending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdf31events.com
 
Patterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencePatterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencessuser9e7c64
 
Salesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZSalesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZABSYZ Inc
 
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptxThe Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptxRTS corp
 
Leveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Leveraging AI for Mobile App Testing on Real Devices | Applitools + KobitonLeveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Leveraging AI for Mobile App Testing on Real Devices | Applitools + KobitonApplitools
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsChristian Birchler
 
Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalLionel Briand
 
Ronisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited CatalogueRonisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited Catalogueitservices996
 
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...OnePlan Solutions
 

Recently uploaded (20)

CRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. SalesforceCRM Contender Series: HubSpot vs. Salesforce
CRM Contender Series: HubSpot vs. Salesforce
 
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full RecordingOpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
OpenChain Education Work Group Monthly Meeting - 2024-04-10 - Full Recording
 
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
Taming Distributed Systems: Key Insights from Wix's Large-Scale Experience - ...
 
Not a Kubernetes fan? The state of PaaS in 2024
Not a Kubernetes fan? The state of PaaS in 2024Not a Kubernetes fan? The state of PaaS in 2024
Not a Kubernetes fan? The state of PaaS in 2024
 
A healthy diet for your Java application Devoxx France.pdf
A healthy diet for your Java application Devoxx France.pdfA healthy diet for your Java application Devoxx France.pdf
A healthy diet for your Java application Devoxx France.pdf
 
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptxReal-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
Real-time Tracking and Monitoring with Cargo Cloud Solutions.pptx
 
What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...What is Advanced Excel and what are some best practices for designing and cre...
What is Advanced Excel and what are some best practices for designing and cre...
 
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
Open Source Summit NA 2024: Open Source Cloud Costs - OpenCost's Impact on En...
 
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
Alfresco TTL#157 - Troubleshooting Made Easy: Deciphering Alfresco mTLS Confi...
 
Amazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilitiesAmazon Bedrock in Action - presentation of the Bedrock's capabilities
Amazon Bedrock in Action - presentation of the Bedrock's capabilities
 
SoftTeco - Software Development Company Profile
SoftTeco - Software Development Company ProfileSoftTeco - Software Development Company Profile
SoftTeco - Software Development Company Profile
 
Sending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdfSending Calendar Invites on SES and Calendarsnack.pdf
Sending Calendar Invites on SES and Calendarsnack.pdf
 
Patterns for automating API delivery. API conference
Patterns for automating API delivery. API conferencePatterns for automating API delivery. API conference
Patterns for automating API delivery. API conference
 
Salesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZSalesforce Implementation Services PPT By ABSYZ
Salesforce Implementation Services PPT By ABSYZ
 
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptxThe Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
The Role of IoT and Sensor Technology in Cargo Cloud Solutions.pptx
 
Leveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Leveraging AI for Mobile App Testing on Real Devices | Applitools + KobitonLeveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
Leveraging AI for Mobile App Testing on Real Devices | Applitools + Kobiton
 
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving CarsSensoDat: Simulation-based Sensor Dataset of Self-driving Cars
SensoDat: Simulation-based Sensor Dataset of Self-driving Cars
 
Precise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive GoalPrecise and Complete Requirements? An Elusive Goal
Precise and Complete Requirements? An Elusive Goal
 
Ronisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited CatalogueRonisha Informatics Private Limited Catalogue
Ronisha Informatics Private Limited Catalogue
 
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
Revolutionizing the Digital Transformation Office - Leveraging OnePlan’s AI a...
 

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