Giter Site home page Giter Site logo

decline's People

Contributors

adamretter avatar andreamarcolin avatar armanbilge avatar avdv avatar bardurarantsson avatar bardurdam avatar benhutchisonseek avatar bkirwi avatar chuwy avatar daddykotex avatar dougc avatar fthomas avatar ivan-klass avatar johnynek avatar kailuowang avatar keynmol avatar kubukoz avatar lavrov avatar martijnhoekstra avatar mdedetrich avatar nikololiahim avatar oscar-stripe avatar plmuninn avatar scala-steward avatar sethtisue avatar taig avatar tpolecat avatar travisbrown avatar travisbrown-stripe avatar xuwei-k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

decline's Issues

Build for Scala Native?

What would be the complexity for providing decline for Scala Native?
I've seen that it depends on refined, which I do not understand clearly if would be available for Scala Native or not.
It also depends on ScalaTest, which in general can be easily replaced by uTest.
It also depends on ScalaCheck, which I simply would disable in the case of Scala Native.

Can you provide some guidance on how it could be ported?

Thanks

How are subcommands composed with `orElse` distinguished?

An aspect of subcommands perplexes me. When a set of subcommands are composed together using orElse, there seems to be no way to distinguish which command was fired, if the Opts for commands are the same (eg Int, Unit etc).

An example of the problem can be found in ParseSpec. Snippet:

      val opts = run orElse clear

      opts.parse(List("run", "--foo", "77")) should equal(Valid(Some(77)))
      opts.parse(List("clear", "--bar", "16")) should equal(Valid(Some(16)))

Note how there is nothing in the response that tells us whether run or clear was the selected command, and since the Opts type Int is the same for both, that doesn't help.

The solution I propose is an alternate combinator orElseLabelled that returns Opts[(String, A)] when composing Command[A], the String in the tuple being the name of the command. Happy to help but want to discuss the problem before going to code...

One "workaround" is to wrap each commands Opts in a case class (as in eg #44) but that adds extra overhead.

Revisit Opts.subcommands

Would you be willing to expose the SubCommand type the same way as Command ?
It would be very useful to use it instead of its parent type Opts.

Currently, it's possible to create a command with Opts (arguments, or subcommands), it would be nice to be able to use the same approach for subcommands.

Passing `"-x"` or `"--xx"` in arguments

I have a program that takes in strings as arguments and some of those strings may contain flags:

❯ runs new --name 'Pendulum-v0/maiden' config '--policy SAC --max-time-steps 100000'

Unexpected option: --policy SAC --max-time-steps 100000

Usage: runs new config <config>...

Pass the string of arguments given to launch script as in
 ❯ docker run -d --rm -it <image> <config>

Options and flags:
    --help
        Display this help text.

So it appears that decline is parsing '--policy SAC --max-time-steps 100000'' as an option, not as a string argument. Is there a way to fix this?

By the way I love this library! Wish there was something like this for every language.

Proposal: Allow grouping of options for help

Really liking the library. I recently started using it for a project and it's the easiest and most flexible command line parser I've come across.

On feature I'd like to see is a way to group a set of flags together and provide help text for that group. I imagine something like:

(opt1, opt2).mapN(GroupedOptions.apply).groupedAs("Options for debugging")

Then the help generator could group these options together with the extra text.

Supporting User Input

Any thoughts on adding support for user input?

I understand the downside that is, the library will likely have to come with cats-effect by default, instead of being optional (unless this also was a separate module).

But I think it would be vital to enable complex tooling and allow the user to build complex flows.

A very simple example would be

$ decline-clt  diff-push-merge 
     username: ....
     password: ...

    Success!

Furthermore, it would enable something like this ticket => #171

To be turned into

$ decline-clt generate-file
    Enter File Name: ...
    Choose file type:
        [.] Class
        [.] Trait
        [.] Object

Arguments from environment variables

It would be nice to be able to add the option to parse arguments from an environment variable. Is that something this library would be interested in doing or should it be provided by some other library?

combinators and typeclass instances on Argument

We could definitely have Apply[Argument] it seems (we don't have pure because can't supply a defaultMetavar) if we take the "left most" or "right most" defaultMetavar.

We can certainly have Functor[Argument] even without the above constraint.

But if we would allow the above (say take the left side). I think we can also have Alternative[Argument] which can be useful to compose different Arguments.

One thing I like is an

def either[A, B](a: Argument[A], b: Argument[B]): Argument[Either[A, B]] =
  Alternative[Argument].combineK(b.map(Right(_)), a.map(Left(_))) 

This allows a single flag like --input to parse multi values (say uris but also local file paths)

Interactive mode?

It would be nice if we could run the application once and then have a sort of REPL in which we could write a command, wait for its completion and write another one. As a further step (much further down the line) commands could change states, resulting in the available command set changing.

The major upside of having this is of course fast startup (not loading a fresh JVM on every time & not allocating resources, if the app has any) and not having to write the application's path every time.

Support for effects in CommandApp

Hi! I just started using decline and it's looking pretty cool.

Did you consider supporting cats-effect style effects? So that there'd be a variant of CommandApp like CommandApp[Task], whose main would have to return a Task[Unit].

Additionally, if Opts had a Traverse instance (didn't think about if it's possible yet, but it should be), we could validate them with effects - let's say I have an Opts[Path] and want to make sure that the file exists - which is an IO operation, and I'd rather do it in an effect.

Of course I'm willing to help :)

What do you think?

Thanks!

proposal: remove defaultMetaVar from Argument

If we did this, we could imagine that Argument can be a coherent typeclass. As it is, it couples information from parsing and the semantics of how a particular argument is being used.

Instead, we could require the metavar at the Opts site, and if we did that, we could really give a batteries totally included suite of Argument instances for all common types. Indeed, we could put them in the Argument companion for primitives and standard types (BigInt, Path, uuid, etc...)

Release 0.6.1?

Any chance of a 0.6.1 release?

I'd love to be able to use the Argument[UUID] I added in #51 . No rush though, this is mostly for tracking purposes.

Autocomplete generation

I've never really looked into it in earnest, but it feels like it could be cool to generate a bash autocomplete file from a supplied com.monovore.decline.Command. This functionality feels like it would be a separate module though.

How to conditionally require an option given the value of another?

This is mostly a question, and possibly a request to add more examples to documentation.

Suppose I have:

val vehicleType: Opts[VehicleType] = ???

If the user specifies vehicleType Car, then I want to require another option:

val licensePlate: Opts[String] = ???

If the user specifies vehicleType Bicycle, then I don't want to require any other options.

How can I accomplish this?

Document behaviour for the top-level command name

When subcommands are composed under a top command, the resultant parser wont accept the top command. See ammonite REPL session for example:

Welcome to the Ammonite Repl 1.4.2
(Scala 2.12.7 Java 11.0.1)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
@ import $ivy.`com.monovore:decline_2.12:0.6.2`
import $ivy.$

@ import cats.implicits._
import cats.implicits._

@ import com.monovore.decline._
import com.monovore.decline._

@
Command("top", "top")(Opts.subcommand(Command("Sub1", "sub1")(Opts.unit)).orElse(Opts.subcommand(Command("Sub2", "sub2")(Opts.unit)))
)
res3: Command[Unit] = com.monovore.decline.Command@5ab5924c

@ res3.showHelp
res4: String = """Usage:
    top Sub1
    top Sub2

top

Options and flags:
    --help
        Display this help text.
...

@ res3.parse(List("top", "Sub1"))
res5: Either[Help, Unit] = Left(
  Help(
    List("Unexpected argument: top"),
    NonEmptyList("top", List()),
    List("Sub1", "Sub2"),
    List(
      "top",
      """Options and flags:
    --help
...

I'd expect that input List("top", "Sub1") should be parsed to Right(Unit). That's shown as valid input in the generated help text.

update to scalajs 1.0

I can't update a project of mine to scalajs 1.0 due to being blocked by decline.

any objections?

Add withDefault value to help message

When using Opts.withDefault I would expect the given value to appear in the help message. Unfortunately it does not and it is not impossible to add it to an existing Opts.

val maxConcurrent: Opts[Int] =
  Opts.option[Int]("max-concurrent", "Maximum amount of concurrent connections")

val maxConcurrentOrDefault: Opts[Int] = maxConcurrent.withDefault(4)

Ideally I'd wish for a way to modify the help message of an existing Opts. E.g. something like:

val maxConcurrentOrDefault: Opts[Int] = {
  val default = 4
  maxConcurrent.withDefault(default).updateHelp(_ + s" [default: $default]")
}

can we publish for cats 1.0.0-MF

I am in a minor dependency hell where I need cats 1.0.0 for something, but decline won't run with cats 1.0.0

I see a 0.4.0-M1 tagged, but I don't see it in maven central. Is there a way I can get it?

Idea: Add a zsh-completions output extension

I think most of the mac users are using zsh instead of bash. zsh has this awesome plugin for auto completion. I think it could be a great extension for "decline" to autogenerate this zsh-autocompletion output from the Opts. I have seen this output in several CLIs like kubectl for kubernetes (source <(kubectl completion zsh)).

So my suggestion is to build an plugin for decline that generates the needed output for example with ./my-decline-cli completion zsh. Perhaps as "com.monovore" %% "decline-zsh-completions" % "1.3.0"?

Clarify Get Started

I just wanted to give Decline a shot. But going through the Get Started gave me:

[109/109] cli.run 
Hello world!
nbszmbp013:camundala mpa$ hello-world --help
bash: hello-world: command not found

I clearly miss a step. But there is no clue in the Get Started of the Documentation.

What do I miss here?

Ability to compose more than 22 arguments?

Hello,

I've done a simple PoC with decline and I've stumbled on a difficulty or maybe on my own feet.
This is the scenario:

My application is made of multiple modules and the vast majority of them provide a command line parser.
I can pick and choose these modules so that I can build specialized fat .jar files according to circumstances.

In a nutshell, I would like to have the ability of composing several command line parsers.

If I'm not mistaken, "the way composition works" (or at least: the way it is suggested in the documentation) is via tuples. However, the concern that immediately comes to mind is that tuples are limited to 22 elements at the moment, something which will only be lifted with Dotty.

Could someone suggest a way of composing parsers so that an arbitrary number of command line arguments would be manageable?

Thanks

json string arguments

I had an argument of type String, that I expected to be able to pass json in:

cmd --foo "[1, 2, 3]"

but I get a confusing error as it looks like decline is splitting the space json argument.

Support ZIO's effect types and runtime system

As a user, I would expect to be able to use ZIO's runtime system, and use its effect types, like Task.
I think these could be done with two improvements:

  • Separating CommandIOApp abstract class definition (with its abstract main method) from the wiring to the cats-effect runtime system (IOApp), and letting the abstract method be polymorphic in the effect type.
  • Add two implementations of this abstract class: one which is identical to how it looks now, for the cats-effect runtime system, and one for the ZIO runtime system (maybe in another module, or just depending on ZIO in the effect module?)

With this approach, even Monix could be supported by creating a third implementation based on his own runtime system (TaskApp). Some insights on why each IO monad implementation benefits from his own runtime system can be found here.

Let me know what you think about this and if you are interested in supporting ZIO at all, if yes I could try to sketch a PR.

Setup gitter room

What do you think about having faster communication channel?
If agreed to set it up then lets remember about badge in readme :)

bug: in help, environment variables are repeated if they appear in different branches

Opts.options that are used in separate branches of, e.g., an orElse only appear once in the help output, but Opts.envs are repeated in the "Environment Variables" section.

This (nonsensical) sample:

import cats.implicits._
import com.monovore.decline.{CommandApp, Opts}

object TestApp extends CommandApp(
  name = "hello-world",
  header = "Says hello!",
  main = {
    val userOpt =
      Opts.option[String]("target", help = "Person to greet.") orElse
        Opts.env[String]("TARGET", help = "Person to greet.").withDefault("world")

    val quietOpt = Opts.flag("quiet", help = "Whether to be quiet.")

    val opts: Opts[String] = (quietOpt, userOpt).mapN((_, u) => u) orElse userOpt

    opts.map { u =>
      println(s"Hello $u!")
    }
  }) {
}

produces the following when run with --help:

Usage: hello-world [--quiet [--target <string>] | --target <string>]

Says hello!

Options and flags:
    --help
        Display this help text.
    --quiet
        Whether to be quiet.
    --target <string>
        Person to greet.

Environment Variables:
    TARGET=<string>
        Person to greet.
    TARGET=<string>
        Person to greet.

enumerate all possible values for enum opts

The main advantage of Enum Opts over the string is that you have a full list of all available values. I believe that this should be included in help (currently I do it manually in the brackets which is timeconsuming and leads to errors if I change the enum and forget to change help) and also when the user makes a mistake in the option value it should tell her the list of all possible values from the enum in the error report

Add --arg=value format

We have a project on top of scio which is being on top of Apache Beam / Google Dataflow supports only --arg=value format as opposed to decline's --arg value. Scio provides its own tooling for argparsing, but I find decline style way more composable, type-safe and just purely awesome.

I was wondering if somebody considered adding an option for equals sign or would it be considered as contribution at all.

Composing subcommands

Decline provides the product combinator to compose multiple subcommands in a row, but it appears the parser has an internal assumption that there will be just one subcommand.

In this example, we construct a Opts for two parallel subcommands, but the parser is unable to parse the corresponding input.

Welcome to the Ammonite Repl 1.4.2
(Scala 2.12.7 Java 11.0.1)
If you like Ammonite, please support our development at www.patreon.com/lihaoyi
@ import $ivy.`com.monovore:decline_2.12:0.6.2`
import $ivy.$

@ import cats.implicits._
import cats.implicits._

@ import com.monovore.decline._
import com.monovore.decline._

@ import cats._
import cats._

@ val nel = Apply[Opts].product(Opts.subcommand("FooInt", "fint")(Opts.argument[Int]("n")), Opts.subcommand("FooS", "foos")(Opts.argument[String]("s")))
nel: Opts[(Int, String)] = App(
  App(Pure(cats.Apply$$Lambda$1767/0x0000000800a07040@636e4bf8), Subcommand(com.monovore.decline.Command@302da330)),
  Subcommand(com.monovore.decline.Command@3568ea59)
)

@ val cmd = Command("test", "test")(nel)
cmd: Command[(Int, String)] = com.monovore.decline.Command@30bd39d5

@ cmd.parse(List("FooInt", "6", "FooS", "s" ))
res6: Either[Help, (Int, String)] = Left(
  Help(
    List("Unexpected argument: FooS"),
    NonEmptyList("test", List("FooInt")),
    List("<n>"),
    List(
      "fint",
      """Options and flags:
    --help
        Display this help text."""
    )
  )
)

It seems like there is an assumption in the parser logic that there is just one subcommand, although the Opts algebra seems to allow it.

add scalacheck tests

many of the option parsing statements can be stated in terms of scalacheck pretty easily (for all A, if we call .toString on them, we can get them back). It would be nice to have tests like this to check for any corner cases.

Short subcommand names

First off - decline is the most usable and sensible CLI parsing library in the Scala ecosystem I've encountered to date - so thanks for that! :-)

Summary:

It would be great if when parsing the library could match on a subcommand when enough letters are specified to uniquely identify the subcommand.

Example:

As a toy example, consider re-implementing bits of the kubectl cli:

lazy val get: Command[Observable[String]] = Command[String](
      name = "get",
      header = "Get resources from the k8s API"
    )(Opts.subcommands(
      get_pods,
      get_statefulsets,
      get_services,
      ...
    ))

It would be nice to trigger execution on get po or get st or get se instead of having to spell out the full resource each time.

Motivation:

For example, in the "real" kubectl, I can do something like kubectl get po rather than kubectl get pods. For CLIs that are complex and heavily used it's nice to be able to trim characters wherever possible. It's probably not part of the POSIX standard, but it certainly does improve quality-of-life for end users and seems prevalent enough in the different CLI tools I've encountered over the years.

Suggestion:

Could perhaps be implemented as an optional behavior - e.g.: Opts.subcommands(..., partialMatch = true)

Disclaimer:

I know Opts is implemented as an Alternative right now. I won't claim to have thought much about whether or not this parsing change would influence that... gut says probably not, but if the answer is "can't get there from here - algebra laws won't allow it" that's fine too.

Also - this could be related to #52 - may need similar matching logic in place (somewhere) for bash completion. Possible that the partial subcommand logic belongs there instead of as a built-in on the library itself... not sure.

Subcommands arguments

Is there a way or plan to display arguments for subcommands?

I think it would be handy to have them in detail in Subcommands section and in usage part with shorter version.

ScalaJs support

We need to remove (or extract) Path Argument to make it possible to use it from scalajs. WDYT?

Better typing for `Opts.OrElse` chaning

Currently if you chain subcommands with orElse you would get Product type as a result and lose exhaustiveness checks during pattern matching. It would be great to return something like Shapeless Coproduct instead (if this even makes sense?) or something in order to properly type it.

Eg:

case class AOpts(a: Boolean, aa: Int, aaa: String)
case class BOpts(b: String, bb: Int, bbb: Boolean)

val aSub: Opts[AOpts] = Opts.subcommand
val bSub: Opts[BOpts] = Opts.subcommand

// You get product, but ideally it should be something like `[AOpts with BOpts]`
val main: Command[Product] = Command() { aSub orElse bSub } 

main.parse() match {
  case Left(_) =>
  case Right(AOpts(...)) =>
  case Right(BOpts(...)) =>
  case Right(_) => // mandatory because of the Product type inside
}

Argument.fromMap

@bkirwi what do you think of a method like this:

  def fromMap[A](defmeta: String, nameToValue: Map[String, A]): Argument[A] =
         new Argument[A] {
           def defaultMetavar: String = defmeta
           val keys = nameTo.keys.toList.sorted.mkString(", ")
           def read(string: String): ValidatedNel[String, A] =
             nameToValue.get(string) match {
               case Some(t) => Validated.valid(t)
               case None =>  
                 Validated.invalidNel(s"unknown value: $string, expected one of: $keys")
             }

This can make it convenient to provide a string to value mapping and get an Argument from it. This is useful for custom enum-like ADTs.

Combining Opts.flag with Opts.env using orElse

Is it possible to combine a flag with fallback to environment variable and ultimately a default value?

ex)

...
val myArg = (Opts
    .flag(
      "with-foo",
      help = "With foo"
    ) orElse Opts
    .env[String]("WITH_FOO", help = "With foo", metavar = "true||false")) orFalse
...

The desired outcome is to allow for CLI to use the --with-foo flag but alternatively allow users to instead specify WITH_FOO=true as an env variable, with both cases defaulting to false.

Because of the intentional missing instances we can't set Opts.env[Boolean], but in the example above the orElse gives a warning that a type was inferred to be Any; this may indicate a programming error. and orFalse gives an error Cannot prove that Any <:< Unit.

Any guidance on how best to achieve the desired outcome?

I have also tried to validate and then cast the Opts.env value, then use withDefault("false") instead of orFalse for the default fallback value:

...
  Opts
    .env[String]("WITH_FOO", help = "With foo", metavar = "true||false")
    .validate("Value must be Boolean") { (s: String) => Try(s.toBoolean).getOrElse(false) }
    .map { _.toBoolean } withDefault ("false")
...

Allow listing usage in API

I have a use case where I want to throw an exception (in order to fail an IO) if a particular flag isn't set in a particular context. I want that exception to contain information about how to turn the flag on i.e.
new Exception(s"Failed to Foo, turn on ${Usage.fromOpt(BarOption)}

with the intention that the string look something like Failed to Foo, turn on --bar. For now I'm using the toString method, but it's output isn't exactly what I want: Opts([--rewrite] . Usage itself is private to the module, so I can't really work around this.

I understand why making Usage private to the module would help hide implementation details, but some mechanism of printing --rewrite seems generally useful to the end-user

Performance issue

I'm running into a performance issue. My app has many options(20 ~ 30) and it takes about 1 minute to parse arguments. It seems the performance is getting worse exponentially according to the number of options. The following is minimal repro:

object App {
  import cats.syntax.apply._

  def main(args: Array[String]): Unit = {
    val N = 20
    val command = Command("demo", "") {
      (1 to N).map { i =>
        Opts.option[String](s"opt$i", "").withDefault(s"$i")
      }.reduce { (a, b) => (a, b).mapN(_ + _) }.map(println(_))
    }

    while (true) {
      println("Start")
      val s = System.currentTimeMillis()
      command.parse(args) match {
        case Left(help) => println(help)
        case Right(_) =>
      }
      val e = System.currentTimeMillis()
      println(s"Finished took ${e - s}[ms]")
    }
  }
}

Output (N=18):

Start
123456789101112131415161718
Finished took 1175[ms]
Start
123456789101112131415161718
Finished took 222[ms]
Start
123456789101112131415161718
Finished took 324[ms]
Start
123456789101112131415161718
Finished took 220[ms]
...

Output(N=19):

Start
12345678910111213141516171819
Finished took 1262[ms]
Start
12345678910111213141516171819
Finished took 553[ms]
Start
12345678910111213141516171819
Finished took 548[ms]
Start
12345678910111213141516171819
Finished took 560[ms]
...

Output(N=20):

Start
1234567891011121314151617181920
Finished took 2361[ms]
Start
1234567891011121314151617181920
Finished took 1488[ms]
Start
1234567891011121314151617181920
Finished took 1088[ms]
Start
1234567891011121314151617181920
Finished took 849[ms]
...

Any thoughts?

Preserve more structure in Help

This idea originated from #126

Basically, if the structure of the error is preserved, it'll be possible to write a custom render function for Help. This can have uses such as for localization and different render formats (in my particular case, "compact" mode which renders with fewer newlines).

set up maven central publishing

I'd like to use decline from some bazel projects, and I don't want to have to figure out bintray support.

Any chance we can just do maven central?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.