Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Opaque stacktrace for "%" in Option description #615

Closed
petermr opened this issue Jan 29, 2019 · 6 comments
Closed

Opaque stacktrace for "%" in Option description #615

petermr opened this issue Jan 29, 2019 · 6 comments

Comments

@petermr
Copy link

petermr commented Jan 29, 2019

If a naked percent character occurs in an option, a large and impenetrable stack trace is thrown.

@Option(names = {"--excludebase"}, 
    		arity="1..*",
    		description = "exclude child files of cTree (only works with --ctree). "
    				+ "Currently must be explicit or with trailing % for truncated glob."
    		)
	public String[] excludeBase;

gives

java.util.MissingFormatArgumentException: Format specifier '% f'
	at java.util.Formatter.format(Formatter.java:2519)
	at java.util.Formatter.format(Formatter.java:2455)
	at java.lang.String.format(String.java:2940)
	at picocli.CommandLine$Model$ArgSpec.renderedDescription(CommandLine.java:5120)
	at picocli.CommandLine$Help$DefaultOptionRenderer.renderDescriptionLines(CommandLine.java:9239)
	at picocli.CommandLine$Help$DefaultOptionRenderer.render(CommandLine.java:9212)
	at picocli.CommandLine$Help.calcLongOptionColumnWidth(CommandLine.java:8832)
	at picocli.CommandLine$Help.parameterList(CommandLine.java:8867)
	at picocli.CommandLine$Model$UsageMessageSpec$8.render(CommandLine.java:4515)
	at picocli.CommandLine.usage(CommandLine.java:1647)
	at picocli.CommandLine.usage(CommandLine.java:1621)
	at picocli.CommandLine.usage(CommandLine.java:1591)
	at picocli.CommandLine$DefaultExceptionHandler.internalHandleParseException(CommandLine.java:1060)
	at picocli.CommandLine$DefaultExceptionHandler.handleParseException(CommandLine.java:1055)
	at picocli.CommandLine.parseWithHandlers(CommandLine.java:1528)
	at picocli.CommandLine.call(CommandLine.java:1786)
	at picocli.CommandLine.call(CommandLine.java:1710)
	at org.contentmine.ami.tools.AbstractAMITool.runCommands(AbstractAMITool.java:206)
	at org.contentmine.ami.tools.AbstractAMITool.runCommands(AbstractAMITool.java:194)
	at org.contentmine.ami.tools.AMIImageTest.testBinarizeTrees(AMIImageTest.java:171)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:497)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

This is not easy to interpret. What are the rules for including "%" escaping and can the error be trapped more meaningfully?

@remkop remkop added this to the 3.9.3 milestone Jan 29, 2019
@remkop remkop changed the title Opaque stacktrace for incorrect "%" in Option Opaque stacktrace for "%" in Option description Jan 29, 2019
@remkop
Copy link
Owner

remkop commented Jan 29, 2019

Hi Peter, good catch, I did not realize this.

What is happening is that picocli formats the description of all options to convert any embedded %n character sequences to the line separator for the platform where the program is running. This allows application authors to use %n in descriptions (and pretty much all other sections of the usage help message) to embed system-independent newlines.

The drawback is that any % characters embedded in descriptions (or other sections of the usage help message) need to be escaped with another %. In your case, change the description to the below:

@Option(names = {"--excludebase"}, arity="1..*",
        description = "exclude child files of cTree (only works with --ctree). "
                    + "Currently must be explicit or with trailing %% for truncated glob."
 )
public String[] excludeBase;

This will get rendered as a single % when picocli prints the usage help.

This is documented partially but I should update the docs to explain that this is done for all option descriptions now, and I should make special mention of the fact that standalone % characters need to be escaped with another %.

@remkop
Copy link
Owner

remkop commented Jan 30, 2019

That said, the obtuse stack trace is very undesirable. I’ll add some error handling around the formatting so that this becomes a warning instead and the unformatted usage help message is shown.

@petermr
Copy link
Author

petermr commented Jan 30, 2019

Thank you!
I appreciate it's probably quite hard to catch it exactly (i.e. tracking back to the actual @option but if you can catch the Exception at

	at picocli.CommandLine$Model$ArgSpec.renderedDescription(CommandLine.java:5120)

and emit something to indicate that this is an illegal % (which should be escaped as %%) , and give the local text context (in " bla bla % foo bling ...") then most people can locate it and edit.

remkop added a commit that referenced this issue Jan 30, 2019
… illegal format specifiers like single '%' characters.
remkop added a commit that referenced this issue Jan 30, 2019
… illegal format specifiers like single '%' characters.
@remkop
Copy link
Owner

remkop commented Jan 30, 2019

I've pushed a fix to master that does this:

  • if an illegal format exception occurs, catch it and use the raw string instead (this means that embedded '%n' strings are not converted to newlines)
  • trace at WARN level to STDERR what happened and what users can do about it.

Can you verify?

I still need to update the documentation so keeping this ticket open for now.

remkop added a commit that referenced this issue Jan 31, 2019
remkop added a commit that referenced this issue Jan 31, 2019
(cherry picked from commit a313d9f)
@petermr
Copy link
Author

petermr commented Jan 31, 2019 via email

remkop added a commit that referenced this issue Feb 1, 2019
…rmat specifiers in javadoc for relevant @command, @option and @parameters annotation attributes

Closes #615
@remkop remkop closed this as completed in 71204dc Feb 1, 2019
@remkop
Copy link
Owner

remkop commented Feb 1, 2019

Picocli 3.9.3 has been released. This includes a fix for this issue.

Enjoy!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants