An open API service indexing awesome lists of open source software.

https://github.com/mtumilowicz/scala-zio2-scopt-workshop

Example of command line application using scopt.
https://github.com/mtumilowicz/scala-zio2-scopt-workshop

command-line command-line-app command-line-parser command-line-parsing command-line-tool scala scopt scopt-parser workshop workshop-materials workshops zio2

Last synced: 2 months ago
JSON representation

Example of command line application using scopt.

Awesome Lists containing this project

README

        

[![Build Status](https://app.travis-ci.com/mtumilowicz/scala-zio2-scopt-workshop.svg?branch=master)](https://app.travis-ci.com/mtumilowicz/scala-zio2-scopt-workshop)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)

# scala-zio2-scopt-monocle-workshop

* references
* https://github.com/scopt/scopt
* https://www.baeldung.com/scala/read-command-line-arguments
* https://github.com/optics-dev/Monocle
* https://www.optics.dev/Monocle/

## preface
* goals of this workshop
* introduction into command line applications using scopt
* workshop
* implement command to print random number that can be long or int and positive or negative
* example: `random --positive`
* run using `docker run`
* example `docker run scala-zio2-scopt-workshop sum -c1 2 -c2 3`
* remember to first `docker:publishLocal`

## scopt
* command line options parsing library
* functional DSL
* scaffold
```
import scopt.OParser
val builder = OParser.builder[Config]
val parser1 = {
import builder._
OParser.sequence(
programName("scopt"),
head("scopt", "4.x"),
// command or options,
// validations
)
}
```
* option
* flag
```
opt[Unit]("verbose")
.action((_, c) => c.copy(verbose = true))
.text("verbose is a flag")
```
* typed option
```
opt[Int]("foo")
.abbr("f")
.action((x, c) => c.copy(foo = x))
.text("foo is an integer property"),
```
* preconfigured
```
help("help") // prints usage text
version("version") // prints header text
note("...") // add given string to the usage text
```
* by default, when the --help or --version are invoked, they call sys.exit(0) after printing the
help or version information
* arguments
* similar to options, but accepts values without `--` or `-`
* example: "sum 2 3"
* prefer options
* example
```
cmd("sum")
.action((_, _) => Command.Sum(0, 0))
.text("sum(a, b) = a + b")
.children(
arg[Int]("constituent1")
.abbr("c1")
.action((value, command) => command.asInstanceOf[Sum].copy(component1 = value))
.text("constituent1 is an Int property"),
arg[Int]("constituent2")
.abbr("c2")
.action((value, command) => command.asInstanceOf[Sum].copy(component2 = value))
.text("constituent2 is an Int property"),
)
```
* validation
```
opt[Int]('f', "foo")
.action( (x, c) => c.copy(intValue = x) )
.validate( x =>
if (x > 0) success
else failure("Option --foo must be >0") )
.validate( x => failure("Just because") )
```
* command
* used to express git branch kind of argument (name means something)
* example
```
cmd("update")
.action((_, c) => c.copy(mode = "update"))
.text("update is a command.")
.children(
// options
)
```
* validation
```
checkConfig(
c =>
if (c.keepalive && c.xyz) failure("xyz cannot keep alive")
else success)
)
```
* parsing
* basic
```
OParser.parse(parser1, args, Config()) match {
case Some(config) =>
// do something
case _ =>
// arguments are bad, error message will have been displayed
}
```
uses as a default
```
abstract class DefaultOEffectSetup extends OEffectSetup {
override def displayToOut(msg: String): Unit = {
Console.out.println(msg)
}
override def displayToErr(msg: String): Unit = {
Console.err.println(msg)
}
override def reportError(msg: String): Unit = {
displayToErr("Error: " + msg)
}
override def reportWarning(msg: String): Unit = {
displayToErr("Warning: " + msg)
}
override def terminate(exitState: Either[String, Unit]): Unit =
exitState match {
case Left(_) => platform.exit(1)
case Right(_) => platform.exit(0)
}
}
```
* advanced
* by default, scopt emits output when needed to stderr and stdout
* if your application requires parsing arguments while not producing output directly, you may wish to
intercept the side effects
* example
```
OParser.runParser(parser1, args, Config()) match {
case (result, effects) =>
OParser.runEffects(effects, new DefaultOEffectSetup {
// override methods here
})

result match {
Some(config) =>
// do something
case _ =>
// arguments are bad, error message will have been displayed
}
}
```
* custom types
* example
```
object WeekDays extends Enumeration {
type WeekDays = Value
val Mon, Tue, Wed, Thur, Fri, Sat, Sun = Value
}

implicit val weekDaysRead: scopt.Read[WeekDays.Value] =
scopt.Read.reads(WeekDays withName _)
```
* refined types
```
implicit val nonZeroRead: scopt.Read[NonZeroInt] =
scopt.Read.intRead.map(unsafeParseInt)

private def unsafeParseInt(i: Int): NonZeroInt = refineV[NonZero](i)
.fold(_ => throw new IllegalArgumentException("'" + i + "' cannot be zero."), identity)
```