refcodes-cli: Parse your args[]

README

The REFCODES.ORG codes represent a group of artifacts consolidating parts of my work in the past years. Several topics are covered which I consider useful for you, programmers, developers and software engineers.


Update [2019-07-11@21:17]: As of version 2.0.3 of the according artifacts I added some code with screenshots on how you now can tweak your ArgsParser with ANSI Escape-Codes. + Update [2020-08-09@20:53]: As of version 2.0.6, the Operation argument has been added.


What is this repository for?

The refcodes-cli artifact defines some helpful utility to parse your command line arguments (as passed to your public static void main( String[] args) { ... } method. It lets you define the exact valid combinations of command line arguments, it parses them arguments for you and it lets you print the syntax as you programmatically defined it. Let’s get started.

Quick start archetype

Use the refcodes-archetype-alt-cli to create a bare metal command line interface (CLI) driven Java application:

Please adjust my.corp with your actual Group-ID and myapp with your actual Artifact-ID!

mvn archetype:generate \
  -DarchetypeGroupId=org.refcodes \
  -DarchetypeArtifactId=refcodes-archetype-alt-cli \
  -DarchetypeVersion=2.1.1 \
  -DgroupId=my.corp \
  -DartifactId=myapp \
  -Dversion=1.0-SNAPSHOT

Using the defaults, this will generate a CLI application providing a command line interface by harnessing the refcodes-cli library. See also “refcodes-cli: Parse your args[]”.

ANSI Escape-Codes

You can start right off by playing around with ANSI Escape-Codes to tweak your ArgsParser instantiated in your freshly generated CLI application:

As a foundation for working with ANSI Escape-Codes the refcodes-data artifact provides the AnsiEscapeCode enumeration for easy construction of ANSI Escape-Code sequences. Below we use the AnsiEscapeCode enumeration to color our ArgsParser’s output (the code corresponds to the last screenshot above):

1
2
3
4
5
6
7
...
ArgsParser theArgsParser = new ArgsParserImpl( ... );
theArgsParser.setBannerEscapeCode( AnsiEscapeCode.toEscapeSequence( AnsiEscapeCode.REVERSE_VIDEO ) );
theArgsParser.setBannerBorderEscapeCode( AnsiEscapeCode.toEscapeSequence( AnsiEscapeCode.FG_BRIGHT_CYAN, AnsiEscapeCode.BOLD ) );
theArgsParser.setParameterEscapeCode( AnsiEscapeCode.toEscapeSequence( AnsiEscapeCode.FG_RED, AnsiEscapeCode.UNDERLINE ) );
theArgsParser.setParameterDescriptionEscapeCode( AnsiEscapeCode.toEscapeSequence( AnsiEscapeCode.FG_BRIGHT_YELLOW, AnsiEscapeCode.BOLD ) );
...    

How do I get set up?

To get up and running, include the following dependency (without the three dots “…”) in your pom.xml:

1
2
3
4
5
6
7
8
9
<dependencies>
	...
	<dependency>
		<groupId>org.refcodes</groupId>
		<artifactId>refcodes-cli</artifactId>
		<version>2.1.1</version>
	</dependency>
	...
</dependencies>

The artifact is hosted directly at Maven Central. Jump straight to the source codes at Bitbucket. Read the artifact’s javadoc at javadoc.io.

How do I get started?

Consider you have a tool called foo-bar to be invoked with the below allowed argument combinations (syntax):

foo-bar [{ -a | -d }] -f <file>

foo-bar can be invoked either with an optional -a or with an optional -d switch, but not both of them at the same time, and a file -f <file> must be provided, else the passed arguments are rejected as not being valid.

Valid arguments would be:

  • foo-bar -f someFile
  • foo-bar -d -f anyFile
  • foo-bar -f otherFile -a
  • foo-bar -a -f otherFile

Invalid arguments would be:

  • foo-bar -f someFile -b
  • foo-bar -a someFile -f
  • foo-bar -a -d -f anyFile
  • foo-bar -a -x -f otherFile

This means that additional switches not supported are not valid. The parser detects such situations and you can print out a help message in such cases.

Construct your parser

First build your syntax using Flags, Options and Conditions. You actually https://www.javadoc.io/docr your command line tool’s supported arguments:

1
2
3
4
5
6
7
8
9
10
StringOption theFile = new StringOption( "-f", "--file", "file", "A file" );
Flag theAdd = new Flag( "-a", null, "Add the specified file" );
Flag theDelete = new Flag( "-d", null, "Delete the specified file" );
Condition theXor = new XorCondition( theAdd, theDelete );
Condition theOptional = new OptionalCondition( theXor );
Condition theAnd = new AndCondition( theOptional, theFile );

ArgsParser theArgsParser = new ArgsParserImpl( theAnd );
theArgsParser.printUsage();
// theArgsParser.printHelp();

Using syntactic sugar

The TinyRestfulServer demo application uses syntactic sugar for setting up the command line arguments parser:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import static org.refcodes.cli.CliSugar.*;
...
	public static void main( String args[] ) {

		IntOption theWidth = intOption( "-w", "--width", "width", "Sets the console width" );
		IntOption thePort = intOption( "-p", "--port", "port", "Sets the port for the server" );
		IntOption theMaxConns = intOption( "-c", "--connections", "connections", "Sets the number of max. connections" );
		StringOption theUsername = stringOption( "-u", "--user", "username", "The username for HTTP basic authentication" );
		StringOption theSecret = stringOption( "-s", "--secret", "secret", "The password for HTTP basic authentication" );

		Flag theHelp = helpFlag( "Shows this help" );
		Condition theRootCondition = xor( 
			and( 
				thePort, optional( theMaxConns ), optional( and( theUsername, theSecret ) ), optional( theWidth )
			),
			theHelp
		);
		ArgsParser theArgsParser = new ArgsParserImpl( theRootCondition );
		theArgsParser.withSyntaxNotation( SyntaxNotation.REFCODES );
		theArgsParser.withName( "TinyRestful" ).withTitle( "TINYRESTFUL" ).withCopyrightNote( "Copyright (c) by FUNCODES.CLUB, Munich, Germany." ).withLicenseNote( "Licensed under GNU General Public License, v3.0 and Apache License, v2.0" );
		theArgsParser.withBannerFont( new FontImpl( FontFamily.DIALOG, FontStyle.BOLD, 14 ) ).withBannerFontPalette( AsciiColorPalette.MAX_LEVEL_GRAY.getPalette() );
		theArgsParser.setDescription( "Tiny evil RESTful server. TinyRestfulServer makes heavy use of the REFCODES.ORG artifacts found together with the FUNCODES.CLUB sources at <http://bitbucket.org/metacodez>." );
		theArgsParser.addExampleUsage( "To use a specific port", thePort );
		theArgsParser.addExampleUsage( "To secure with a user's credentials", theUsername, theSecret);
		List<? extends Operand<?>> theResult = theArgsParser.evalArgs( args );
		...
	}
...

Most obvious is the missing new statement for instantiating the parts of your command line parser as this is done by the statically imported methods.

Flip through fluent methods provided by the CliSugar class to find out about further possibilities of the refcodes-cli](https://bitbucket.org/refcodes/refcodes-cli) artifact, such as the creation of arrays from Option arguments by invoking asArray(...).

Under the hood

As seen above, you pass your root Condition to the [`ArgsPhttps://www.javadoc.io/docvadoc.io/org.refcodes/refcodes-cli/2.1.1/org/refcodes/cli/ArgsParser.html) which then alhttps://www.javadoc.io/doc command line tool’s usage string:

1
2
3
...
theArgsParser.printUsage();
...

Test (invoke) your parser: In real live you would pass your main-method’s args[] array to the parser. Now just for a test-run, pass a java.lang.String array to your parser and let it parse it:

1
2
3
4
5
String[] args = new String[] {
	"-f", "someFile", "-d"
};
List<? extends Operand<?>> theResult = theArgsParser.evalArgs( args );
File theConfigFile = new File( theFile.getValue() );

Now the leaves of your syntax tree are filled with the argument’s values according to the syntax you have been setting up: Your StringOption instance alhttps://www.javadoc.io/docains the value “someFile”.

The theResult contains the parsed arguments for you to use in your business logic.

In case of argument rejection, a sub-type of the ArgsMismatchException ishttps://www.javadoc.io/docone of the exceptions UnknownArgsException, https://www.javadoc.io/doc(https://www.javadoc.io/doc/org.refcodes/refcodes-cli/2.1.1/org/refcodes/cli/AmbiguousArgsException.html), https://www.javadoc.io/doconhttps://www.javadoc.io/doc](https://www.javadoc.io/doc/org.refcodes/refcodes-cli/2.1.1/org/refcodes/cli/ParseArgsException.html) is behttps://www.javadoc.io/doc the cause of the rejection. So you can either catch the [ArgsMismatchException`](https://www.javadoc.io/doc/org.refcodes/refcodes-cli/2.1.1/org/refcodes/cli/ArgsMismatchException.html) orhttps://www.javadoc.io/docls on the cause, the other sub-exceptions.

Syntaxable

A Syntaxable defines the mhttps://www.javadoc.io/doc when building a command line arguments syntax tree for traversing the syntax tree; either for parsing command line arguments or for constructing the command line arguments syntax.

By providing various implementations of the Syntaxable’s subclasses https://www.javadoc.io/doc://static.javadoc.io/org.refcodes/refcodes-cli/2.1.1/org/refcodes/cli/Operand.html), Option, Condition or Flag, a command line arghttps://www.javadoc.io/doce constructed. This syntax tree can be used to create a human readable (verbose) command line arguments syntax and to parse an array of command line arguments for determining the Operands’, the Flages’ or the [`Optionshttps://www.javadoc.io/doc.io/org.refcodes/refcodes-cli/2.1.1/org/refcodes/cli/Option.html)’ values.

Operand

An Operand represents a value parsed from command line arguments. An Operand has a state which changes with each invocation of the parseArgs(String[]) method.

It is recommended to put your Operand instance(s) at the end of your top Condition to enforce it to be the last Syntaxable(s) when parsing the command line arguments - this makes sure that any Options pick their option arguments so that the Operand(s) will correctly be left over for parsing command line argument(s); the Operand will not pick by mistake an Option argument.

Option

An Option represents a command line option with the according option’s value. An Option can be seen as a key / value(s) pair defined in the command line arguments parsed via the parseArgs(String[]) method.

An Option has a state which changes with each invocation of the parseArgs(String[]) method.

Flag

A Flag is an Option with a Boolean https://www.javadoc.io/docare just set or omitted in the command line arguments with no value provided; former representing a true status and latter representing a false status.

Operation

The Operation is an argument representing a function or a method and is either provided or not provided as of the isEnabled() method. It must neither be prefixed with - nor with -- in contrast to the Option or the Flag type.

Condition

The Condition interface reprhttps://www.javadoc.io/docmand line arguments syntax tree; simply extending the Syntaxable interface and adding the functionality of providing access to the added Operands (leafs). In future extensions, a Condition might provide access to the child Syntaxable elements contained in a Condition instance. As of the current findings, access to the children of the Condition node is not required and would make the interface unnecessarily complicated.

Happy coding

Want more than just the usage text? You can print out each building block of the help text on its own, below is all you can get:

1
2
3
4
...
// theArgsParser.printUsage();
theArgsParser.printHelp();
...

See also Parsing arguments to a Java command line program at StackOverflow.

Resources

  • See StringOption
  • See [`Flahttps://www.javadoc.io/docc.io/org.refcodes/refcodes-cli/2.1.1/org/refcodes/cli/Flag.html)
  • See [`StringOperahttps://www.javadoc.io/dococ.io/org.refcodes/refcodes-cli/2.1.1/org/refcodes/cli/StringOperand.html)
  • See [`Xohttps://www.javadoc.io/docic.javadoc.io/org.refcodes/refcodes-cli/2.1.1/org/refcodes/cli/XorCondition.html)
  • See [`Andhttps://www.javadoc.io/docc.javadoc.io/org.refcodes/refcodes-cli/2.1.1/org/refcodes/cli/AndCondition.html)
  • See [`Opthttps://www.javadoc.io/docstatic.javadoc.io/org.refcodes/refcodes-cli/2.1.1/org/refcodes/cli/OptionalCondition.html)

Chttps://www.javadoc.io/doc#

  • When printing out the usage, concatenate switches something like -a, -d, -x and -h (which are set or omitted without any argument) to become -adxh
  • When printing out the POSIX like usage text, it should look more POSIX like than it currently does (though I like the way it is)
  • Code review
  • Other guidelines

Who do I talk to?

  • Siegfried Steiner (steiner@refcodes.org)

Terms and conditions

The REFCODES.ORG group of artifacts is published under some open source licenses; covered by the refcodes-licensing (org.refcodes group) artifact - evident in each artifact in question as of the pom.xml dependency included in such artifact.