com-lihaoyi / mainargs Goto Github PK
View Code? Open in Web Editor NEWA small, convenient, dependency-free library for command-line argument parsing in Scala
License: Other
A small, convenient, dependency-free library for command-line argument parsing in Scala
License: Other
for example
@main
def ooo(@arg(short = 'j') concurrency: Int=1, vals: Leftover[String]): Unit = {
}
should allow -j16
as -j 16
.
currently -j16
goes to leftover
I'm using mainargs in ammonite, but the result is not correct.
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)
}
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))
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))
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
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.
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.
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)
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.
Like Scallop choice options where we can define a list of default values to choose from.
This is what's supported by python argparse and gflags.
Thanks!
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!
Currently, default values in case class
es 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)
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.
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 = "...")
Currently, all cli options are listed in the order they where defined.
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
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.