JCLAP: Java Command-Line Argument Parser

Copyright © 2009-2020 Giles Winstanley
Version: 2.2

Usage: java snaq.util.jclap.example.ParserExample <options> <file> [<file>] ...
Options:
	-w,--width <integer>	(mandatory)
		Width of resized images.

	-h,--height <integer>	(mandatory)
		Height of resized images.

	-d,--dir <folder>
		Output directory for resized images.

	-f,--format <string>
		Allowed values: { "jpg", "png" }
		Output format of images.

	-v,--verbose
		Displays extra runtime information.

	-@,--version
		Displays the application version information.

	-?,--help
		Displays help information.

Example application help/usage message created by JCLAP.

What is it?

Command-line applications are very useful for getting things done, particularly when time is critical. Designing and developing a graphical user interface (GUI) for an application is often a lengthy and tedious process, and a large number of applications have no need for an interface so complicated. That said, the interface part is very important, and when an application needs to be run from the command-line, it helps enormously if it has been made simple to use. That's where JCLAP becomes useful. JCLAP helps Java developers to create simple-to-use command-line interfaces for their applications.

For example, if you wanted to write an application to process text files, you might imagine issuing a command such as:

java -jar Application.jar --folder ../foo --type xml

Or perhaps using short versions of the options:

java -jar Application.jar -f ../foo -t xml

This utility allows you to easily parse all these command-line arguments, retrieve the values assigned, and even semi-automatically display help information about the available arguments. The primary aim is to parse command-line argument in the POSIX  standard, which is what most end users will already recognise. In addition JCLAP supports long names for options, and an option to stop argument parsing, in the style of the GNU  standard. JCLAP takes this hybrid approach to provide a highly flexible way to specify arguments.

Licence Agreement

JCLAP is available under a BSD-style licence as described below. This licence permits redistribution of the binary or source code (or both) for commercial or non-commercial use, provided the licence conditions are followed to acknowledge origination and authorship of the library.

JCLAP: Java Command-Line Argument Parser <https://www.snaq.net/software/jclap/>
Copyright (c) 2009-2020 Giles Winstanley. All Rights Reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. Redistributions of modified versions of the source code, must be accompanied by documentation detailing which parts of the code are not part of the original software.
  4. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Where can I get it?

The simplest way is using Apache Maven, adding the following details to your POM file's dependencies section:

<dependency>
      <groupId>net.snaq</groupId>
      <artifactId>jclap</artifactId>
      <version>2.2</version>
    </dependency>

Otherwise you can download JCLAP using the following links:

You can download the latest version of JCLAP from: https://www.snaq.net/software/jclap/

JCLAP requires Java Platform 8 or greater installed. The JAR also includes module support for Java 9+ (module name: net.snaq.jclap).

What about support?

Send all queries/issues/requests here, including as much information as possible to help diagnose the problem (e.g. stacktraces, etc.). The library includes basic CLI translations for a few European languages (en/fr/de/es/pt), and chooses based on the default system locale. However, given my limited grasp of non-English technical jargon, some of the translations may be incorrect. Please contact me if you know more accurate translations for the terms, or want to provide another translation.


Usage

JCLAP command-line argument specification.
  • Each option must have a short-name (single alphanumeric character, ?, or @)
  • Each option may also have a long-name (multiple alphanumeric characters)
  • Option long-name may include hyphens as non-terminal characters
  • An option can be referenced by a single hyphen or forward-slash followed by its short name (e.g. -o or /o)
  • An option can be referenced by a double hyphen followed by its long name (e.g. --option)
  • An option which requires a value must have the value specified as the next argument
  • An option and its value may be separated using a space, colon, or equals
    (e.g. -o value, -o:value, /o=value, --option=value, etc.)
  • An option specified by short name and its value may be unseparated if not ambiguous
    (e.g. /ovalue)
  • Options not requiring values can be grouped in the short-form
    (e.g. "-abc" is equivalent to "-a -b -c")
  • Options can appear in any order (e.g. -abc is equivalent to -cba, and "-i foo /o:bar" is equivalent to "-o bar -i=foo"
  • Options can appear multiple times (if defined to support multiple values)
  • Options always precede non-option arguments (e.g. "-abc -d val nonOption")
  • The -- argument terminates options (e.g. -d is a non-option argument here: "-abc -- -d")
  • The - option is typically used to represent the standard input stream; it is always stored as a non-option argument

JCLAP is simple to use, but it's recommended to follow certain guidelines to make the flow of your application predictable for end users. The basic rules for specifying options are shown in the table above. If you know the POSIX and GNU standards, then you'll quickly recognise how they operate, and even if not you may be familiar with the patterns. The easiest way to demonstrate is with a real-world example.

Imagine a hypothetical application for resizing images to specified width/height dimensions, writing the resized image files to a specified folder/directory, in either JPEG or PNG image format. The user might choose to have information output to the console during processing if required, and there should also be a usage message to show how the basics of how the application may be run. The following code provides all this functionality, including error-checking of arguments, etc. This example doesn't cover all the available methods for creating/using options, but gives a good example of likely use for such a scenerio. For more information see the Javadoc documentation.

import java.util.List;
import snaq.util.jclap.*;

/**
 * Example usage of {@link CLAParser} class, showing how options may be added,
 * parsed, and referenced, along with a recommended exception-handling strategy.
 */
public class ParserExample
{
  private ParserExample() {}

  public static void main(String[] args) throws OptionException
  {
    // Create an instance of the parser class.
    final CLAParser parser = new CLAParser();
    // Define string to describe the non-option arguments in the usage message.
    final String extraArgs = "<file> [<file>] ...";

    // Arguments:
    //   -w/--width   : mandatory,   single
    //   -h/--height  :  optional,   single
    //   -d/--dir     : mandatory,   single
    //   -f/--format  : mandatory,   single, enum{"jpg", "png"}
    //   -v/--verbose :  optional, multiple
    //   -@/--version :  optional,   single
    //   -?/--help    :  optional,   single

    // Note: a reference to an Option instance may be retained in a variable,
    // or simply added & retrieved by name (which is generally easier).
    final Option<Integer> oWidth = parser.addIntegerOption("w", "width", "Width of resized images.", true, false);
    parser.addIntegerOption("h", "height", "Height of resized images.", false, false);
    parser.addStringOption("d", "dir", "Output directory for resized images.", true, false);
    parser.addEnumStringOption("f", "format", "Output format of images.", true, false, new String[]{"jpg", "png"});
    parser.addBooleanOption("v", "verbose", "Displays extra runtime information.", true);
    parser.addBooleanOption("@", "version", "Displays the application version information.", false);
    parser.addBooleanOption("?", "help", "Displays help information.", false);
    try
    {
      // Parse the command-line arguments into options.
      parser.parse(args);

      // Check for display of version information.
      // This checks a boolean/flag option value, with an specified default value.
      if (parser.getBooleanOptionValue("@", false))
        System.out.printf("Version: %s%n", "1.2.3");

      // Check for help option, and display if requested.
      // This checks the boolean/flag option values, with an assumed default value (false).
      if (parser.getBooleanOptionValue("?") || args.length == 0)
      {
        // Usage message is generated automatically from the defined options.
        // The three last argument define:
        //     1. application launch command
        //     2. extra (non-option) arguments
        //     3. extra (non-option) information
        parser.printUsage(System.out, true, null, extraArgs, null);
        System.exit(0);
      }

      // Get resized image dimensions.
      // Don't care about a default value, as options are mandatory.
      final Integer imageW = parser.getOptionValue(oWidth, null);      // Get by object reference.
      final Integer imageH = parser.getIntegerOptionValue("h", null);  // Get by short name.

      // Check for verbose option, and see how verbose user wants.
      // Get typed option value by long name.
      final List<Boolean> lVerbose = parser.getBooleanOptionValues("verbose");
      final int verbosity = lVerbose.size();
      if (verbosity > 0)
        System.out.printf("Width:%d, Height:%d%n", imageW, imageH);

      // Get folder.
      final String dir = parser.getStringOptionValue("dir", ".");

      // Get output image format.
      // EnumeratedString option is retrieved with string-typed method.
      final String format = parser.getStringOptionValue("format", "jpg");
      if (verbosity > 0)
        System.out.printf("Image format: %s%n", format);

      // Display arguments not related to any defined options.
      if (verbosity > 0)
      {
        for (String s : parser.getNonOptionArguments())
          System.out.printf("Unknown argument: %s%n", s);
      }

      // Output specified options.
      System.out.printf("width  : %d%n", imageW);
      System.out.printf("height : %d%n", imageH);
      System.out.printf("dir    : %s%n", dir);
      System.out.printf("format : %s%n", format);
      System.out.printf("verbose: %b (%d)%n", verbosity > 0, verbosity);
    }
    catch (OptionException ox)
    {
      try
      {
        // If help required, print usage message.
        if (parser.getBooleanOptionValue("?") || args.length == 0)
        {
          parser.printUsage(System.out, true, null, extraArgs, null);
          System.exit(0);
        }
        System.err.println(ox.getMessage());
        parser.printUsage(System.out, false, null, extraArgs, null);
        System.exit(1);
      }
      catch (OptionException ox2)
      {
        ox2.printStackTrace();
      }
    }
  }
}

Creating your own option types

JCLAP also makes it straightforward to provide your own custom option types, if the built-in ones are not sufficient for your needs. As an example of how to create your own, take a look at the source code for the snaq.util.jclap.LocalDateOption class shown below, which provides a basic implementation to parse a LocalDate value according to the default locale.

package snaq.util.jclap;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Locale;

/**
 * Option implementation for specifying date values.
 */
public class LocalDateOption extends Option<LocalDate>
{
  private DateTimeFormatter dtf = null;

  public LocalDateOption(String shortName, String longName, String description, boolean mandatory, boolean allowMany)
  {
    super(shortName, longName, description, true, mandatory, allowMany);
  }

  public LocalDateOption(String shortName, String longName, String description, int minCount, int maxCount)
  {
    super(shortName, longName, description, true, minCount, maxCount);
  }

  @Override
  public Class<LocalDate> getType()
  {
    return LocalDate.class;
  }

  protected LocalDate parseValue(String arg, Locale locale) throws OptionException
  {
    if (arg == null)
      throw new OptionException(OptionException.Type.ILLEGAL_OPTION_VALUE, this, arg);
    try
    {
      final DateTimeFormatter df = (dtf != null) ?
              dtf.withLocale(locale) :
              DateTimeFormatter.ISO_LOCAL_DATE.withLocale(locale);
      return LocalDate.parse(arg, df);
    }
    catch (DateTimeParseException px)
    {
      throw new OptionException(OptionException.Type.ILLEGAL_OPTION_VALUE, this).withCause(px);
    }
  }

  public void setDateFormat(DateTimeFormatter dtf)
  {
    this.dtf = dtf;
  }

  public DateTimeFormatter getDateFormat(Locale locale)
  {
    if (dtf != null)
      return dtf.withLocale(locale);
    return DateTimeFormatter.ISO_LOCAL_DATE.withLocale(locale);
  }
}

Notes

Strange characters on command-line

Some people are confused when trying to use "non-standard" (e.g. accented) characters in command-line applications, as non-English messages often show up with unexpected and/or garbled characters. Java supports the Unicode UTF-8 character set well, but many operating systems launch command-line utilities using the platform's default character/file encoding, and to complicate matters each terminal application has differing support for these encodings. These are some possible ways to help work around this:

Other libraries

JCLAP is by no means unique, and many similar utilities are available both for free and commercially. JCLAP was originally written to fulfil a specific need, then simply evolved as time passed, and it's proved useful in many applications. So many similar solutions now exist that it seems redundant to have yet another, but having already created JCLAP it seems beneficial to make it publicly available.

Unsurprisingly I take no responsibility for the quality, or lack thereof, of the software provided via the links above.


Change log

2020-09-12
(v2.2)
  • Fixed regression bug for parsing of concatenated boolean options.
  • Fixed regression bug for -- terminating option parsing.
2019-02-21
(v2.1)
  • Update to allow EnumeratedStringOption types to be specified by substring.
2018-12-04
(v2.0)
  • Updated to support Java 9+ as a module.
  • Added support for overriding LocalDateOption date format.
  • Changed to only show short option names in short usage message (can override if needed).
  • Bug fix for parsing grouped flag/value options.
  • Bug fix for case-sensitivity handling in EnumeratedStringOption.
  • Bug fix for possible newline display in some circumstances.
  • Bug fix for an occasional wrong parsing-related error message.
  • Improved documentation.
2017-10-26
(v1.5)
  • Updated to support Java 9 as an automatic module.
2015-10-01
(v1.4)
  • Updated to support Java 1.8 as minimum version.
  • Converted date option to use LocalDate instead of Date.
  • Better integration for date option.
2015-04-29
(v1.3)
  • Updated to support Java 1.7 as minimum version.
  • Added overloaded methods for defining options with minimum/maximum value counts.
2013-11-09
(v1.2)
  • Added overloaded methods for greater convenience of option retrieval.
  • Fixed bug with detection of external resources.
  • Fixed some resource translation errors.
2012-02-29
(v1.1.1)
  • Fixed bug with auto-detection of JAR file launch for usage message.
  • Fixed bug in error message display when parser option already in use.
  • Fixed a few small resource typos/problems.
2011-07-05
(v1.1)
  • Added ability to specify limits on option multiples.
  • Added basic language support for additional locales: fr/de/es/pt.