lexicalscope / jewelcli Goto Github PK
View Code? Open in Web Editor NEWJewelCli uses an annotated interface definition to automatically parse and present command line arguments
License: Apache License 2.0
JewelCli uses an annotated interface definition to automatically parse and present command line arguments
License: Apache License 2.0
I've started implementing jewelcli and I like it so far, but I've ran into an issue.
[10:12:19] [Server thread/WARN]: java.lang.IllegalArgumentException: interface com.graywolf336.jail.command.commands.jewels.Jailing is not visible from class loader
[10:12:19] [Server thread/WARN]: at java.lang.reflect.Proxy.getProxyClass(Unknown Source)
[10:12:19] [Server thread/WARN]: at java.lang.reflect.Proxy.newProxyInstance(Unknown Source)
[10:12:19] [Server thread/WARN]: at com.lexicalscope.jewelcli.internal.fluentreflection.dynamicproxy.$FluentProxy.dynamicProxy(FluentProxy.java:23)
[10:12:19] [Server thread/WARN]: at com.lexicalscope.jewelcli.internal.fluentreflection.bean.$MapBean.bean(MapBean.java:36)
[10:12:19] [Server thread/WARN]: at com.lexicalscope.jewel.cli.InterfaceArgumentPresentingStrategy.presentArguments(InterfaceArgumentPresentingStrategy.java:33)
[10:12:19] [Server thread/WARN]: at com.lexicalscope.jewel.cli.ArgumentPresenterImpl.presentArguments(ArgumentPresenterImpl.java:63)
[10:12:19] [Server thread/WARN]: at com.lexicalscope.jewel.cli.AbstractCliImpl.parseArguments(AbstractCliImpl.java:42)
[10:12:19] [Server thread/WARN]: at com.lexicalscope.jewel.cli.CliFactory.parseArguments(CliFactory.java:67)
[10:12:19] [Server thread/WARN]: at com.graywolf336.jail.command.subcommands.JailCommand.execute(JailCommand.java:62)
[10:12:19] [Server thread/WARN]: at com.graywolf336.jail.command.JailHandler.parseCommand(JailHandler.java:124)
[10:12:19] [Server thread/WARN]: at com.graywolf336.jail.JailMain.onCommand(JailMain.java:121)
[10:12:19] [Server thread/WARN]: at org.bukkit.command.PluginCommand.execute(PluginCommand.java:44)
[10:12:19] [Server thread/WARN]: at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:196)
[10:12:19] [Server thread/WARN]: at org.bukkit.craftbukkit.v1_7_R1.CraftServer.dispatchCommand(CraftServer.java:546)
[10:12:19] [Server thread/WARN]: at org.bukkit.craftbukkit.v1_7_R1.CraftServer.dispatchServerCommand(CraftServer.java:533)
[10:12:19] [Server thread/WARN]: at net.minecraft.server.v1_7_R1.DedicatedServer.aw(DedicatedServer.java:309)
[10:12:19] [Server thread/WARN]: at net.minecraft.server.v1_7_R1.DedicatedServer.u(DedicatedServer.java:274)
[10:12:19] [Server thread/WARN]: at net.minecraft.server.v1_7_R1.MinecraftServer.t(MinecraftServer.java:540)
[10:12:19] [Server thread/WARN]: at net.minecraft.server.v1_7_R1.MinecraftServer.run(MinecraftServer.java:446)
[10:12:19] [Server thread/WARN]: at net.minecraft.server.v1_7_R1.ThreadServerApplication.run(SourceFile:617)
Any ideas on this as right now I'm clueless?
I would like to register a class to apply to the value of an option.
Implementations of the following interface would allow extremely flexible pre-validation without having to add this noise to your code and would allow better reuse of common validations.
public interface OptionValidator<T>
{
public boolean isValid(@Nullable final T value);
@Nonnull
public String getErrorMessage();
}
If you could point me to where this should be added in I can write it and submit a pull request, but the indirection is obfuscating where this would go.
If I try to define a ListSubclass with a single String parameter that does some parsing, I get a ClassCastException. Here's the sample code:
public static class MyList extends ArrayList<String> {
public MyList(String string) {
this.addAll(Arrays.asList(string.split(";;")));
}
}
public interface Options {
@Option
MyList getList();
}
public static void main(String[] args) {
Options options = CliFactory.parseArguments(Options.class, args);
System.err.println(options.getList());
}
And here's the exception:
Exception in thread "main" java.lang.ClassCastException: java.util.LinkedList cannot be cast to Bug$MyList
at $Proxy4.getList(Unknown Source)
at Bug.main(Bug.java:22)
Seems like maybe it's testing whether MyList is a subclass of List, when it really should be checking if MyList is a superclass of LinkedList. Also, since MyList has a constructor with a single String argument, shouldn't that just be used instead?
Trying to register the help command line option "/?" (which is common on the Windows platform) doesn't work. It silently gets translated to "-/". Is this a bug?
Is there an easy way to create one's own ArgumentValidationException
with a message? I miss something like
throw new ArgumentValidationException("Lorem ipsum");
It would help writing custom semantic validatiom.
The below code will throw NPE when accessing cli.f().
If I rename f() to getF(), everything works well.
interface CliOptions {
@Option(shortName = "f")
boolean f();
}
public static void main(String[] args) {
CliOptions cli = CliFactory.parseArguments(CliOptions.class, "-f");
System.out.println("cli.f() = " + cli.f());
}
It will be very helpful to provide enum option support and should not be hard to implement.
it would be useful if options with default values showed the default value in the help output
JewelCli 0.8.9 requires that the type of an option has a (String) constructor, but many value types do not meet this requirement.
Exception in thread "main" java.lang.ClassCastException: cannot convert class java.lang.String to interface java.nio.file.Path
at com.lexicalscope.jewel.cli.ConvertTypeOfObject.convertValueTo(ConvertTypeOfObject.java:159)
at com.lexicalscope.jewel.cli.ConvertTypeOfObject.convert(ConvertTypeOfObject.java:98)
at com.lexicalscope.jewel.cli.ArgumentPresenterImpl.putValuesInMap(ArgumentPresenterImpl.java:79)
at com.lexicalscope.jewel.cli.ArgumentPresenterImpl.presentArguments(ArgumentPresenterImpl.java:40)
at com.lexicalscope.jewel.cli.AbstractCliImpl.parseArguments(AbstractCliImpl.java:42)
The workaround is to build an intermediate type that parses a String into the desired type, but since JewelCli also does not support Java 8 default methods (#38), this cannot be done transparently.
Other option libraries such as jopt-simple (see withValuesConvertedBy) allow a factory method to be registered in case a constructor is not available or is otherwise undesirable.
Here's two different ways to do this:
Add a parser attribute to @Option
that accepts a Class<? extends Function<String, ?>>
. The main downside of this approach is that, as far as I know, we cannot guarantee the function return type matches the annotated method's return type at compile time. :( It can also cause some duplication if there are multiple arguments of the same type, but on the other hand it has the flexibility of allowing different parsers (or validators) for each argument as well.
Add a method to either Cli or CliFactory to register a parser, e.g. <T> Cli<O> withParser(Class<T>, Function<String, T>)
. The downside of this is that it is not aligned with the rest of JewelCli's annotation-centric API.
I think the first approach is more desirable since it maintains API consistency and option parsing tends to happen very early in the application, lessening the pain of errors at runtime.
I have a problem that appears to have come along in moving between the
old package 0.58 to the new one, 0.8.1. The issue is that in the old
version, an Option which did not follow the JavaBeans naming
convention for getters was processed, while in the new one it is
ignored and an exception will be thrown when one tries to use it.
Example: I have a simple interface
public interface Options {
@option String name();
}
The I parse the args
options = CliFactory.parseArguments(Options.class, args);
System.out.println("name: " + options.name())
I will get an exception
Exception in thread "main" java.lang.UnsupportedOperationException: no
implemention found for method public abstract java.lang.String
com.test.Main$Options.name()
at com.lexicalscope.jewelcli.internal.fluentreflection.dynamicproxy.
$Implementing.invoke(Implementing.java:165)
at $Proxy4.name(Unknown Source)
I understand why this happens, but perhaps there could be an exception
thrown during parsing? For example, if a method marked @option does
not follow the JavaBeans naming convention, throw an exception.
This has bitten me a couple of times in the last few months as I am
still using the 0.58 on some old projects. The exception message is
not helpful - I thought there was a problem with a proxying library or
dependency conflict.
Thanks!
Patrick
I think Java 8 default methods would be a great way to convert or enrich parsed arguments. For example, I tried the following to convert a String to a Path:
public interface Args {
@Option(shortName = "d")
String directory();
default Path directoryPath() {
return FileSystems.getDefault().getPath(directory());
}
}
Unfortunately, jewelcli does not seem to support default methods. When calling directoryPath()
, I get null
.
Without having looked at the code, I feel like this should be easy to implement. All that needs to be done it to not override/implement default methods in proxies.
Hello,
I saw ticket #4 where a boolean is used to get a certain enum value.
It would be nice to have an option to specify one string value of an enum and get the typed Enum value from the interface.
I have worked around this by doing:
@option(shortName = "r", description = "value should be either: 'stopYYYStartXXX' or 'stopXXXstartYYY'")
ScenarioEnumWrapper getScenario();
public class ScenarioEnumWrapper extends EnumWrapper<Scenario> {
public ScenarioEnumWrapper(final String value) {
super(value);
}
}
and then:
public abstract class EnumWrapper<T extends Enum<?>> {
private final T enumValue;
@SuppressWarnings({ "unchecked", "rawtypes" })
public EnumWrapper(final String value) {
final List<Class<?>> classes = getTypeArguments(EnumWrapper.class, this.getClass());
enumValue = (T) Enum.valueOf((Class) classes.get(0), value);
}
public T getValue() {
return enumValue;
}
}
where the getTypeArguments is a whole lot of reflection magic going on to extract the value from T, it works, yet it would be nice if it could be hidden more inside jewelcli, so that I no longer have to get the ScenarioEnumWrapper I could just get the Scenario from a commandline value. It should even be easier to implement inside your proxy since you have the GenericReturnType of the Method in question all you need to do is return Enum.getValue(genericReturnType.class, incomingValue); and you don't need any special reflection tricks.
I might look into writing a patch.
Kris
Not available LICENSE file in source directory structure
Please. Added license and copyright notice.
the fedora pakaging guideline is very strictly precise about this problem
https://fedoraproject.org/wiki/Packaging:LicensingGuidelines?rd=Packaging/LicensingGuidelines#License_Text
thanks
regards
should be formatted like [optional] option
It appears the default option ordering, OptionOrder.LEXICOGRAPHIC
, sorts options based on the bean-style name of the method rather than the longName
field of the Option
annotation.
For example:
@CommandLineInterface(application="test")
public interface Test {
@Option(longName="a")
boolean getZ();
@Option(longName="z")
boolean getA();
}
Associated help message:
Usage: test [options]
[--z]
[--a]
boolean isName() does not seem to work as expected: trying to make an optional switch that takes an optional value. Leaving the value off throws an ArgumentValidationException. See testOptionalPresentNoValue in this Junit test.
import static org.junit.Assert.*;
import org.junit.Test;
import com.lexicalscope.jewel.cli.*;
public class JewelParseTest {
public interface TestOption {
@Option
String getName();
boolean isName();
}
@Test
public void testOptionalMissing() throws ArgumentValidationException {
final TestOption rv = CliFactory.parseArguments(TestOption.class);
System.out.println(rv.toString());
assertFalse(rv.isName());
assertNull(rv.getName());
}
@Test
public void testOptionalPresentNoValue()
throws ArgumentValidationException {
final TestOption rv = CliFactory.parseArguments(TestOption.class, "--name");
System.out.println(rv.toString());
assertTrue(rv.isName());
assertNull(rv.getName());
}
@Test
public void testOptionalPresentWithValue() throws ArgumentValidationException {
final TestOption rv = CliFactory.parseArguments(TestOption.class, "--name",
"value");
System.out.println(rv.toString());
assertTrue(rv.isName());
assertEquals(rv.getName(), "value");
}
}
In my project I need ability to pass negative numbers as option arguments, but unfortunately it doesn't work. It seems parser treats negative numbers as options themselves.
You can easily reproduce this by changing TestPrimitiveExample test (see "Option must have a value: --int value" failure)
--- a/jewelcli/src/test/java/com/lexicalscope/jewel/cli/examples/TestPrimitiveExample.java
+++ b/jewelcli/src/test/java/com/lexicalscope/jewel/cli/examples/TestPrimitiveExample.java
@@ -13,7 +13,7 @@ public class TestPrimitiveExample {
new String[] { "--boolean",
"--byte", "1",
"--short", "2",
- "--int", "3",
+ "--int", "-3",
"--long", "4",
"--float", "4.1",
"--double", "4.2",
use type literal support to allow parametrized interfaces
My program repeatedly in call the jewelcli's generated object in a loop. I found this will lead to memory leak. It seems to me that jewelcli creates something every time when the method is called, rather than cache the parsed value.
Here is jmap's output.
1: 7420760 653026880 java.lang.reflect.Method
2: 1679510 316381800 [B
3: 10261545 234341328 [Ljava.lang.Class;
4: 681145 186828472 [I
5: 5801706 185654592 java.util.AbstractList$Itr
6: 3092467 98958944 com.lexicalscope.jewelcli.internal.fluentreflection.$FluentClassImpl
7: 3092467 98958944 com.lexicalscope.jewelcli.internal.fluentreflection.$ReflectedMembersImpl
8: 3092467 98958944 com.lexicalscope.jewelcli.internal.fluentreflection.$ReflectedMethodsImpl
9: 3672323 88135752 com.lexicalscope.jewelcli.internal.guice.$TypeLiteral
10: 3672310 88135440 com.lexicalscope.jewelcli.internal.fluentreflection.$FluentAnnotatedImpl
11: 3092467 74219208 com.lexicalscope.jewelcli.internal.fluentreflection.$ReflectedFieldsImpl
12: 3092467 74219208 com.lexicalscope.jewelcli.internal.fluentreflection.$ReflectedSuperclassesAndInterfacesImpl
13: 3092467 74219208 com.lexicalscope.jewelcli.internal.fluentreflection.$ReflectedConstructorsImpl
for example:
@Option(shortName = "f", longName = "format", defaultValue = "csv", validValues = "csv,json,xml", description = "Output File Format")
public Set<String> getFormats();
If it also auto generated an addition to the description
like "Output File Format defaultValue=csv valid=[csv,json,xml]"
it would go a long way to adding more information with no effort.
Maven version: 3.3.9
"mvn site" fails with this error message:
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-site-plugin:3.0:site (default-site) on project jewelcli-parent: Execution default-site of goal org.apache.maven.plugins:maven-site-plugin:3.0:site failed: A required class was missing while executing org.apache.maven.plugins:maven-site-plugin:3.0:site: org/sonatype/aether/graph/DependencyFilter
This issue was fixed with maven-site-plugin 3.3.
How to fix: Update maven plugin versions in pom.xml.
Links:
its common to have "-" in options names, which is not allowed in java; replacing _ with - in option names might be a neat way of solving this issue; although it cause trouble for anyone using _ at the moment and make it harder to get _ to work (would have to specify an explicit option name string). But its so much more likely for people to want - in the option that it might be worth it.
This is really more of a question -- is it intended that Java 7 is required as of the 0.8.5 release? I noticed that it is compiled to target Java 7 since the major version is 51 in the JAR file. This makes it not load in Java 6.
Allow nested CLI definitions so options can be grouped depending on the mode of the application - the master option would control the availability of the slave options:
interface Top {
@option SlaveOne getSlaveOneOption();
boolean isSlaveOneOption();
@option SlaveTwo getSlaveTwoOption();
boolean isSlaveTwoOption();
}
interface SlaveOne {
@option getMandatorySlaveOneOption()
}
etc...
I have a suggestion for a slight usability improvement. The help functionality doesn't separate the long and short names of an option very well. Example:
[--port -p value] : Webserver port
The specification of --port and -p as alternatives looks somewhat confusing in my opinion. It kind of implies that you need to specify both to use the option. Perhaps the format could be changed to something like:
[-p/--port value]: Webserver port
or:
[-p value | --port value]: Webserver port
or:
[--port value]: Webserver port (short form: -p)
There are probably other good formats as well.
There is currently no way to support incremental integer parameter like for verbose levels
specified by multiple repeated -v arguments on command line
This means that -v -v -v will lead to getVerbose() to return 3
Any boolean (or Boolean) options are not working, it will always return true. The following code will not work.
@option
boolean getOption1()
@option(defaultValue="false")
boolean getOption2()
If I did not pass any argument, both methods will return true. Your test case(http://jewelcli.sourceforge.net/xref-test/uk/co/flamingpenguin/jewel/cli/examples/TestPrimitiveExample.html) only tests when the boolean option arguments are present, however, there should also be test cases testing if no arguments are passed.
When I throw an exception in a method that gets invoked by CliFactory.parseArgumentsUsingInstance()
I get a InvocationTargetRuntimeException
in the first place rather than my own exception.
I don't know if there are reasons against it, but I would expect to receive my own exception. That way I could do some argument validation in the methods and throw exceptions respectively.
Would be nice if it was possible to set some constraints on the result from @unparsed, such as "count=1" or "count > 0", maybe some more advanced things like patterns.
Thanks!
null is unhelpful and can lead to NPE, empty list is safer
It would be useful to be able to create the usage message without having a failed option parse.
My use case for this is that i have a number of unparsed arguments which must meet some moderately complex requirements (the first must be a URL, the second must be a method supported by the object referred to by that URL, etc), and if the user enters bad arguments, i would like to emit a usage message.
At the moment, i can say CliFactory.parseArguments(options.getClass(), "--help"), and handle the resulting exception as i would the exception from a failed option parse, but this is mildly icky.
Command line: interface --mode STATIC --family ipv4 --mask 255.255.0.0 --gateway 10.77.1.4 --address 10.77.111.179 eth0
Use case: eth0 must be the interface name
Help:
Usage: interface options
--address -a /\d{1,3}+.\d{1,3}+.\d{1,3}+.\d{1,3}+/ : interface ip address
--family -f value : ip address family <ipv4, ipv6>
--gateway -g /\d{1,3}+.\d{1,3}+.\d{1,3}+.\d{1,3}+/ : gateway ip address
--mode -m value : modes <STATIC, DHCP, LOOPBACK>
--netmask -n /\d{1,3}+.\d{1,3}+.\d{1,3}+.\d{1,3}+/ : ip address net mask
Java interface:
@commandlineinterface(application = "interface")
public interface InterfaceCommand {
@option(shortName = "m", description = "modes <STATIC, DHCP, LOOPBACK>")
InterfaceMode getMode();
@option(shortName = "f", description = "ip address family <ipv4, ipv6>", defaultValue = "ipv4")
IPAddressFamily getFamily();
@option(shortName = "n", description = "ip address net mask", pattern = "\d{1,3}+.\d{1,3}+.\d{1,3}+.\d{1,3}+", defaultValue = "255.255.255.0")
String getNetmask();
@option(shortName = "g", description = "gateway ip address", pattern = "\d{1,3}+.\d{1,3}+.\d{1,3}+.\d{1,3}+", defaultValue = "127.0.0.1")
String getGateway();
@option(shortName = "a", description = "interface ip address", pattern = "\d{1,3}+.\d{1,3}+.\d{1,3}+.\d{1,3}+", defaultValue = "127.0.0.1")
String getAddress();
//@option(shortName = "i", description = "interface name")
@unparsed(name = "")
String getInterface();
enum IPAddressFamily {
ipv4, ipv6
}
enum InterfaceMode {
STATIC, DHCP, LOOPBACK
}
}
Error: Option does not take a value: uk.co.flamingpenguin.jewel.cli.UnparsedSpecificationImpl@4b222f
I have been looking at implementing this into my plugin for Bukkit (Minecraft server api) and was testing it out before I actually started using it. When doing so I made some unit tests and found something which looks like a bug but might be my ignorance on the usage. Here is the interface I am using:
public interface Jailing {
@Option(shortName={"player", "pl", "p"})
String getPlayer();
}
And here is the unit tests:
public class TestJewel {
@Test
public void testJewel() {
String[] args = { "-pl", "graywolf336" };
Jailing j = CliFactory.parseArguments(Jailing.class, args);
Assert.assertEquals("graywolf336", j.getPlayer());
}
}
And the result is an argument validation exception message which is wrong:
com.lexicalscope.jewel.cli.ArgumentValidationException: Option must have a value: --player -p -p -p value
Unexpected Option: l
at com.lexicalscope.jewel.cli.ValidationErrorBuilderImpl.validate(ValidationErrorBuilderImpl.java:64)
at com.lexicalscope.jewel.cli.validation.ArgumentValidatorImpl.finishedProcessing(ArgumentValidatorImpl.java:66)
at com.lexicalscope.jewel.cli.ArgumentCollectionBuilder.processArguments(ArgumentCollectionBuilder.java:129)
at com.lexicalscope.jewel.cli.AbstractCliImpl.parseArguments(AbstractCliImpl.java:42)
at com.lexicalscope.jewel.cli.CliFactory.parseArguments(CliFactory.java:67)
at test.java.com.graywolf336.jail.TestJewel.testJewel(TestJewel.java:14)
To my understanding of how this works, how I've got it setup and parsing it this should work. Am I missing something or this actually a bug?
JewelCli users should be able to control the order of options displayed in help message. It seems the most easy and natural way is to display options in the order they defined in interface/class.
would be nice if the help message contains the allowed enum values
Newest version: Result of my parsed arguments is {cache=mysql, duration=0, fillCache=false, writeRequestsPerSecond=0, readRequestsPerSecond=0, readThreadCount=5, size=4096, verbose=false, writeThreadCount=0}
Calling .isVerose() or isFillCache() returns true.
Cannot debug it because there are no sources for the .internal classes.
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.