Git Product home page Git Product logo

mainargs's People

Contributors

ckipp01 avatar dependabot[bot] avatar lefou avatar lihaoyi avatar lolgab avatar mpollmeier avatar scala-steward avatar thiloplanz 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

mainargs's Issues

Leftover args not parsed as expected

I'm using mainargs in ammonite, but the result is not correct.

code

run in ammonite 2.5.8/scala 3.2.1/java 17

// filename: bug.sc
import mainargs.Leftover
@main
def mycmd(@arg(name = "the-flag") f: mainargs.Flag = mainargs.Flag(false), @arg str: String = "s", args: Leftover[String]) = {
  println(f)
  println(str)
  println(args)
}

case 1

amm bug.sc --str str a b c d

expected:

Flag(false)
str
Leftover(List(a, b, c, d))

actual:

Flag(true)
str
Leftover(List(b, c, d))

case 2

amm bug.sc a b c d

expected:

 Flag(false)
 s
 Leftover(List(a, b, c, d))

actual:

 Flag(true)
 b
 Leftover(List(c, d))

Support for nested subcommands

Is it possible to somehow nest subcommands with mainargs?
I'd like to achieve something like this:

program foo bar --some --arguments
program foo baz --other --args

Add new documentation annotation for program level help text

Please add new documentation annotation for program level help text. Place for this text could be before "Available subcommands:" so at the very top or at the bottom or both depending on the size of the program. If just one place accepted then I prefer to place it at the bottom.

Annotations could be e.g. @begindoc, @enddoc

Program level help text should be visible when user runs program --help -command.

Reason: Many larger programs contain multiple subcommands and are part of a longer task process. A subcommand help contains instructions how to run that subcommand but program level documentation can help the user to understand the longer process:
What subcommands are run in what order, describle one or more simple workflows how to use subcommands to manage the whole task process from begin to end.

Program level help text can also be used for any other information common to all / multiple subcommands, where to find more information, author of the program, copyright etc.

Support main methods from traits

My Main object has grown too large and I want to split it. I tried to create a trait for each entry point and then making the Main object extends all those traits. But in such case, mainargs does not find any of the @main annotated entry points in any of the traits, while it worked flawlessly when all those methods were together in the same Main object.

trait CommandList {
  @main
  def list(@arg v: String): Unit = ???
}

trait CommandCopy {
  @main
  def copy(@arg from: String, @arg to: String): Unit = ???
}

object Main extends CommandList with CommandCopy {
  def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args)
}

Is there any way to support this use case? Without having to duplicate the entry point in all of them, of course.

support kebab-case options

For example

import mainargs.{main, ParserForMethods}

object Main {
  @main def `opt-for-18+`(): Unit = {}

  def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args)
}

when run Main.scala opt-for-18+ it fails

Unknown argument: "opt-for-18+"
Expected Signature: opt$minusfor$minus18$plus

those opt$minusfor$minus18$plus are to be unescaped, at least $minus and $plus

It was fixed in PR #6 which seems to be deleted by the author
(archived at http://archive.today/2021.02.21-032145/https://github.com/lihaoyi/mainargs/pull/6/files)

Unable to parse a positional argument value of `-`

I have a simple function that takes two positional String parameters to indicate an input and output filename.

This works well, except for the case where the filename is - (which I would like to support to indicate STDIN/STDOUT).

It then fails with a parsing error of Unknown argument "-".

@main
  def copy(
            @arg(doc = "The file to be transferred, set to '-' to read data from console input")
            inputFile: String,
            @arg(doc = "The target location for the file, set to '-' to write to the console.")
            outputFile: String
          )

This can be called as copy a b (with positional args enabled), but it fails for copy a -

Missing argument: --outputFile <str>
Unknown argument: "-"
Expected Signature: copy
  --inputFile <str>   The file to be transferred, set to '-' to read data from console input
  --outputFile <str>  The target location for the file, set to '-' to write to the console.

Possible argument parsing error

I have simple experimental scala script using mainargs.

#!/usr/bin/env amm

import $ivy.`ch.qos.logback:logback-classic:1.2.3`
import $ivy.`ch.qos.logback:logback-core:1.2.3`
import $ivy.`ch.qos.logback:logback-access:1.2.3`
import $ivy.`org.slf4j:slf4j-api:1.7.25`
import $ivy.`com.lihaoyi::os-lib:0.2.5`
import $ivy.`com.lihaoyi::sourcecode:0.2.7`
import $ivy.`com.lihaoyi::mainargs:0.4.0`

import java.nio.file.Files
import org.slf4j.{Logger, LoggerFactory}
import ammonite.ops
import scala.util.Try
import java.io.File
import mainargs.{main, arg, ParserForMethods, ParserForClass, Flag}

val logConfFile = new File("/home/jk/bin/logback.xml")
System.setProperty("logback.configurationFile", logConfFile.getCanonicalPath)
val log = LoggerFactory.getLogger(s"${sourcecode.File()}")
if (logConfFile.exists() == false) log.error(s"Log config file not found: ${logConfFile.getCanonicalPath}")
else log.debug(s"Using logback config: ${logConfFile.getCanonicalPath}")


@main(doc = "Do foo." )
def foo(): Unit = {
  log.debug(s"${sourcecode.Name()}(${sourcecode.Args()}) running...")
  log.debug(s"END")
}

@main(doc = "Print stuff into file. " )
def template(
           @arg(short = 'p', doc = "This is p-flag. ") pflag: Flag,
           @arg(short = 'h', doc = "This is h-flag. ") hflag: Flag,
           @arg(short = 'j', doc = "This is j-flag. ") jflag: Flag,
           @arg(short = 'f', doc = "Output file name. ") file: String = null,
         ): Unit = {
  log.debug(s"${sourcecode.Name()}(${sourcecode.Args()}) running...")
  log.debug(s"pflag: ${pflag.value}")
  log.debug(s"hflag: ${hflag.value}")
  log.debug(s"jflag: ${jflag.value}")
  log.debug(s"file: ${if (file != null) file else "<NULL>"}")
  log.debug(s"END")
}

When I run this with different arguments I got following results:

jk@t04:~/bin$ expmainargs.sc template 
2023-04-08_23:28:12.232 [main] DEBUG e.sc - Using logback config: /home/jk/bin/logback.xml
2023-04-08_23:28:12.260 [main] DEBUG e.sc - template(List(List(Text(Flag(false),pflag), Text(Flag(false),hflag), Text(Flag(false),jflag), Text(null,file)))) running...
2023-04-08_23:28:12.260 [main] DEBUG e.sc - pflag: false
2023-04-08_23:28:12.260 [main] DEBUG e.sc - hflag: false
2023-04-08_23:28:12.260 [main] DEBUG e.sc - jflag: false
2023-04-08_23:28:12.260 [main] DEBUG e.sc - file: <NULL>
2023-04-08_23:28:12.260 [main] DEBUG e.sc - END

This is expected result.

jk@t04:~/bin$ expmainargs.sc template -p 
2023-04-08_23:28:32.578 [main] DEBUG e.sc - Using logback config: /home/jk/bin/logback.xml
2023-04-08_23:28:32.606 [main] DEBUG e.sc - template(List(List(Text(Flag(true),pflag), Text(Flag(false),hflag), Text(Flag(false),jflag), Text(null,file)))) running...
2023-04-08_23:28:32.606 [main] DEBUG e.sc - pflag: true
2023-04-08_23:28:32.606 [main] DEBUG e.sc - hflag: false
2023-04-08_23:28:32.606 [main] DEBUG e.sc - jflag: false
2023-04-08_23:28:32.606 [main] DEBUG e.sc - file: <NULL>
2023-04-08_23:28:32.607 [main] DEBUG e.sc - END

This is expected result.

jk@t04:~/bin$ expmainargs.sc template -p foo
2023-04-08_23:28:39.022 [main] DEBUG e.sc - Using logback config: /home/jk/bin/logback.xml
2023-04-08_23:28:39.050 [main] DEBUG e.sc - template(List(List(Text(Flag(true),pflag), Text(Flag(true),hflag), Text(Flag(false),jflag), Text(null,file)))) running...
2023-04-08_23:28:39.050 [main] DEBUG e.sc - pflag: true
2023-04-08_23:28:39.050 [main] DEBUG e.sc - hflag: true
2023-04-08_23:28:39.050 [main] DEBUG e.sc - jflag: false
2023-04-08_23:28:39.050 [main] DEBUG e.sc - file: <NULL>
2023-04-08_23:28:39.050 [main] DEBUG e.sc - END

This is unexpected result. Why does the argument value "foo" set the hflag to true and file is still null?
I intuitively thought that the results would have been: pflag = true, hflag = false, jflag = false, file = "foo" but for some reason this was not the case.

jk@t04:~/bin$ expmainargs.sc template -p foo bar
2023-04-08_23:28:49.660 [main] DEBUG e.sc - Using logback config: /home/jk/bin/logback.xml
2023-04-08_23:28:49.688 [main] DEBUG e.sc - template(List(List(Text(Flag(true),pflag), Text(Flag(true),hflag), Text(Flag(true),jflag), Text(null,file)))) running...
2023-04-08_23:28:49.689 [main] DEBUG e.sc - pflag: true
2023-04-08_23:28:49.689 [main] DEBUG e.sc - hflag: true
2023-04-08_23:28:49.689 [main] DEBUG e.sc - jflag: true
2023-04-08_23:28:49.689 [main] DEBUG e.sc - file: <NULL>
2023-04-08_23:28:49.689 [main] DEBUG e.sc - END

This is also unexpected result. Why does the argument values "foo" and "bar" set the hflag and jflag to true?
I intuitively thought that this command line would have been rejected because there were 2 string arguments "foo" and "bar".

jk@t04:~/bin$ expmainargs.sc template -p foo bar baf
2023-04-08_23:28:59.663 [main] DEBUG e.sc - Using logback config: /home/jk/bin/logback.xml
2023-04-08_23:28:59.691 [main] DEBUG e.sc - template(List(List(Text(Flag(true),pflag), Text(Flag(true),hflag), Text(Flag(true),jflag), Text(baf,file)))) running...
2023-04-08_23:28:59.691 [main] DEBUG e.sc - pflag: true
2023-04-08_23:28:59.692 [main] DEBUG e.sc - hflag: true
2023-04-08_23:28:59.692 [main] DEBUG e.sc - jflag: true
2023-04-08_23:28:59.692 [main] DEBUG e.sc - file: baf
2023-04-08_23:28:59.692 [main] DEBUG e.sc - END

This is again unexpected result. Why does the argument values "foo" and "bar" set the hflag and jflag to true?
I intuitively thought that this command line would have been rejected because there were 3 string arguments "foo", "bar" and "baf".

jk@t04:~/bin$ expmainargs.sc template -p foo bar baf blaa
2023-04-08_23:29:18.136 [main] DEBUG e.sc - Using logback config: /home/jk/bin/logback.xml
Unknown argument: "blaa"
Expected Signature: template
Print stuff into file.
  -p --pflag       This is p-flag.
  -h --hflag       This is h-flag.
  -j --jflag       This is j-flag.
  -f --file <str>  Output file name.

This is expected result, too many arguments.

Is this an error in mainargs code or something missing from the user documentation or what is the problem?

Used versions:

mainargs:0.4.0

scala -version
Scala code runner version 2.12.8 -- Copyright 2002-2018, LAMP/EPFL and Lightbend, Inc.

java -version
openjdk version "11.0.18" 2023-01-17
OpenJDK Runtime Environment (build 11.0.18+10-post-Ubuntu-0ubuntu120.04.1)
OpenJDK 64-Bit Server VM (build 11.0.18+10-post-Ubuntu-0ubuntu120.04.1, mixed mode, sharing)

amm
Loading...
Welcome to the Ammonite Repl 2.4.0 (Scala 2.12.13 Java 11.0.18)

Thank you for your support!

Support default values in `case class`es in Scala 3

Currently, default values in case classes are not handled correctly in Scala 3.
Example:

object Main {
  @mainargs.main
  case class Config(bar: String = "bar")

  def main(args: Array[String]): Unit = {
    val config = mainargs.ParserForClass[Config].constructOrExit(args)
    println(config)
  }
}

Scala 2 prints:

Missing argument: --bar <str>
Expected Signature: apply
  --bar <str>

Scala 2 prints:

Config(bar)

Add support for Scala 3

Scala 3 is going to include at least a subset of mainargs functionality in the future. That is certainly a good piece of news. However, the schedule is not known and the content of this subset is also unknown.

Because of this, porting of existing Scala 2 apps using mainargs is difficult/impossible. If the Scala 3 subset of mainargs is very limited, then there is also a long term need for separate Scala 3 mainargs library.

Make default args snake-case

i see a lot of:

  @arg(name = "my-num", ...)
  myNum: Int = 2,

  @arg(name = "no-default-predef", ...)                                                                                                                                       
  noDefaultPredef: Flag,

  @arg(name = "predef-code", ...)                                                                                                                                             
  predefCode: String = "",   

clearly the preferred command-line arguments are snake-case, not camelCase. so why not make that the default (so by default convert case class camelCase parameters to cmdline snake-case arguments)? it would remove a lot of boilerplate i think. and if someone doesn't like it they can still override the names back to camelCase using @arg(name = "...")

Allow runtime values for argument and main annotations

Currently this is not possible because we use ClassfileAnnotation, which only allows constants. StaticAnnotation would in theory let us use expressions, but that doesn't work for annotating arguments while using named arguments in the annotation due to https://users.scala-lang.org/t/how-to-use-named-arguments-in-scala-user-defined-annotations/4163.

A workaround for this would be to explode the singular @arg annotation:

class arg(val name: String = null,
          val short: Char = 0,
          val doc: String = null,
          val noDefaultName: Boolean = false,
          val positional: Boolean = false) extends ClassfileAnnotation

Into a collection of single-value annotations:

class name(value: String) extends StaticAnnotation
class short(value: Char) extends StaticAnnotation
class doc(value: String) extends StaticAnnotation
class noDefaultName extends StaticAnnotation
class positional extends StaticAnnotation

Using this style, a user would be able to specify things about an argument without needing to use named arguments, allowing us to workaround both problems and provide non-constant runtime values as part of the annotation metadata

Allow hiding arguments from help

It is useful, when you want to deprecate a command line argument, to hide it from the instructions given by --help.
I'm trying to migrate an application using case-app and I'm missing this feature ( done with the @Hidden annotation in case-app

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.