Command Line Parser

CommandLineParser is an utility class for parsing command line. In addition to just parse command line arguments CommandLineParser also has a built-in help system which automatically handles basic command line arguments like -? or --help. Parsed command line arguments can be directed to member fields by annotations. Also, arguments can have constraints like minValue, maxValue or value ranges etc. and for defined constraints informative help text fragments are generated automatically (for more information see Configuration elements).

All the help texts can be generated to HTML and XML as well as to various wiki formats just by defining a system property (see System properties).

Requirements

MANIFEST.MF file

MANIFEST.MF file must have the following items:

  • Implementation-Title:, which is used for creating usage help texts. This must contain the final name for the .jar file without the .jar extension. For example, if the jar file is utility.jar then Implementation-Title: must be utility.
  • Implementation-Version:, which is used for (built-in) --version option.

If either of the items is missing a ConfigurationException is thrown with a descriptive error message. Also, MANIFEST.MF file must have Main-Class: field defined.

Packaging

All the classes in the com.hapiware.util.cmdlineparser and it's sub-packages must be included to the final jar file. If you are a Maven user see chapter For Maven users.

Command line structure

CommandLineParser can be configured either to use arguments (like ls utility) or to handle several commands (like git).

Using arguments

If CommandLineParser is configured to use arguments the general form is:

java -jar utilname.jar OPTS ARGS

where:

  • OPTS are options for the utility like -v, --type, etc.
  • ARGS are mandatory and/or optional arguments for the utility like 12, ^.+Writer$, etc.

Notice that CommandLineParser is able to handle the situation where some of the options are given after the arguments. Thus the general form for the arguments confguration is:

java -jar utilname.jar OPTS ARGS OPTS

The order options and arguments are defined (using various add() methods) is irrelevant. Here is an example of argument definition:

private static CommandLineParser _clp;

@Id("ALGORITHM") private static String _algorithm;
@Id("ITER") private static long _iter;
@Id("INPUT") private static String _input;
@Id("DIGEST") private static String _digest;

static {
    _clp =
        new CommandLineParser(
            Hash.class,
            new Description()
                .b("hash").d(" is an utility to create and check hashed strings.")
        );
    _clp.add(String.class, new Argument<String>("ALGORITHM") {{
        description("An algorithm to be used for hashing. Case is irrelevant.");
        constraint(new Enumeration<String>() {{
            valueIgnoreCase("sha", " SHA algorithm. Same as SHA-1.");
            valueIgnoreCase("sha-1", "SHA-1 algorithm. Same as SHA.");
            valueIgnoreCase("md5", "MD5 algorithm.");
        }});
    }});
    _clp.add(Long.class, new Argument<Long>("ITER") {{
        description("Number of iterations.");
        minValue(1l);
    }});
    _clp.add(String.class, new Argument<String>("INPUT") {{
        description("A string to be salt-hashed.");
    }});
    _clp.add(String.class, new Argument<String>("DIGEST") {{
        description("A digest to be tested if defined.");
        optional("", false);
    }});
    _clp.addExampleArguments("sha 1000 password");
    _clp.addExampleArguments("sha 1000 password D16soUrQ0lPaJMO2yy7aN7RZnI6Nx1Zj");
}

Using commands

If CommandLineParser is configured to use commands the general form is:

java -jar utilname.jar OPTS CMD CMD-OPTS CMD-ARGS

where:
  • OPTS are global options for the utility like -v, --log, etc. Notice the difference between OPTS and CMD-OPTS.
  • CMD is a command name like set, commit, list, etc.
  • CMD-OPTS are options for the command like -n, --type, etc. Notice that each defined command can have its own specific command options. Notice also the difference between CMD-OPTS and OPTS.
  • CMD-ARGS are mandatory and/or optional arguments for the command like 12, ^.+Writer$, etc. CMD-ARGS are command specific.

CommandLineParser is able to handle the situation where some of the global options and command options are given after the command arguments. Thus the general form for the command is:

java -jar utilname.jar OPTS CMD CMD-OPTS CMD-ARGS CMD-OPTS OPTS

Notice here that the order between latter CMD-OPTS and OPTS is significant. Here is an example of how an utility with multiple commands can be defined:

private static CommandLineParser _clp;
static {
    _clp =
        new CommandLineParser(
            CommandHelpTest.class,
            _writer,
            new Description()
                .d("Lists and sets the logging level of Java Loggers")
                .d("(java.util.logging.Logger) on the run.")
                .d("Optionally log4j is also supported but it requires")
                .d("java.util.logging.LoggingMXBean interface to be implemented for log4j.")
                .d("See http://www.hapiware.com/jmx-tools.")
        );
    _clp.add(new Option("v") {{
        alternatives("verbose");
        multiple();
        description("Prints more verbose output.");
    }});
    _clp.add(new Option("l") {{
        alternatives("log");
        description("Logs every step of the process.");
    }});

    _clp.add(
        new Command(
            "j",
            "Shows running JVMs (jobs).",
            new CommandExecutor() {
                public void execute(
                    Data command,
                    List<Option.Data> globalOptions)
                {
                    // Show jobs...
                }
            }
        ) {{
            alternatives("jobs");
            d("Shows PIDs and names of running JVMs (jobs). PID starting with");
            d("an asterisk (*) means that JMX agent is not runnig on that JVM.");
            d("Start the target JVM with -Dcom.sun.management.jmxremote or");
            d("if you are running JVM 1.6 or later use startjmx service.");
    }});

    final Option optionT =
        new Option("t") {{
            alternatives("type");
            description("Type of the logger (i.e. Java logger or log4j logger).");
            set(String.class, new OptionArgument<String>() {{
                constraint(new Enumeration<String>() {{
                    value("4", "stands for log4j logger.");
                    valueIgnoreCase("j", "stands for Java logger.");
                }});
            }});
        }};

    _clp.add(
        new Command(
            "l",
            "Lists current logging levels.",
            new CommandExecutor() {
                public void execute(
                    Data command,
                    List<Option.Data> globalOptions)
                {
                    // List logging levels...
                }
            }
        ) {{
            alternatives("list");
            add(optionT);
            add(Integer.class, new Argument<Integer>("PID") {{
                description("Process id of the running JVM.");
            }});
            add(Integer.class, new Argument<Integer>("PATTERN") {{
                d("Java regular expression for matching logger names.");
                d("A special value ").b("root").d(" lists only the root logger(s).");
            }});
            d("Lists current logging levels for all loggers matching PATTERN");
            d("in a JVM process identified by PID.");
            d("Logger type is identified by a prefix. ").b("(J)")
                .d(" for Java loggers and ").b("(4)").d(" for log4j loggers.");
    }});

    _clp.add(
        new Command(
            "s",
            "Sets a new logging level.",
            new CommandExecutor() {
                public void execute(
                    Data command,
                    List<Option.Data> globalOptions)
                {
                    // Set new logging level...
                }
            }
        ) {{
            alternatives("set");
            add(optionT);
            add(Integer.class, new Argument<Integer>("PID") {{
                description("Process id of the running JVM.");
            }});
            add(Integer.class, new Argument<Integer>("PATTERN") {{
                d("Java regular expression for matching logger names.");
                d("A special value ").b("root").d(" sets only the root logger(s).");
            }});
            add(String.class, new Argument<String>("LEVEL") {{
                d("Represents a new logging level for the logger.");
                d("See logger documentation for further help.");
                p();
                d("LEVEL accepts a special value ").b("null").d(" to set the logging level");
                d("to follow a parent logger's logging level.");
            }});
            d("Sets a new logging level LEVEL for all loggers matching PATTERN");
            d("in a JVM process identified by PID.");
    }});

    _clp.addExampleArguments("jobs");
    _clp.addExampleArguments("--log l 50001 ^.+");
    _clp.addExampleArguments("list 50001 root");
    _clp.addExampleArguments("set -tJ 50001 ^com\\.hapiware\\..*Worker.* INFO");
    _clp.addExampleArguments("set --type 4 50001 .*Test null");
}

Parsing command line

Parsing is done one of the parse commands:

  • parse(String[])
  • parse(Class, String[])
  • parse(Object, String[])
  • parsec(String[])
  • parsec(Class, String[])
  • parsec(Object, String[])
  • parsech(String[])
  • parsech(Class, String[])
  • parsech(Object, String[])
  • parseInternalOptions(String[])

Parse methods try to parse given arguments according to the configuration and throw an exception if the parsing fails. Otherwise the execution of the code continues normally. The difference between parse(), parsec() and parsech() methods is how they handle exceptions and help messages. parse() methods throw an exception if something goes wrong and it is the programmer's responsibility to show error messages and help texts. To make the usage of CommandLineParser much easier parsec() and parsech() methods were introduced. Both methods catch all the exceptions and writes error messages using the current Writer. They also will call System.exit(int) automatically as a part of the exception handling.The difference between parsec() and parsech() methods is that parsech() writes help texts in addition to error messages whereas parsec() does not.

The next example uses parsec(String[]) method and is a continuation to an example introduced in Using arguments:

public static void main(String[] args)
{
    _clp.parsec(args);
    if(_digest.length() == 0)
        createDigest(_algorithm, _iter, _input);
    else
        verifyDigest(_algorithm, _iter, _input, _digest);
}

If the command line cannot be parsed properly parsec(String[]) writes the error message and then exits using System.exit(int). Otherwise the code continues normally.

parseInternalOptions(String[]) method differs from the other parse commands in that it only reacts to internal options by printing a proper message (e.g. help or version number) and exits using System.exit(int). If no internal options are found among given arguments then the code continues normally after parseInternalOptions(String[]) call. This is used in those scenarios where there are something else to do, like reading a configuration file, before calling the parse(). For example:

public static void main(String[] args)
{
     _clp.parseInternalOptions(args);
    readConfigurationFile();
    generateConfigurationFiles();
    _clp.parsec(args);
}

Now, the program can react to internal options without first handling configuration files.

Coding style

Argument and command definitions can be defined with two different ways; using double-brace syntax or by chaining commands. They can be mixed if wanted. The examples above uses mainly the double-brace syntax and here is a copied code fragment:

_clp.add(String.class, new Argument<String>("ALGORITHM") {{
    description("An algorithm to be used for hashing. Case is irrelevant.");
    constraint(new Enumeration<String>() {{
        valueIgnoreCase("sha", " SHA algorithm. Same as SHA-1.");
        valueIgnoreCase("sha-1", "SHA-1 algorithm. Same as SHA.");
        valueIgnoreCase("md5", "MD5 algorithm.");
    }});
}});

Notice that description(String) method belongs to Argument class whereas valueIgnoreCase(T, String) belongs to Enumeration class. The same example could have been written like this by using method chaining:

_clp.add(
    String.class,
    new Argument<String>("ALGORITHM")
        .description("An algorithm to be used for hashing. Case is irrelevant.")
        .constraint(
            new Enumeration<String>()
                .valueIgnoreCase("sha", " SHA algorithm. Same as SHA-1.")
                .valueIgnoreCase("sha-1", "SHA-1 algorithm. Same as SHA.")
                .valueIgnoreCase("md5", "MD5 algorithm.")
        )
);

Built-in command line options

CommandLineParser has two built-in options:

  • -?, --help for showing help.
  • --version for asking the version number of the utility

Help option has several subcommands depending is CommandLineParser defined to use arguments or commands. For argument definition the help commands are:

  • --help all for full help.
  • --help usage for showing just the usage synopsis.
  • --help examples for showing examples.
  • --help opts for showing just the options.
  • --help args for showing just the argumets

For command definition the help commands are:

  • --help all for full help.
  • --help usage for showing just the usage synopsis.
  • --help examples for showing examples.
  • --help opts for showing just the options.
  • --help cmds lists all the commands and their short descriptions.
  • --help cmd=CMD shows a full help for the single command CMD.

Here is an example help command call:

java -jar utility.jar --help all

Help commands are available automatically depending on only how the CommandLineParser has been configured (i.e. no extra programming is needed).

Configuration elements

CommandLineParser is configured using the following elements:

  • Argument is a bare argument for the utility or the command. Arguments are type checked in compile and run time. An argument can be optional. For more information see add(Class, Argument), Command.add(Class, Argument), Argument and a chapter Using arguments.
  • Option is an optional command line argument which is identified either by preceding minus (-) or minus-minus (—). For more information see add(Option), Command.add(Option) and Option.
  • Option argument is an argument for the option when needed. Option arguments are also type checked in compile and run time. For more information see Option.set(Class, OptionArgument) and OptionArgument.
  • Command elements are used for defining multiple tasks for the same command line utility. For more information see add(Command), Command and a chapter Using commands.
  • Description element is used to create help texts. The idea to use a separate class for creating help texts instead of using String is flexibility. With description elements a programmer does not need to care about screen width or other formatting aspects. For more information see Description.
    • Length
    • MinLength
    • MaxLength
    • MinValue
    • MaxValue
    • Enumeration

Constraints are set by using Argument.constraint(Constraint) method but there are also helper methods for all built-in constraints except for Enumeration. The helper methods are:

  • Argument.length(int) (and OptionArgument.length(int))
  • Argument.minLength(int) (and OptionArgument.minLength(int))
  • Argument.maxLength(int) (and OptionArgument.maxLength(int))
  • Argument.minValue(Object) (and OptionArgument.minValue(Object))
  • Argument.maxValue(Object) (and OptionArgument.maxValue(Object))

Annotations

Annotation Id can be used to mark a member field to have a value automatically from parsed command line arguments. Values for annotated fields are set for matched Id.value() by comparing it to the id (element's name by default) of defined configuration elements. In general the name of the configuration element is used for Id.value() to annotate a member field. There may arise a need to use same names for different elements. For example, a global option and a command option may have the same short name. This should not be a common problem but there is a way to get around this by defining a different id for either of the conflicting element (thus keeping the same name). Now, Id.value() can be the just defined id. For more information see:

  • Argument.Argument(String) and Argument.id(String)
  • Option.Option(String), Option.alternatives(String…) and Option.id(String)
  • Command.Command(String, String), Command.alternatives(String…) and Command.id(String)

If the option does not have an argument then the annotated member field must be boolean. If Option.multiple() has been set then the annotated field must be an array of defined argument types. If there are no defined arguments the the field must be a boolean array.

Command executors

There is two ways to trigger some action depending on what command has been called from the command line; manual and by using command executors.

Manually a programmer can either use a combination of Id.value() and Command.id(String) or instead of using annotations use CommandLineParser.getCommand(). The other way is to implement CommandExecutor interface and give the instance of the implemented class as parameter to Command.Command(String, String, CommandExecutor).

Writer

Writer is an interface to format the output of the help texts and error messages. If no writer is defined for CommandLineParser ScreenWriter is used as a default Writer.

The main reason for the existence of Writer interface is the ability to reformat already defined help texts for the command line tool's home page, for example.

It is also possible to use the current writer for writing the normal output of the command line utility. The current writer can be fetched by calling CommandLineParser.getWriter().

There are several built-in Writer implementations:

  • ScreenWriter (the default writer if nothing else is defined)
  • WikidotWriter
  • ConfluenceWriter
  • GitHubWriter
  • HtmlWriter
  • XmlWriter is mainly for demonstrating the internal structure of the built-in help system.

See also System properties.

Configuration principles

The most important parameters for configuration elements are name and description which are required. Also, for commands a short description is required. Options and commands can have multiple alternative names. The use of CommandLineParser.addExampleArguments(String) is not forced but highly recommended.

Naming conventions

Names must have a certain pattern (see Util.checkName(String)) and for options there are some additional considerations. If the option name (or alternative name) is just a single character then it is interpreted as a short option and is identified from the command line arguments by a single minus (-) character. For example -v. On the other hand if the option name is two or more characters long then it is interpreted as a long option and is identified by two minus (--) characters. For example --type.

Defining options and fetching them

Notice the difference in naming options and fetching them using various getOption() methods. The difference is that for definition the option name is given without the preceding minus characters. The option is interpreted as a short or long option as defined above. However, when fetching and checking options the preceding minus charactes must be used for short and long options accordingly (e.g. "-a", "--verbose").

System properties

System property writerclass overrides the hard coded Writer. writerclass must have a full class name of a class implementing Writer interface. The implementation must have a default constructor. writerclass also recognizes a special format to use built-in writer implementations. Internal writers are recognized by their class name prefixes (case does matter) and are:

  • Screen (default, if nothing is hard coded)
  • Wikidot
  • Confluence
  • GitHub
  • Html
  • Xml

For example, to use GitHubWriter as a writer for the output:

java -Dwriterclass=GitHub -jar myutil.jar

See also ScreenWriter for it's system property.

For Maven users

Here is an example pom.xml showing all the necessary elements to package CommandLineParser correctly:

<?xml version="1.0" encoding="UTF-8"?>
<project
    xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
>
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.hapiware.util</groupId>
    <artifactId>hash</artifactId>
    <version>1.0.0</version>
    <build>
        <finalName>hash</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.5</source>
                    <target>1.5</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <artifactSet>
                                <includes>
                                    <include>com.hapiware.util:command-line-parser</include>
                                </includes>
                            </artifactSet>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.hapiware.util.Hash</mainClass>
                        </manifest>
                        <manifestEntries>
                            <Implementation-Title>${build.finalName}</Implementation-Title> 
                            <Implementation-Version>${project.version}</Implementation-Version>
                            <Implementation-Vendor>http://www.hapiware.com</Implementation-Vendor>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>com.hapiware.util</groupId>
            <artifactId>command-line-parser</artifactId>
            <version>[1.0.0,)</version>
        </dependency>
    </dependencies>
</project>

Requirements

  • Java 5 or later

Download

Download from Java Tools page.

Maven repository

[http://hapi.github.com/maven2/]

  • groupId: com.hapiware.util
  • artifactId: command-line-parser
  • version: [1.0.0,)

License

MIT License

Copyright (c) 2010 Hapi, http://www.hapiware.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License