Successfully reported this slideshow.
Your SlideShare is downloading. ×

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

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Loading in …3
×

Check these out next

1 of 223 Ad

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

Download to read offline

As developers, we often have to create command-line applications, which expose APIs, workflows, or data processing functionality in a way that is accessible to scripts and non-developers. Although a simple command-line application can be created by only using Array[String], such applications lack features that users take for granted, including flexible subcommands, options, arguments, validations and documentation pages.

In this presentation, Jorge Vásquez will show you how to easily build beautiful command-line applications that your users will love. Using ZIO CLI, your command-line applications will support all types of command-line parameters, perform both Pure and Impure validation, generate beautiful short and long documentation pages, and support auto-correction plus auto-completion.

Discover how you can have fun creating beautiful command-line apps that delight your users!

As developers, we often have to create command-line applications, which expose APIs, workflows, or data processing functionality in a way that is accessible to scripts and non-developers. Although a simple command-line application can be created by only using Array[String], such applications lack features that users take for granted, including flexible subcommands, options, arguments, validations and documentation pages.

In this presentation, Jorge Vásquez will show you how to easily build beautiful command-line applications that your users will love. Using ZIO CLI, your command-line applications will support all types of command-line parameters, perform both Pure and Impure validation, generate beautiful short and long documentation pages, and support auto-correction plus auto-completion.

Discover how you can have fun creating beautiful command-line apps that delight your users!

Advertisement
Advertisement

More Related Content

Recently uploaded (20)

Advertisement

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

  1. 1. Behold! The Happy Path To Captivate Your Users With Stunning CLI Apps! Functional Scala December 2022
  2. 2. Jorge Vásquez Scala Developer/ZIO Trainer @Scalac
  3. 3. Scalac ZIO Trainings If your team is interested in our ZIO trainings:
  4. 4. Scalac ZIO Trainings If your team is interested in our ZIO trainings: ✓ Visit https://scalac.io/services/ training/
  5. 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!
  6. 6. Why CLI Apps?
  7. 7. Why CLI Apps?
  8. 8. Why CLI Apps? ✓ CLI Apps make your work scriptable/testable/usable by non- devs
  9. 9. Why CLI Apps? ✓ CLI Apps make your work scriptable/testable/usable by non- devs ✓Around API
  10. 10. Why CLI Apps? ✓ CLI Apps make your work scriptable/testable/usable by non- devs ✓Around API ✓Around data processing task
  11. 11. Why CLI Apps? ✓ CLI Apps make your work scriptable/testable/usable by non- devs ✓Around API ✓Around data processing task ✓Others
  12. 12. Why CLI Apps? ✓ CLI Apps make your work scriptable/testable/usable by non- devs ✓Around API ✓Around data processing task ✓Others ✓ Examples:
  13. 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. 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
  15. 15. Problem: Implementing Production-Grade CLI Apps
  16. 16. Implementing Production-Grade CLI Apps There are several things we need to handle!
  17. 17. Implementing Production-Grade CLI Apps There are several things we need to handle! ✓ Command-line parameters
  18. 18. Implementing Production-Grade CLI Apps There are several things we need to handle! ✓ Command-line parameters ✓ Inputs validation
  19. 19. Implementing Production-Grade CLI Apps There are several things we need to handle! ✓ Command-line parameters ✓ Inputs validation ✓ Documentation
  20. 20. Handling Parameters
  21. 21. Handling Parameters There are several types of Command-line Parameters
  22. 22. Handling Parameters There are several types of Command-line Parameters ✓ Options: Named parameters
  23. 23. Handling Parameters There are several types of Command-line Parameters ✓ Options: Named parameters ✓ Arguments: Unnamed parameters
  24. 24. Handling Parameters There are several types of Command-line Parameters ✓ Options: Named parameters ✓ Arguments: Unnamed parameters ✓ Subcommands
  25. 25. Handling Options $ git commit –-message "My first commit"
  26. 26. 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"
  27. 27. 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"
  28. 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. 29. We need to handle Flags! $ git commit –-message "My first commit" --verbose
  30. 30. 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"
  31. 31. 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"
  32. 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. 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. 34. 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"
  35. 35. 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"
  36. 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. 37. Handling Arguments $ cat foo.txt
  38. 38. Arguments are Order-sensitive! # This command... $ cp foo1.txt foo2.txt # Is NOT the same as... $ cp foo2.txt foo1.txt
  39. 39. Arguments are Order-sensitive! # This command... $ cp foo1.txt foo2.txt # Is NOT the same as... $ cp foo2.txt foo1.txt
  40. 40. Arguments are Order-sensitive! # This command... $ cp foo1.txt foo2.txt # Is NOT the same as... $ cp foo2.txt foo1.txt
  41. 41. We need to handle Varargs! $ cat foo1.txt foo2.txt foo3.txt
  42. 42. Handling Subcommands $ git clone https://github.com/zio/zio-cli.git $ git add . $ git commit –-message "My first commit"
  43. 43. Handling Subcommands $ git clone https://github.com/zio/zio-cli.git $ git add . $ git commit –-message "My first commit"
  44. 44. Handling Subcommands $ git clone https://github.com/zio/zio-cli.git $ git add . $ git commit –-message "My first commit"
  45. 45. Handling Subcommands $ git clone https://github.com/zio/zio-cli.git $ git add . $ git commit –-message "My first commit"
  46. 46. Inputs Validation
  47. 47. Inputs Validation
  48. 48. Inputs Validation ✓ We need to validate inputs the user provides as options/ arguments
  49. 49. Inputs Validation ✓ We need to validate inputs the user provides as options/ arguments ✓ Two types of validation
  50. 50. Inputs Validation ✓ We need to validate inputs the user provides as options/ arguments ✓ Two types of validation ✓Pure
  51. 51. Inputs Validation ✓ We need to validate inputs the user provides as options/ arguments ✓ Two types of validation ✓Pure ✓Impure
  52. 52. Pure Validation We need to validate whether an option/argument is a:
  53. 53. Pure Validation We need to validate whether an option/argument is a: ✓ String
  54. 54. Pure Validation We need to validate whether an option/argument is a: ✓ String ✓ Integer
  55. 55. Pure Validation We need to validate whether an option/argument is a: ✓ String ✓ Integer ✓ Datetime
  56. 56. Pure Validation We need to validate whether an option/argument is a: ✓ String ✓ Integer ✓ Datetime ✓ Other data type
  57. 57. Pure Validation $ tail -n xyz foo.txt # tail: illegal offset -- xyz
  58. 58. Pure Validation $ tail -n xyz foo.txt # tail: illegal offset -- xyz
  59. 59. Pure Validation $ tail -n xyz foo.txt # tail: illegal offset -- xyz
  60. 60. Impure Validation We need to verify a connection between an option/ argument and the Real World
  61. 61. Impure Validation We need to verify a connection between an option/ argument and the Real World ✓ Does the given file actually exist?
  62. 62. 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?
  63. 63. Impure Validation $ tail -n 500 foo.txt # tail: foo.txt: No such file or directory
  64. 64. Impure Validation $ tail -n 500 foo.txt # tail: foo.txt: No such file or directory
  65. 65. Impure Validation $ tail -n 500 foo.txt # tail: foo.txt: No such file or directory
  66. 66. Documentation
  67. 67. Documentation
  68. 68. Documentation ✓ We need basically two types of documentation:
  69. 69. Documentation ✓ We need basically two types of documentation: ✓Synopsis
  70. 70. Documentation ✓ We need basically two types of documentation: ✓Synopsis ✓Rich documentation
  71. 71. Documentation ✓ We need basically two types of documentation: ✓Synopsis ✓Rich documentation ✓ We have to consider docs for subcommands as well!
  72. 72. 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?
  73. 73. Presenting ZIO CLI!
  74. 74. What is ZIO CLI?
  75. 75. What is ZIO CLI? ✓ It's a library that allows you to rapidly build Powerful Command-line Applications powered by ZIO
  76. 76. 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
  77. 77. ZIO CLI Walkthrough
  78. 78. Sample app: CSV Utils
  79. 79. Sample app: CSV Utils ✓ The following subcommands should be supported:
  80. 80. Sample app: CSV Utils ✓ The following subcommands should be supported: ✓ Rename a column
  81. 81. Sample app: CSV Utils ✓ The following subcommands should be supported: ✓ Rename a column ✓ Delete a column
  82. 82. Sample app: CSV Utils ✓ The following subcommands should be supported: ✓ Rename a column ✓ Delete a column ✓ Move a column to a different position
  83. 83. 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
  84. 84. 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
  85. 85. 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
  86. 86. Create the CLI application entrypoint
  87. 87. Create the CLI application entrypoint import zio.cli._ object CsvUtil extends ZIOCliDefault { val cliApp = ??? // We need to implement this }
  88. 88. Create the CLI application entrypoint import zio.cli._ object CsvUtil extends ZIOCliDefault { val cliApp = ??? // We need to implement this }
  89. 89. Create the CLI application entrypoint import zio.cli._ object CsvUtil extends ZIOCliDefault { val cliApp = ??? // We need to implement this }
  90. 90. Create the CLI application entrypoint import zio.cli._ object CsvUtil extends ZIOCliDefault { val cliApp = ??? // We need to implement this }
  91. 91. Define a Pure Data- Structure that models inputs to all possible Subcommands
  92. 92. 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 }
  93. 93. 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 }
  94. 94. 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 }
  95. 95. 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 }
  96. 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. 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. 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. 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. 100. 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 } }
  101. 101. 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 } }
  102. 102. 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 } }
  103. 103. 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 } }
  104. 104. Define Subcommands themselves
  105. 105. 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!
  106. 106. 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!
  107. 107. 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!
  108. 108. 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!
  109. 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. 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. 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. 112. 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!
  113. 113. 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!
  114. 114. 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!
  115. 115. 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!
  116. 116. 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.")
  117. 117. 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.")
  118. 118. 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.")
  119. 119. 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.")
  120. 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. 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. 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. 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. 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. 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. 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. 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. 128. 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)) }
  129. 129. 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)) }
  130. 130. 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)) }
  131. 131. 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)) }
  132. 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. 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. 134. 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)) }
  135. 135. 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)) }
  136. 136. 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)) }
  137. 137. 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)) }
  138. 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. 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. 140. 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)) }
  141. 141. 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)) }
  142. 142. 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)) }
  143. 143. 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)) }
  144. 144. 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)) }
  145. 145. 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)) }
  146. 146. 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)) }
  147. 147. 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)) }
  148. 148. 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)) }
  149. 149. 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)) }
  150. 150. Compose Subcommands under a Root Command
  151. 151. 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 )
  152. 152. 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 )
  153. 153. 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 )
  154. 154. Create a CLIApp that assigns Subcommands to Handlers
  155. 155. 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" } ... } } }
  156. 156. 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" } ... } } }
  157. 157. 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" } ... } } }
  158. 158. 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" } ... } } }
  159. 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. 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. 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. 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. 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. 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. 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. 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. 167. Installing the application
  168. 168. Installing the application
  169. 169. Installing the application ✓ For installing your CLI application, ZIO CLI includes an installer script that:
  170. 170. Installing the application ✓ For installing your CLI application, ZIO CLI includes an installer script that: ✓Generates a GraalVM native image for you
  171. 171. 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
  172. 172. Touring the CSV-Util application!
  173. 173. Executing the root command with no options/arguments
  174. 174. Executing the root command with the --help flag
  175. 175. Executing a subcommand with the --help flag
  176. 176. Executing a subcommand with missing args
  177. 177. Executing a subcommand successfully
  178. 178. Executing a subcommand with different variations in options
  179. 179. Grouping several flags together
  180. 180. Pure validation of options/args
  181. 181. Impure validation of options/args
  182. 182. Varargs
  183. 183. Automatic misspellings detection and correction!
  184. 184. Shell-integrated autocompletion!
  185. 185. 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
  186. 186. 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
  187. 187. 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
  188. 188. 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
  189. 189. 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
  190. 190. Wizard mode for the root command!
  191. 191. Wizard mode for a subcommand!
  192. 192. Comparing ZIO CLI to other libraries
  193. 193. Comparing ZIO CLI to other libraries Annotations for customization Autocorrection Autocompletion Wizard mode decline ❌ ❌ ❌ ❌ mainargs ✅ ❌ ❌ ❌ case-app ✅ ❌ ❌ ❌ scallop ❌ ❌ ❌ ❌ scopt ❌ ❌ ❌ ❌ optparse ❌ ❌ ❌ ❌ zio-cli ❌ (Coming soon!) ✅ ✅ ✅
  194. 194. Summary
  195. 195. Summary
  196. 196. Summary ✓ CLI apps are very useful for non-devs to interact with your applications
  197. 197. Summary ✓ CLI apps are very useful for non-devs to interact with your applications ✓ However, writing production-grade CLI apps is hard
  198. 198. 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!!!
  199. 199. Special thanks
  200. 200. Special thanks ✓ Ziverge for organizing this conference
  201. 201. Special thanks ✓ Ziverge for organizing this conference ✓ Scalac for sponsoring
  202. 202. Special thanks ✓ Ziverge for organizing this conference ✓ Scalac for sponsoring ✓ John De Goes for guidance and support
  203. 203. Contact me @jorvasquez2301 jorge-vasquez-2301 jorge.vasquez@scalac.io

×