Git Product home page Git Product logo

case-app's People

Contributors

adpi2 avatar alexarchambault avatar atry avatar battermann avatar bluesheeptoken avatar danxmoran avatar densh avatar dependabot[bot] avatar gaborbarna avatar gedochao avatar gitter-badger avatar joan38 avatar jorokr21 avatar larsrh avatar laurencewarne avatar lavrov avatar lwronski avatar maciejg604 avatar natsukagami avatar nicolasrouquette avatar nightscape avatar olafurpg avatar regadas avatar ryan-williams avatar scala-steward avatar slakah avatar tkroman avatar xuwei-k avatar yannmoisan 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  avatar  avatar  avatar  avatar  avatar  avatar

case-app's Issues

app should exit with non-zero exit code and usage/help text when no argument provided

If you run following example without providing any arguments, app exits with zero exit code without help/usage

sealed trait DemoCommand
case class First(foo: Int, bar: String) extends DemoCommand
case class Second(baz: Double) extends DemoCommand

object MyApp extends CommandApp[DemoCommand] {
  def run(command: DemoCommand, args: RemainingArgs): Unit = {}
}

Customizable CommandParser

The current ways to construct a CommandParser are fairly limited. It would be cool to have something like this:

CommandParser.fromRecord(
  "build" ->> BuildCommand ::
  "check" ->> CheckCommand ::
  HNil
)

I'm a little fuzzy on the details, though. Ideally, this should support common and command-specific options, e.g.

cli-app --config /home/lars/.local/cli-app.conf build --session HOL

Maybe something like this?

CommandParser.fromRecord[Common] { common =>
  "build" ->> BuildCommand(common) ::
  "check" ->> CheckCommand(common) ::
  HNil
}

... where the return type of *Command is some Parser[T].

shapeless 2.3 causes "could not find implicit value" compilation error

The automatic Parser derivation doesn't seem to work with shapeless 2.3. See this ammonite session

$ amm
@ load.ivy("com.chuusai" %% "shapeless" % "2.3.1")

@ load.ivy("com.github.alexarchambault" %% "case-app" % "1.0.0-RC3")

@ import caseapp._
import caseapp._
@ case class HasStr(str: String)
defined class HasStr
@ CaseApp.parse[HasStr](Seq("--str", "banana"))
cmd4.sc:1: could not find implicit value for evidence parameter of type caseapp.Parser[$sess.cmd3.HasStr]
val res4 = CaseApp.parse[HasStr](Seq("--str", "banana"))
                                ^
Compilation Failed
@

If I remove the first line that loads shapeless 2.3 the compilation error goes away. My project transitively pulled in shapeless 2.3 via another dependency.

Scala Duration support

Hi,

Would you be open for me to open a PR to support scala.concurrent.duration.{Duration, FiniteDuration}?
As in:

  implicit val durationParser: ArgParser[Duration] =
    SimpleArgParser.from[Duration]("Duration")(s =>
      Either.catchOnly[NumberFormatException](Duration(s)).leftMap(e => MalformedValue("Duration", e.getMessage))
    )

  implicit val finiteDurationParser: ArgParser[FiniteDuration] =
    SimpleArgParser.from[FiniteDuration]("FiniteDuration")(s =>
      Either
        .catchOnly[NumberFormatException](Duration(s))
        .leftMap(e => MalformedValue("FiniteDuration", e.getMessage))
        .flatMap {
          case finite: FiniteDuration => Right(finite)
          case _ => Left(MalformedValue("FiniteDuration", s"Expected finite duration but found infinite duration: $s"))
        }
    )

Let me know
Thanks

Note about non litteral in annotations

Annotations (@ExtraName, @AppName, @AppVersion, ...) expect litteral arguments. They'll likely crash with variable arguments.

Add a note about that in doc.

defaultDescription behaviour has changed between 1.2.0 and 1.2.0-M2

With version 1.2.0, it seems that defaultDescription are always set to None

Here is a small example to reproduce the bug : https://scastie.scala-lang.org/YgickCTpTkWDK7xpoVTOJw

Arg(i,List(Name(i)),None,None,false,false,int,None)
Arg(s,List(Name(s)),None,None,false,false,string,None)
Arg(b,List(Name(b)),None,None,false,true,bool,None)
Arg(li,List(Name(li)),None,None,false,false,int*,None)

And it works with version 1.2.0-M2

Arg(i,List(Name(i)),None,None,false,false,int,Some(2))
Arg(s,List(Name(s)),None,None,false,false,string,Some("defaultt"))
Arg(b,List(Name(b)),None,None,false,true,bool,Some(false))
Arg(li,List(Name(li)),None,None,false,false,int*,Some(List()))

Support for custom option names

Hi @alexarchambault!

I'm just wondering if adding support for other types of option names (i.e camel case --camelCase) is this something that could be added to case-app or if you want to keep it opinionated and only support --camel-case.

Tagged types not practical

They have to be unwrapped (...), to get the relevant value (the Int out of verbose: Int @@ Counter say). Add a note about it in README, and/or remove them in next version (replacing them with a real class Counter, like verbose: Counter).

Missing options are reported in camel case, when they should be hyphenated

Using caseapp 1.2.0, and a slight tweak from the example code in the README:

import caseapp._

case class ExampleOptions(
  theBar: Int
)

object Example extends CaseApp[ExampleOptions] {

  def run(options: ExampleOptions, arg: RemainingArgs): Unit = ()

}

If you run without specifying --the-bar, you see:

$ sbt -error run
[error] Required option --theBar not specified

The error message should display --the-bar, not --theBar. This can cause some major confusion:

$ sbt -error run --theBar 0
[error] Required option --theBar not specified

Code with traits does not work - missing implicits

Hi

I'm trying to abstract over parsing arguments and preparing general class skeleton, but this code does not compile and it shows implicits missing:

import caseapp._
import caseapp.core.help.Help

case class Arguments(foo: Int, bar: String)

object MyApp extends AbstractApp[Arguments] {
  run[Arguments]()
}

trait AbstractApp[T] extends App {
  def run[T: Parser: Help]() = {
    val parsedArgs = CaseApp.parse[T](args)
    println(parsedArgs)
  }
}

How can I make this work?

Also where does those implicits come from when used in just a single file?

Enhance doc

Make it more readable. (Generated, à la scalatex?)

Note about the different versions (which shapeless version they depend on, ...)

HelpMessage annotated on Command does not get printed with --help

In following example, help message annotated against Start command is not printed anywhere when we run either app --help or app start --help command.

sealed trait Command 

@CommandName("start")
@HelpMessage("Starts all the services when no option is provided")
case class Start(
@HelpMessage("Start Config Service")
 config: Boolean = false
@HelpMessage("Start Event Service")
 event: Boolean = false
) extends Command

Following help gets printed when I run app start --help

Command: start
Usage: app start
   --config  <bool>
         start config server
   --event  <bool>
         start event server

could not find implicit value for evidence parameter of type caseapp.core.parser.Parser[T]

I can't get the Commands work for the CaseApp.parse[T] method. The compiler can't find the implicit Parser.

What am I missing?

import caseapp._

sealed trait DemoCommand
case class First() extends DemoCommand
case class Second() extends DemoCommand

object MyApp extends App{
  CaseApp.parse[DemoCommand](List())
}

could not find implicit value for evidence parameter of type caseapp.core.parser.Parser[DemoCommand]

Reverse Parse

Is it be possible to take a case class and turn it into a List [String] that would generate the given case class?

Switch build to Mill

Currently having issues with publishing on the CI. sbt-ci-release and sbt-pgp don't really allow for fine grained customization of gpg-related things (passing custom gpg options, etc.). We should switch the build here to Mill, and use the same setup as many of my other projects (setup that must itself be based on the Ammonite's one IIRC), not using mill-ci-release, that doesn't allow for customization / tweakings of gpg things either. This particular setup, with secrets uploaded via touch Foo.scala && scala-cli publish setup --ci Foo.scala has never failed me.

Lower camel case annotations

Maybe I'm a bit picky, but the Scala style guide recommends annotation names to be in lower camel case like @appName. Unless there are a certain reason, how about following the guide?

One concern is source compatibility as I'm not sure if we can deprecate an annotation itself smoothly.

Duplicate arguments are reported as "???"

Stumbled across this while working on fixing #70. The SimpleArgParser reports any duplicate arguments it finds as "???", instead of the arg name (here's a test that asserts on the behavior).

It seems like the SimpleArgParser doesn't have any knowledge of the names attached to the values it consumes, so I'm wondering if the responsibility for checking duplicate names should be moved to some other enclosing parser.

Better help message

The help messages generated by case-app are currently rather raw. All the options are printed, without any grouping of related options, with no reflow of text depending on the terminal width, no colors, etc. For example:

$ cs launch --help
Command: launch
Usage: cs launch <org:name:version|app-name[:version]*>
  --main-class | -M | --main  <string>
  --extra-jars  <string*>
        Extra JARs to be added to the classpath of the launched application. Directories accepted too.
  --property | -D  <key=value>
        Set Java properties before launching the app
  --fork  <bool?>

(there's approx. ~70 options like this in this command!)

scopt or picocli could be used as sources of inspiration (there's probably many other libraries worth a look too…)

About implementing that, colors and reflow should just be a matter of changing some methods in Help.

Grouping arguments is more tricky I think, and requires some changes in the typelevel core of case-app. In more detail, I believe it should be possible to group arguments, by

We should then adjust RecursiveConsParser.args, so that it prepends its group (if it has one) to each of the Args it returns.

Lastly, in Help, we can then use the Arg.group of its args to group / show arguments however we want in the help message generated there. I didn't actually try to implement it, hopefully I didn't miss blockers along the way.

Users would then be able to group arguments like

case class Group1(arg: String = "", n: Int = 0)
case class Group2(other: String = "", m: Int = 0)

case class Options(
  @Recurse("Group 1")
    group1: Group1 = Group1(),
  @Recurse("Group 2")
    group2: Group2 = Group2()
)

Annotations not found for CommandApp

It seems that caseapp can't find annotations on commands.

When running the following command in sbt:

sbt:case-app-root> testsJVM/test:runMain caseapp.demo.CommandAppTest --help

I expect to see:

Demo
Usage: demo-cli [options] [command] [command-options]

Available commands: first, second

Type  demo-cli command --help  for help on an individual command

Actual result:

None.type
Usage: none.type [options] [command] [command-options]

Available commands: first, second

Type  none.type command --help  for help on an individual command

No implicits found for parameter eveidence$1: Parser[Options]

case class Options(length: Option[Int] = Some(10),
                   interval: Option[Int] = Some(3),
                   parallelRequests: Option[Int] = Some(4096))


import caseapp._

val options = CaseApp.parse[Options](args)

This gives a compile time error: No implicits found for parameter eveidence$1: Parser[Options]

Screenshot 2019-07-05 at 11 33 11 AM

Custom types / nested case classes conflict

When using a custom case class type, like

case class Custom(...)

in an argument case class, like:

case class Options(
  custom: Custom
)

// Then used like
CaseApp.parse[Options](args)

it can have two meanings:

  • either it's a custom type with an implicit ArgParser[Custom] available,
  • or it should be seen as nesting option definitions, that is its fields should be interpreted as options (or as other nested case classes, recursively).

A possible implicit ArgParser[Custom] put at the wrong place, or given the wrong type, should lead to a compilation error. But it doesn't because of the latter case acting as a fallback.

The two cases could be disambiguated by enforcing nested options to be annotated, like

case class Options(
  @nested extraOptions: ExtraOptions
)

This way, there would be no ambiguity between the two (either the field is annotated with @nested, or we look for an implicit ArgParser[...]).

Commands

Add support for commands

Should likely amount to have CaseApp.parse[...] accept an ADT base type as argument, e.g.

sealed trait Commands
case class Meh(options...) extends Commands
case class Neh(options...) extends Commands

CaseApp.parse[Commands]

The resulting Commands in the return type would either be a Meh or a Neh, depending on the command found.

Commands could also be replaced by a (shapeless) union type, like

CaseApp.parse[Union.`'meh -> Meh, 'neh -> Neh`.T]

A convenient way to specify options accepted when no command is specified has to be found, though.

zsh completion on a new shell

Very tiny issue with zsh completion.

I'm not sure what typeset -A opt_args does here

def script(progName: String): String =
s"""#compdef _$progName $progName
|typeset -A opt_args
|
|function _$progName {
| eval "$$($progName complete $id $$CURRENT $$words[@])"
|}
|""".stripMargin

but with it, on a new shell, I always need to hit tab twice for the first completion to work, e.g. try scala-cli re| on a new shell, but after that everything works normally. After taking out that line the first completion on a new shell works on the first tab. Tested with scala-cli on Arch Linux and Mac OS (intel), zsh 5.9.

Help output with custom parser

Using a custom parser in a command leads to an error when called with --help (if any (custom) required argument is not passed):

Main.scala:

sealed trait UtilsCommand extends Command

case class Test(file: java.io.File) extends UtilsCommand {
  println(file)
}

object Main extends CommandAppOf[UtilsCommand]

someThingInScope.scala:

implicit val clJFileParser: ArgParser[java.io.File] =
    ArgParser.instance[java.io.File] { s =>
      Try(new java.io.File(s)) match {
        case Success(x) if x.exists() => Right(x)
        case Success(x) => Left(s"$s does not exist!")
        case _ => Left(s"$s is not a valid file?")
      }
    }

command line:

java -jar .\utils.jar test --help
Required option file / List(Name(file)) not specified      // In HListParser::get ?

shapeless for Scala > 2.10 no longer depends on macro-compat

As of this commit shapeless no longer depends on macro-compat for Scala > 2.10, instead it provides a local definition of macrocompat.bundle to preserve source compatibility for later Scala versions.

Unfortunately this interferes with case-app's extension of CaseClassMacros in AnnotationListMacros, as discovered by @SethTisue in the 2.12.x community build.

I suggest making a similar change here: provide a local definition of macrocompat.bundle which is only used for Scala > 2.10.

At some point in the next year or two shapeless will drop 2.10 support, at which point the macro-compat dependency will go away altogether. If case-app drops 2.10 support now it could immediately drop the macro-compat dependency which would also solve this issue.

Only one missing option is reported at a time

Using caseapp 1.2.0, if you have:

import caseapp._

case class ExampleOptions(
  foo: String,
  bar: Int
)

object Example extends CaseApp[ExampleOptions] {
  def run(options: ExampleOptions, arg: RemainingArgs): Unit = {
    println(options)
    println(arg)
  }
}

If you run without arguments, you see:

$ sbt -error run
[error] Required option --foo not specified

Which is correct, but it'd be awesome if both --foo and --bar were reported missing.

Positional arguments, parsing scaladocs

I love this library, thank you!

Some things I would like to see that afaict it doesn't have now:

  • positional arguments (likely an annotation with an integer index)
  • parse scaladocs for each field of an arguments case class for the help-string
  • automatically recurse parsing (like the @Recurse annotation apparently does); ideally that would just happen automatically, but could be configured to e.g. prepend a string to the front of all the fields in the nested class, or just flatten them into the top-level class's argument/field namepace, etc.

I may try to dig in to this when I have a moment, will post any notable updates here

boolean handling is confusing

If an option type is boolean, and a default value of true is provided, there doesn't seem to be a way to set it to false on the command line:

[info] Starting scala interpreter...
Welcome to Scala 2.13.3 (Java HotSpot(TM) 64-Bit Server VM, Java 11.0.4).
Type in expressions for evaluation. Or try :help.

scala> import caseapp._
import caseapp._

scala> case class Options(enableFoo: Boolean = false)
class Options

scala> CaseApp.parse[Options](Seq())
val res0: Either[caseapp.core.Error,(Options, Seq[String])] = Right((Options(false),List()))

scala> CaseApp.parse[Options](Seq("--enable-foo", "true"))
val res2: Either[caseapp.core.Error,(Options, Seq[String])] = Right((Options(true),List(true)))

scala> CaseApp.parse[Options](Seq("--enable-foo", "false"))
val res3: Either[caseapp.core.Error,(Options, Seq[String])] = Right((Options(true),List(false)))

scala> case class Options2(enableFoo: Boolean = true)
class Options2

scala> CaseApp.parse[Options2](Seq("--enable-foo", "false"))
val res5: Either[caseapp.core.Error,(Options2, Seq[String])] = Right((Options2(true),List(false)))

scala> CaseApp.parse[Options2](Seq())
val res6: Either[caseapp.core.Error,(Options2, Seq[String])] = Right((Options2(true),List()))

For the case of res3 above, I would expect to be able to explicitly pass false, although I can get a false value by not providing the argument at all (e.g. res0). Unexpectedly --enable-foo false sets the value to true, because "false" is treated as an extra argument.

For the case of the default true value (Options2), there doesn't seem to be any command line that can set it to false.

Question @Recurse with context/prefix

I have the following common options

case class CommonOptions(
    prod: Boolean = false,
    dry: Boolean = false,
    @Recurse slack: SlackOptions
)

with slack options defined with

case class SlackOptions(
    slackToken: Option[String],
    slackChannel: Option[String]
)

I would like to be able to define my case class like this

case class SlackOptions(
    token: Option[String],
    channel: Option[String]
)

But I want to keep context informations when I use command line

--slack-token=... --slack-channel=...

It should be great to have one annotation "@PrefixRecurse" (or similar) that uses the name used in parent in command line

--slack.token=... --slack.channel=...

Maybe do you have some tricks to do that? Using "ExtraName" is for the moment the best solution I found but I will have conflict If I have same keys in different context

case class SlackOptions(
    @ExtraName("slack-token")
    token: Option[String],
    @ExtraName("slack-channel")
    channel: Option[String]
)

Trying to get desired behavior

Here is a short test program:

import caseapp._

case class EwApiDemoOptions (
  @ExtraName("l") displaySpeciesList: Boolean = false,
  @ExtraName("s") displaySpecies: String = ""
) extends App {
  if (displaySpeciesList) {
    println("speciesList")
  } else if (displaySpecies.nonEmpty) {
    println("displaySpecies")
  } else if (!displaySpeciesList && displaySpecies.isEmpty)
    Console.err.println(CaseApp.helpMessage[EwApiDemoOptions])
}

object EwApiDemo extends AppOf[EwApiDemoOptions]
  1. Calling with the -Xlint option enables the check for delayedinit-select, which causes this warning to appear several times: Selecting value displaySpeciesList from class EwApiDemoOptions, which extends scala.DelayedInit, is likely to yield an uninitialized value. Yes, I can specify:
scalacOptions ++= Seq(
  "-Xlint",
  "-Xlint:-delayedinit-select"
)

But that turns off the warnings for any other delayed init issues that may exist. Is there another way to structure the program to avoid delayed initialization entirely?

  1. Invoking a option that requires an argument, but failing to provide a value for the argument, should cause an error message to be generated and the program to stop. Instead, the feeble argument missing message is displayed and then an ugly exception is thrown:
$ sbt "runMain EwApiDemo2 -s"
... lots of output ...
argument missing

Exception: sbt.TrapExitSecurityException thrown from the UncaughtExceptionHandler in thread "run-main-0"
java.lang.RuntimeException: Nonzero exit code: 1
        at scala.sys.package$.error(package.scala:27)
[trace] Stack trace suppressed: run last compile:runMain for the full output.
[error] (compile:runMain) Nonzero exit code: 1
[error] Total time: 1 s, completed Mar 1, 2017 5:43:30 PM

Users should not see the exception. Instead, they should politely be told what happened, and what they need to do to correct the problem. A better output would be:

Error: the -s/--displaySpecies option requires an argument, but none was supplied. Please retry with a value for the option.

Until this is fixed, how might I catch the exception?

implicit resolution errors while instantiating a Parser with `Parser.generic`

In our codebase, we instantiate a lot of CaseApps.
With a profiling, we noticed we instantiate a lot of time the same Parsers.

Hence, we would like to cache them for the implicit resolution. However I do not successfully instantiate one. Here is a MVE:

import caseapp._

object Main {
  final case class Foo(foo: Int)

  implicit val parserFoo: Parser[Foo] = Parser.generic
}

I get the error could not find implicit value for parameter lowPriority: caseapp.util.LowPriority.

I guess I am missing some imports, am I right?

Which imports could help?

Is there a simpler way to generate a Parser? (I see that the API changes in 2.1.0-M8)

I am using:

  • case-app: 2.0.6
  • scala: 2.12.10

Help does not display if arguments are invalid or not supplied

This perplexes me; help will not run if required arguments are not supplied:

> run --help
[info] Running io.enoble.svg2d.Main --help
Required option input / List(Name(input), Name(i)) not specified

Is this not the reason why help is... helpful? ;)

Completions that contain single quotes are broken in zsh

$ scala-cli e
(eval):5: unmatched '
(eval):5: unmatched '
(eval):5: unmatched '

The problem seems to be with those escaped ' characters:

$ scala-cli complete zsh-v1 "1" "e"
local -a args306746944
args306746944=(
'export:The \'export\' sub-command is experimental.'
)
_describe command args306746944

Without the backslashes the completions work, but the quotes simply aren't printed. I'm not sure if there's any way to output single quotes succesfully...

Feature request: more automatic documentation for flags in --help

Just some fancy features that I think would be cool to have. It would be nice if the --help message could

  • show the default values for options with default values.
  • mark non-optional values as required.
  • show <type> of the flag instead of "<value>". For example, <bool> for Boolean or <int*> for List[Int].

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.