FreeCLI is another command line argument parsing library build using Free Applicative hence the name.
The library uses Cats and Shapeless at it's core.
Here is a list of all of FreeCLI's modules:
freecli-circe
: provides bindings for Circefreecli-core
: the core FreeCLI library, the types and type classesfreecli-examples
: a few examples on how to use the libraryfreecli-testkit
: testing helpers for testing the library
Before diving in, this is what your CLI configuration will produce:
To get started with SBT, simply add the following to your build.sbt
file:
libraryDependencies += "com.pavlosgi" %% "freecli-core" % "0.1.5"
If you require circe
bindings then add the following:
libraryDependencies += "com.pavlosgi" %% "freecli-circe" % "0.1.5"
To get started in code you can use the following imports.
import freecli.core.all._ // provides core operations
import freecli.command.all._ // provides command operations
import freecli.config.all._ // provides config operations
des
construct descriptiongroup[T]
group to generic TgroupT
group to tuple
Allows defining positional arguments
string
argument of type Stringint
argument of type Intlong
argument of type Longbool
argument of type Booleanfile
argument of type java.io.FileexistentFile
argument of type ExistentFilenewFile
argument of type NewFilearg[T]
argument with type Tname
construct argument name-~
modifier for optional configuration of the arguments
parseArgument
will return a CliParser that will need to berun
runArgumentOrFail
will return the value or fail printing the errors and help
import freecli.core.all._
import freecli.argument.all._
val dsl = string
val res: String = runArgumentOrFail(dsl)(Seq("one"))
val dsl2 = string -~ name("arg1")
val res2: String = runArgumentOrFail(dsl2)(Seq("one"))
val dsl3 = string -~ name("arg1") -~ des("argument description")
val res3: String =
runArgumentOrFail(dsl3)(Seq("one"))
case class Arguments(arg1: String, arg2: Int)
val dsl4 =
group[Arguments] {
string ::
int
}
val res4: Arguments =
runArgumentOrFail(dsl4)(Seq("one", "2"))
val dsl5 =
groupT {
string ::
int
}
val res5: (String, Int) =
runArgumentOrFail(dsl5)(Seq("one", "2"))
More examples can be found in Argument tests and the Argument example that can be run as follows:
$ sbt
[info] Set current project to freecli-root (in build file:/Users/pavlos/Workspace/freecli/)
> project freecli-examples
[info] Set current project to freecli-examples (in build file:/Users/pavlos/Workspace/freecli/)
> run 8080 host username password database
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
Multiple main classes detected, select one to run:
[1] freecli.examples.argument.DatabaseConfig
[2] freecli.examples.command.Git
[3] freecli.examples.config.DatabaseConfig
[4] freecli.examples.decoder.Decoder
[5] freecli.examples.option.DatabaseConfig
Enter number: 1
[info] Running freecli.examples.arguments.DatabaseConfig 8080 host username password database
DatabaseConfig(8080,host,username,password,database)
[success] Total time: 22 s, completed 22-Jan-2017 23:52:26
Allows defining named options
string
option of type Stringint
option of type Intlong
option of type Longdouble
option of type Doubleboolean
option of type Booleanfile
option of type FileexistentFile
option of type ExistentFilenewFile
option of type NewFileflag
flag optionhelp
adds help option that prints help (does not appear in parsed type)version
adds version option that prints the version (does not appear in parsed type)value
adds the string value to display for versionopt[T]
option of type Tsub[T]
subset of optionssubT(description: Description)
subset of options with descriptionreq
required optionor[T](default: T)
sets option default--"name"
sets the name of the option-'a'
sets the name abbreviation of the option-~
modifier for optional configuration of the options
parseOption
will return a CliParser that will need to berun
runOptionOrFail
will return the value or fail printing the errors and help
import freecli.core.all._
import freecli.option.all._
val dsl = string --"opt1"
val res: Option[String] =
runOptionOrFail(dsl)(Seq("--opt1", "one"))
val dsl2 = string --"opt1" -~ des("option description")
val res2: Option[String] =
runOptionOrFail(dsl2)(Seq("--opt1", "one"))
val dsl3 = string -'o'
val res3: Option[String] =
runOptionOrFail(dsl3)(Seq("-o", "one"))
val dsl4 = string -'o' -~ des("option description")
val res4: Option[String] =
runOptionOrFail(dsl4)(Seq("-o", "one"))
val dsl5 = string --"opt1" -'o'
val res5: Option[String] =
runOptionOrFail(dsl5)(Seq("--opt1", "one"))
val dsl6 = string --"opt1" -'o' -~ des("option description")
val res6: Option[String] =
runOptionOrFail(dsl6)(Seq("-o", "one"))
case class Options(
opt1: Option[String],
opt2: Int,
opt3: Int,
opt4: Boolean)
val dsl7 =
group[Options] {
string --"opt1" ::
int --"opt2" -~ req ::
int --"opt3" -~ or(1) ::
flag --"opt4" ::
help --"help" ::
version --"version" -~ value("v1.0")
}
val res7: Options =
runOptionOrFail(dsl7)(
Seq(
"--opt1", "one",
"--opt2", "two",
"--opt3", "three",
"--opt4"
))
val res7b: Options =
runOptionOrFail(dsl7)(
Seq(
"--opt2", "two",
"--opt3", "three"))
More examples can be found in Option tests and the Option example that can be run as follows:
$ sbt
[info] Set current project to freecli-root (in build file:/Users/pavlos/Workspace/freecli/)
> project freecli-examples
[info] Set current project to freecli-examples (in build file:/Users/pavlos/Workspace/freecli/)
> run --port 8080 --host host --username username --password password --database database
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
Multiple main classes detected, select one to run:
[1] freecli.examples.argument.DatabaseConfig
[2] freecli.examples.command.Git
[3] freecli.examples.config.DatabaseConfig
[4] freecli.examples.decoder.Decoder
[5] freecli.examples.option.DatabaseConfig
Enter number: 5
[info] Running freecli.examples.options.DatabaseConfig --port 8080 --host host --username username --password password --database database
DatabaseConfig(8080,host,username,password,database)
[success] Total time: 22 s, completed 22-Jan-2017 23:52:26
Config allows mixing options with arguments as long as the arguments come last.
The syntax for config re-exports Argument syntax and Option syntax and resolves the conflicts between them by namespacing Option with O for the following:
O.string
O.int
O.long
O.double
O.boolean
O.file
O.existentFile
O.newFile
parseConfig
will return a CliParser that will need to berun
runConfigOrFail
will return the value or fail printing the errors and help
import freecli.core.all._
import freecli.config.all._
val dsl = O.string --"opt1"
val res: Option[String] =
runConfigOrFail(dsl)(Seq("--opt1", "one"))
val dsl2 = O.string --"opt1" -~ des("option description")
val res2: Option[String] =
runConfigOrFail(dsl2)(Seq("--opt1", "one"))
val dsl3 = O.string -'o'
val res3: Option[String] =
runConfigOrFail(dsl3)(Seq("-o", "one"))
val dsl4 = O.string -'o' -~ des("option description")
val res4: Option[String] =
runConfigOrFail(dsl4)(Seq("-o", "one"))
val dsl5 = O.string --"opt1" -'o' -~ or ("1")
val res5: String =
runConfigOrFail(dsl5)(Seq("--opt1", "one"))
val dsl6 = O.string --"opt1" -'o' -~ req -~ des("option description")
val res6: String =
runConfigOrFail(dsl6)(Seq("--opt1", "one"))
case class Config(
opt1: Option[String],
opt2: Int,
opt3: Int,
opt4: Boolean,
arg1: String,
arg2: Int)
val dsl7 =
group[Config] {
O.string --"opt1" ::
O.int --"opt2" -~ req ::
O.int --"opt3" -~ or(1) ::
flag --"opt4" ::
string ::
int
}
val res7: Config =
runConfigOrFail(dsl7)(
Seq(
"--opt1", "one",
"--opt2", "two",
"--opt3", "three",
"--opt4",
"five",
"six"))
More examples can be found in Config tests and the Config example that can be run as follows:
$ sbt
[info] Set current project to freecli-root (in build file:/Users/pavlos/Workspace/freecli/)
> project freecli-examples
[info] Set current project to freecli-examples (in build file:/Users/pavlos/Workspace/freecli/)
> run --port 8080 -d -v host username password database
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
Multiple main classes detected, select one to run:
[1] freecli.examples.argument.DatabaseConfig
[2] freecli.examples.command.Git
[3] freecli.examples.config.DatabaseConfig
[4] freecli.examples.decoder.Decoder
[5] freecli.examples.option.DatabaseConfig
Enter number: 3
[info] Running freecli.examples.config.DatabaseConfig --port 8080 -d -v host username password database
DatabaseConfig(8080,true,true,host,username,password,database)
[success] Total time: 22 s, completed 22-Jan-2017 23:52:26
Allows building commands or nested commands with configurations. When nesting commands it's important to note that the run configuration of the nested command needs to be the product of the parent configuration and the nested command configuration.
cmd
construct a commandtakes
add a config to the commandtakesG[T]
add a config as TtakesT
add a config as a tupleruns
specify parameterless function that executes when running the commandruns[T]
specify function that takes T and executes when running the command
parseCommand
will return a CliParser that will need to berun
runCommandOrFail
will return the value or fail printing the errors and help
import freecli.core.all._
import freecli.config.all._
import freecli.command.all._
case class Command1Config(opt1: Option[Int], opt2: String)
case class Command2Config(opt3: Int, arg1: String)
case class Command3Config(arg2: String)
val dsl =
cmd("command1") {
takesG[Command1Config] {
O.help --"help" ::
O.int --"opt1" -~ des("opt1 description") ::
O.string --"opt2" -~ req
} ::
cmd("command2") {
takesG[Command2Config] {
O.help --"help" ::
O.int --"opt3" -~ or(1) ::
string -~ name("arg1")
} ::
runs[ParentWith[Command1Config, Command2Config]] { conf =>
println(conf)
}
} ::
cmd("command3") {
takesG[Command3Config] {
O.help --"help" ::
string -~ name("arg2")
} ::
runs[ParentWith[Command1Config, Command3Config]] { conf =>
println(conf)
}
}
}
val res: Unit =
runCommandOrFail(dsl)(
Seq(
"command1", "--opt1", "1", "opt2", "two",
"command2", "--opt3", "3", "four")).run
/*
//Fails and prints errors and help for failing command
runCommandOrFail(dsl)(
Seq(
"command1", "--opt1", "1", "opt2", "two",
"command2", "--opt3", "3")).run
*/
More examples can be found in Command tests and the Git example that can be run as follows:
$ sbt
[info] Set current project to freecli-root (in build file:/Users/pavlos/Workspace/freecli/)
> project freecli-examples
[info] Set current project to freecli-examples (in build file:/Users/pavlos/Workspace/freecli/)
> run git remote add origin [email protected]:pavlosgi/freecli.git
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
Multiple main classes detected, select one to run:
[1] freecli.examples.argument.DatabaseConfig
[2] freecli.examples.command.Git
[3] freecli.examples.config.DatabaseConfig
[4] freecli.examples.decoder.Decoder
[5] freecli.examples.option.DatabaseConfig
Enter number: 2
[info] Running freecli.examples.command.Git git remote add origin [email protected]:pavlosgi/freecli.git
Remote origin [email protected]:pavlosgi/freecli.git added
[success] Total time: 22 s, completed 22-Jan-2017 23:52:26
You can define your own string decoder to parse custom types from the command line.
import cats.data.{Validated, ValidatedNel}
import freecli.argument.all._
import freecli.core.api.{StringDecoder, StringDecoderError}
sealed trait FooBar
case object Foo extends FooBar
case object Bar extends FooBar
implicit object fooBarStringDecoder extends StringDecoder[FooBar] {
override def apply(value: String): ValidatedNel[StringDecoderError, FooBar] = {
value match {
case v if v.equalsIgnoreCase("Foo") => Validated.valid(Foo)
case v if v.equalsIgnoreCase("Bar") => Validated.valid(Bar)
case v =>
Validated.invalidNel(StringDecoderError(s"$v did not match any of (Foo, Bar)"))
}
}
override def toString(v: FooBar): String = {
v match {
case Foo => "Foo"
case Bar => "Bar"
}
}
}
val x: FooBar = runArgumentOrFail(arg[FooBar])(Seq("Foo"))
More examples can be found in StringDecoder and the Decoder example that can be run as follows:
$ sbt
[info] Set current project to freecli-root (in build file:/Users/pavlos/Workspace/freecli/)
> project freecli-examples
[info] Set current project to freecli-examples (in build file:/Users/pavlos/Workspace/freecli/)
> run Apple
[warn] Multiple main classes detected. Run 'show discoveredMainClasses' to see the list
Multiple main classes detected, select one to run:
[1] freecli.examples.argument.DatabaseConfig
[2] freecli.examples.command.Git
[3] freecli.examples.config.DatabaseConfig
[4] freecli.examples.decoder.Decoder
[5] freecli.examples.option.DatabaseConfig
Enter number: 4
[info] Running freecli.examples.decoder.Decoder Apple
Apple
[success] Total time: 6 s, completed 22-Jan-2017 23:52:26
Create key if no key exists
gpg --gen-key
gpg --list-keys
gpg --keyserver hkp://pool.sks-keyservers.net --send-keys key-id
Set sonatype credentials
export SONATYPE_USERNAME=username
export SONATYPE_PASSWORD=password
Release
sbt releaseAll
freecli is licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License.
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Copyright 2015-2019 Pavlos Georgiou