1. Siegfried Steiner
  2. refcodes-console

Overview

HTTPS SSH

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.

What is this repository for?

The refcodes-console 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.

How do I get set up?

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

<dependencies>
    ...
    <dependency>
        <groupId>org.refcodes</groupId>
        <artifactId>refcodes-console</artifactId>
        <version>1.1.7</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.

Getting 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 Switches , Options and Conditions. You actually build the syntax tree for your command line tool's supported arguments:

Option<String> theFile = new StringOptionImpl( "-f", "--file", "file", "A file" );
Switch theAdd = new SwitchImpl( "-a", null, "Add the specified file" );
Switch theDelete = new SwitchImpl( "-d", null, "Delete the specified file" );
Condition theXor = new XorConditionImpl( theAdd, theDelete );
Syntaxable theOptional = new OptionalImpl( theXor );
Condition theAnd = new AndConditionImpl( 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:

import static org.refcodes.console.ConsoleSugar.*;
...
    public static void main( String args[] ) {

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

        Switch theHelp = helpSwitch( "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( FontType.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/refcodes>." );
        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.

Under the hood

As seen above, you pass your root Condition to the ArgsParser which then already can print out your command line tool's usage string:

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:

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 StringOptionImpl instance aliased theFile now contains 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 is being thrown; actually one of the exceptions UnknownArgsException, AmbiguousArgsException, SuperfluousArgsException or ParseArgsException is being thrown, according to the cause of the rejection. So you can either catch the ArgsMismatchException or, if you need more details on the cause, the other sub-exceptions.

Syntaxable

A Syntaxable defines the methods at least required 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 such as Operand, Option or Condition, a command line arguments syntax tree can be constructed. This syntax tree can be use to create a human readable (verbose) command line arguments syntax and to parse an array of command line arguments for determining the Operands', the Switches' or the Options' 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.

Switch

A Switch is an Option with a Boolean state. Usually switches are just set or omitted in the command line arguments with no value provided; former representing a true status and latter representing a false status.

Condition

The Condition interface represents a node in the command 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:

...
// theArgsParser.printUsage();
theArgsParser.printHelp();
...

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

Resources

Contribution guidelines

  • 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.