options, final String name,
+ final int currentLineNo)
+ throws ConfigurationException
+ {
+ if (!options.isEmpty())
+ {
+ // check to ensure we've not defined an option that has already
+ // been defined, and is defined after another option later on:
+ int index = options.size();
+ Object[] names = options.keySet().toArray();
+ String lastName = names[index - 1].toString();
+ if (!name.equals(lastName) && options.containsKey(name))
+ {
+ throw new ConfigurationException(currentLineNo,
+ "Bad configuration ordering; options must be"
+ + " grouped together. Option '" + name + "' has"
+ + " been defined prior to the declaration of option"
+ + " '" + lastName + "'");
+ }
+ }
+ }
+
+ /**
+ * Check that the option specified is of the correct type - short, long or
+ * both - according to what has been defined in the global configuration (if
+ * it has been defined), setting it if it hasn't and this is the first
+ * option in the configuration and the option conforms to the expected type.
+ *
+ * @param globalConfig non-{@code null} global configuration.
+ *
+ * @param expectedType non-{@code null} expected type.
+ *
+ * @param currentOption non-{@code null} current option being parsed that
+ * does not yet have the specified option set on it yet.
+ *
+ * @param option non-{@code null} option.
+ *
+ * @param currentLineNo current line number of where the data is encountered
+ * in the input stream.
+ *
+ * @throws ConfigurationException if the specified option does not match the
+ * global configuration's option type or the option formatting is incorrect
+ * (such as specifying a short option when the option type is long option).
+ */
+ private void checkCurrentOption(final GlobalConfiguration globalConfig,
+ final OptionsTypeEnum expectedType,
+ final OptionConfiguration currentOption, final String option,
+ final int currentLineNo)
+ throws ConfigurationException
+ {
+ final OptionsTypeEnum globalOptType = globalConfig.getOptionsType();
+ if (globalOptType != null && !(globalOptType.equals(expectedType)))
+ {
+ throw new ConfigurationException(currentLineNo,
+ "Configuration type specifies "
+ + GlobalConfiguration.OPTION_TYPE + " as "
+ + globalOptType.getType() + " but found "
+ + expectedType);
+ }
+ if (globalOptType == null)
+ {
+ globalConfig.setOptionsType(expectedType);
+ }
+ if (globalConfig.getOptionsType() == OptionsTypeEnum.BOTH)
+ {
+ final String forwardSlash = "/";
+ final Pattern p = Pattern.compile(SHORT_OPTION_FORMAT
+ + forwardSlash + LONG_OPTION_FORMAT);
+ final Matcher m = p.matcher(option);
+ if (m.matches())
+ {
+ currentOption.setShortOption(m.group(1));
+ currentOption.setLongOption(m.group(2));
+ }
+ else
+ {
+ throw new ConfigurationException(currentLineNo, "Invalid short and"
+ + " long option format; must be [character]/"
+ + " [text] but found " + option);
+ }
+ }
+ else if (globalConfig.getOptionsType() == OptionsTypeEnum.SHORT)
+ {
+ final Pattern p = Pattern.compile(SHORT_OPTION_FORMAT);
+ final Matcher m = p.matcher(option);
+ if (m.matches())
+ {
+ currentOption.setShortOption(m.group(1));
+ }
+ else
+ {
+ throw new ConfigurationException(currentLineNo, "Expected single"
+ + " character for short option but found " + option);
+ }
+ }
+ else
+ {
+ final Pattern p = Pattern.compile(LONG_OPTION_FORMAT);
+ final Matcher m = p.matcher(option);
+ if (m.matches())
+ {
+ currentOption.setLongOption(m.group(1));
+ }
+ else
+ {
+ throw new ConfigurationException(currentLineNo, "Expected text"
+ + " for long option but found " + option);
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/config/GlobalConfiguration.java b/src/main/java/org/apache/commons/cli/config/GlobalConfiguration.java
new file mode 100644
index 000000000..0eec47ff8
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/config/GlobalConfiguration.java
@@ -0,0 +1,414 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.commons.cli.config;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.apache.commons.cli.HelpFormatter;
+
+/**
+ * The global configuration is the configuration for an entire file with at
+ * least one {@link OptionConfiguration}.
+ *
+ *
+ * Lines beginning with # are ignored. Each global configuration can only be
+ * declared once; redeclaring a global option will produce an error.
+ *
+ * Global configuration items are as follows - the global options beginning with
+ * {@code HELP_} enable callers to print help linked to an option without any
+ * code whatsoever (more on this below):
+ *
+ *
+ *
+ * {@code OPTION_TYPE=[OPTION_TYPE]}: where {@code [OPTION_TYPE]} is one of
+ * {@code BOTH} where both short and long options are used, specified as
+ * {@code [char]/[text]}, {@code SHORT} where only short options are specified
+ * as a single character, and {@code LONG} where only long options are
+ * specified. However, {@code OPTION_TYPE} is not strictly required so long as
+ * the options specified are all consistent (although it aids readability for
+ * others maintaining the file to implicitly define {@code OPTION_TYPE});
+ *
+ * {@code HELP_OPTION_NAME=[optionName]}: where {@code [optionName]} is the
+ * name of an option defined in the options configuration further down in the
+ * options configuration (see {@link OptionConfiguration});
+ *
+ * {@code HELP_COMMAND_NAME=[commandName]}: The command name is the name of
+ * the command that will be printed when the "Usage: commandName..." is printed
+ * from a call to invoking the help option;
+ *
+ * {@code HELP_COMMAND_HEADER=[headerText]}: where {@code [headerText]} is
+ * the text that will be displayed as the header text from an invocation to call
+ * for help to be printed. The command header is optional; and
+ *
+ * {@code HELP_COMMAND_FOOTER=[footerText]}: where {@code [footerText]} is
+ * the text that will be displayed as the footer text from an invocation to call
+ * for help to be printed. Like the header, the command footer is optional.
+ *
+ *
+ *
+ * In all cases, lines may be escaped; escaped lines must end in a trailing
+ * backslash character; lines to be appended must be indented using white space
+ * (space character and/or tab characters). For example:
+ *
+ *
+ * HELP_COMMAND_HEADER=Show some useful information, \
+ * with some extra escaped lines. \
+ * Also, there's some extra information here about the command.
+ *
+ *
+ * Note in the above example the spaces before the backslashes - this is so
+ * sentences are not 'glued' together and provide spacing that is easy on the
+ * eye to readers of the output.
+ *
+ *
+ * Regardless of how the lines are escaped with regard to the number of
+ * characters per line, this will not affect the CLI help output since the CLI
+ * {@link HelpFormatter} will format this according to the API rules for line
+ * sizes.
+ *
+ *
+ * For example, by adding the following global options to the start of the
+ * example file defined in {@link OptionConfiguration}:
+ *
+ *
+ * # The following definition implies there must be an option.showHelp option
+ * # defined in the options following these global definitions:
+ * HELP_OPTION_NAME=showHelp
+ * HELP_COMMAND_NAME=writeData
+ * HELP_COMMAND_HEADER=Write the specified text to the specified file. If the \
+ * options contain spaces or special characters, supply the arguments in \
+ * double quotes.
+ * HELP_COMMAND_FOOTER=Copyleft Foo, Bar & Baz International.
+ *
+ *
+ *
+ * ... When invoking {@code --help} or {@code -h} via the
+ * {@link CommandLineConfiguration}:
+ *
+ *
+ * InputStream is = ConfigurationParserTest.class.getResourceAsStream("opt.config");
+ * CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ * cliConfig.addOptionListener(listener);
+ * // args[] from the public static void main(String[] args) call:
+ * cliConfig.process(is, args);
+ *
+ *
+ *
+ * ... Would produce the following output:
+ *
+ *
+ * usage: writeData
+ * Write the specified text to the specified file. If the options contain
+ * spaces or special characters, supply the arguments in double quotes.
+ * -f,--file <file> File to write to.
+ * -h,--help Print this help then quit.
+ * -o,--overwrite If set, write over the existing file; otherwise,
+ * append to the file.
+ * -t,--text <text> Text to write to the file.
+ * Copyleft Foo, Bar & Baz International.
+ *
+ *
+ *
+ * Callers are required to decide what to do when help is invoked in this
+ * manner; typically, in the {@link OptionListener}, callers will check for the
+ * call to help and then exit gracefully.
+ *
+ *
+ * The {@code HELP_} global configuration options are not mandatory and are
+ * there for convenience; however, configuration creators can omit these if they
+ * wish to define their own help (in which case, the {@link OptionListener} must
+ * cater for the call to help).
+ *
+ */
+public class GlobalConfiguration
+{
+
+ /**
+ * Regular expression to match global option configurations. The general
+ * form of a global configuration is upper case characters using underscores
+ * (if necessary) with the value separated by an equals symbol, with the
+ * value being any number of characters (with a minimum of one).
+ */
+ public static final String OPTION_REGEX = "([A-Z_]+)=(.+)";
+
+ /**
+ * Declaration for global option type (short, long, both).
+ */
+ public static final String OPTION_TYPE = "OPTION_TYPE";
+
+ /**
+ * Declaration for the name of the command that will be invoked to show help
+ * options.
+ */
+ public static final String HELP_COMMAND_NAME = "HELP_COMMAND_NAME";
+
+ /**
+ * Declaration for the header of the command that will be shown when
+ * invoking help.
+ */
+ public static final String HELP_COMMAND_HEADER = "HELP_COMMAND_HEADER";
+
+ /**
+ * Declaration for the footer of the command that will be shown when
+ * invoking help.
+ */
+ public static final String HELP_COMMAND_FOOTER = "HELP_COMMAND_FOOTER";
+
+ /**
+ * Declaration for the option name of that is defined in the
+ * {@link OptionConfiguration} such that when that CLI option is invoked,
+ * help will be printed.
+ */
+ public static final String HELP_OPTION_NAME = "HELP_OPTION_NAME";
+
+ /**
+ * The key is the actual name part of the {@code option.[name].*}
+ * declaration, in other words the option name.
+ */
+ private final Map optionMap = new LinkedHashMap<>();
+
+ /**
+ * The option type of the configuration.
+ */
+ private OptionsTypeEnum optionsType;
+
+ /**
+ * If the global configuration has help defined, this is the name of the
+ * command that will be printed with the help text.
+ */
+ private String helpCommandName;
+
+ /**
+ * If the global configuration has help defined, this is the header text of
+ * the command that will be printed with the help text.
+ */
+ private String helpCommandHeader;
+
+ /**
+ * If the global configuration has help defined, this is the footer text of
+ * the command that will be printed with the help text.
+ */
+ private String helpCommandFooter;
+
+ /**
+ * The option configuration name {@code option.[name]}, for example,
+ * {@code option.help}.
+ */
+ private String helpOptionName;
+
+ /**
+ * Update the given global configuration with the specified line data read.
+ *
+ * @param line non-{@code null} line data to parse that matches
+ * {@link #OPTION_REGEX}.
+ *
+ * @throws ConfigurationException if the configuration is defined
+ * incorrectly.
+ */
+ public void updateGlobalConfiguration(String line) throws ConfigurationException
+ {
+ String[] data = line.split("=");
+ if (data[0].trim().matches(GlobalConfiguration.OPTION_TYPE))
+ {
+ parseOptionType(data[1].trim());
+ }
+ else if (data[0].trim().startsWith("HELP"))
+ {
+ parseHelp(line);
+ }
+ else
+ {
+ throw new ConfigurationException(
+ "Unknown global configuration declaration: " + data[0].trim());
+ }
+ }
+
+ /**
+ * Get the name of the command that will be printed when (if) the user has
+ * defined help.
+ *
+ * @return the name of the command that the help displays information for.
+ */
+ public String getHelpCommandName()
+ {
+ return helpCommandName;
+ }
+
+ /**
+ * Get the help command header.
+ *
+ * @return the command header, if it has been set; {@code null} otherwise.
+ */
+ public String getHelpCommandHeader()
+ {
+ return helpCommandHeader;
+ }
+
+ /**
+ * Get the help command footer.
+ *
+ * @return the command footer, if it has been set; {@code null} otherwise.
+ */
+ public String getHelpCommandFooter()
+ {
+ return helpCommandFooter;
+ }
+
+ /**
+ * Get the option name specified by the global configuration; the name is
+ * the name of the {@link OptionConfiguration} that must exist in the option
+ * configurations.
+ *
+ * @return the help option name if it is set; {@code null} otherwise.
+ */
+ public String getHelpOptionName()
+ {
+ return helpOptionName;
+ }
+
+ /**
+ * Add the specified option configuration.
+ *
+ * @param optionConfig non-{@code null} option configuration to add.
+ */
+ public void addOptionConfiguration(OptionConfiguration optionConfig)
+ {
+ optionMap.put(optionConfig.getName(), optionConfig);
+ }
+
+ /**
+ * Get the option map for this configuration; the key to the map will be the
+ * option configuration names defined by the {@code option.[name]}
+ * declarations.
+ *
+ * @return the non-{@code null}, non-empty option map (note that if no
+ * option configurations are defined when parsing an exception will be
+ * thrown.
+ */
+ public Map getOptionConfigurations()
+ {
+ return Collections.unmodifiableMap(optionMap);
+ }
+
+ /**
+ * Get the options type for this global configuration.
+ *
+ * @return the options type; may be {@code null}.
+ */
+ public OptionsTypeEnum getOptionsType()
+ {
+ return optionsType;
+ }
+
+ /**
+ * Set the options type for this global configuration.
+ *
+ * @param optionsType the options type.
+ */
+ public void setOptionsType(OptionsTypeEnum optionsType)
+ {
+ this.optionsType = optionsType;
+ }
+
+ /**
+ * Parse the option type.
+ *
+ * @param data data containing the option type - one of
+ * {@link #GLOBAL_OPTION_TYPE_SHORT}, {@link #GLOBAL_OPTION_TYPE_LONG}, or
+ * {@link #GLOBAL_OPTION_TYPE_BOTH}.
+ *
+ * @throws ConfigurationException if the global options type has already
+ * been set, or if the options type did not match a known type.
+ */
+ private void parseOptionType(String data) throws ConfigurationException
+ {
+ if (getOptionsType() != null)
+ {
+ throw new ConfigurationException(OPTION_TYPE
+ + " has already been defined as "
+ + getOptionsType().getType()
+ + " but found second definition: " + data);
+ }
+ if (OptionsTypeEnum.BOTH.getType().equals(data))
+ {
+ setOptionsType(OptionsTypeEnum.BOTH);
+ }
+ else if (OptionsTypeEnum.SHORT.getType().equals(data))
+ {
+ setOptionsType(OptionsTypeEnum.SHORT);
+ }
+ else if (OptionsTypeEnum.LONG.getType().equals(data))
+ {
+ setOptionsType(OptionsTypeEnum.LONG);
+ }
+ else
+ {
+ throw new ConfigurationException("Unknown options type: " + data);
+ }
+ }
+
+ /**
+ * Parses global configurations that begin with {@code HELP_}.
+ *
+ * @param line non-{@code null} line to parse.
+ */
+ private void parseHelp(final String line) throws ConfigurationException
+ {
+ String[] data = line.split("=");
+ if (HELP_COMMAND_NAME.equals(data[0].trim()))
+ {
+ if (helpCommandName != null)
+ {
+ throw new ConfigurationException(HELP_COMMAND_NAME
+ + " has already been defined.");
+ }
+ helpCommandName = data[1].trim();
+ }
+ else if (GlobalConfiguration.HELP_COMMAND_HEADER.equals(data[0].trim()))
+ {
+ if (helpCommandHeader != null)
+ {
+ throw new ConfigurationException(HELP_COMMAND_HEADER
+ + " has already been defined.");
+ }
+ helpCommandHeader = data[1].trim();
+ }
+ else if (HELP_COMMAND_FOOTER.equals(data[0].trim()))
+ {
+ if (helpCommandFooter != null)
+ {
+ throw new ConfigurationException(HELP_COMMAND_FOOTER
+ + " has already been defined.");
+ }
+ helpCommandFooter = data[1].trim();
+ }
+ else if (HELP_OPTION_NAME.equals(data[0].trim()))
+ {
+ if (helpOptionName != null)
+ {
+ throw new ConfigurationException(HELP_OPTION_NAME
+ + " has already been defined.");
+ }
+ helpOptionName = data[1].trim();
+ }
+ else
+ {
+ throw new ConfigurationException("Unknown help configuration: " + line);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/config/OptionConfiguration.java b/src/main/java/org/apache/commons/cli/config/OptionConfiguration.java
new file mode 100644
index 000000000..e91ba6028
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/config/OptionConfiguration.java
@@ -0,0 +1,282 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.commons.cli.config;
+
+/**
+ * Contains information from a set of properties of the form
+ * {@code option.[name].*}. The following option values are supported:
+ *
+ *
+ * {@code option.[name].opts=[options]}: specify short and long options, a
+ * short option or a long option. For the first form, a single character is
+ * specified for the short option followed by a forward slash, followed by a
+ * text string of at least two characters for the long option. For the second
+ * form, a single character is specified for the short option. For the final
+ * form, text of at least two characters is specified for the long option. This
+ * is the only required configuration option.
+ *
+ *
+ * If the {@link GlobalConfiguration} {@code OPTION_TYPE} is not defined, all
+ * defined options must take the same form, otherwise an exception will the
+ * thrown. if the global configuration is defined then all options must conform
+ * to that type;
+ *
+ * {@code option.[name].hasArg=[true|false]}: if {@code true} then the
+ * argument will be supplied with a value via the command line; otherwise the
+ * option will not require a value. By default, this is {@code false} so is not
+ * required for options that do not require arguments although can be supplied
+ * for clarity;
+ * {@code option.[name].argName=[argName]}: used when displaying help. By
+ * default, this configuration is not required;
+ * {@code option.[name].description=[description]}: used when displaying
+ * help. By default this configuration is not required.
+ *
+ *
+ *
+ * The {@code [name]} section of the option is the name that
+ * {@link OptionListener}s will receive an update for via the {@code option}
+ * parameter via
+ * {@link OptionListener#option(java.lang.String, java.lang.Object)}; if the the
+ * argument's {@code hasArg} is {@code true}, then the {@code value} will be the
+ * value supplied via the command line; for arguments that have {@code hasArg}
+ * as {@code false}, {@code value} will be {@code null}.
+ *
+ *
+ * Lines beginning with # are ignored.
+ *
+ *
+ * In all cases, lines may be escaped; escaped lines must end in a trailing
+ * backslash character; lines to be appended must be indented using white space
+ * (space character and/or tab characters). This is especially useful when
+ * defining help descriptions. For example:
+ *
+ *
+ * option.file.description=Supply the output file to write results to. If the \
+ * file doesn't exist, it is created. If the file does exist, it is
+ * appended to.
+ *
+ *
+ * Note in the above example the spaces before the backslashes - this is so
+ * sentences are not 'glued' together and provide spacing that is easy on the
+ * eye to readers of the output.
+ *
+ *
+ * Options must be defined in groups - that is, once an option has been defined,
+ * it cannot have other values set on it (like description, argument name etc.)
+ * after another option has been defined. Options cannot have a value set on it
+ * more than once - so you cannot define the description twice, for example. In
+ * either case an exception will be thrown with the offending line and its line
+ * number.
+ *
+ *
+ * For example, a configuration file named {@code opt.config} could be created
+ * with the following option configuration (note that typically, creators of
+ * configurations will likely have the {@code name} and the long option text
+ * using the same text, although here they are different to add clarity to how
+ * the {@link OptionListener} is updated):
+ *
+ *
+ * option.outfile.opts=f/file
+ * option.outfile.hasArg=true
+ * option.outfile.argName=file
+ * option.outfile.description=File to write to.
+ *
+ * option.textToWrite.opts=t/text
+ * option.textToWrite.hasArg=true
+ * option.textToWrite.argName=text
+ * option.textToWrite.description=Text to write to the file.
+ *
+ * option.writeover.opts=o/overwrite
+ * # We do not need to specify this since all options are false for hasArg if not specified
+ * # option.writeover.hasArg=false
+ * option.writeover.description=If set, write over the existing file; \
+ * otherwise, append to the file.
+ *
+ * option.showHelp.opts=h/help
+ * option.showHelp.hasArg=false
+ * option.showHelp.description=Print this help then quit.
+ *
+ *
+ *
+ * ... When parsed by the {@link CommandLineConfiguration} with the value
+ * {@code --file datafile.txt} would update all registered
+ * {@link OptionListener}s via
+ * {@link OptionListener#option(java.lang.String, java.lang.Object)} and would
+ * receive an update with the option {@code file} given a value of
+ * {@code datafile.txt}.
+ */
+public class OptionConfiguration
+{
+
+ /**
+ * The name of the property; this is the name as it appears in the
+ * configuration after the {@code option.} declaration.
+ */
+ private String name;
+
+ /**
+ * The description for the option that will be displayed when help is
+ * displayed.
+ */
+ private String description;
+
+ /**
+ * The short-form option of the CLI declaration.
+ */
+ private String shortOption;
+
+ /**
+ * The long-form option of the CLI declaration.
+ */
+ private String longOption;
+
+ /**
+ * The argument name (if required).
+ */
+ private String argName;
+
+ /**
+ * Determines if this option configuration has an argument. The motivation
+ * for using {@code java.lang.Boolean} is that we need to determine if a
+ * configuration has been defined where the user defines hasArg twice for
+ * the same option. Only be having the option of having the argument as
+ * {@code null} can we check for this.
+ */
+ private Boolean hasArg;
+
+ /**
+ * Get the name.
+ *
+ * @return the name.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Set the name.
+ *
+ * @param name the name.
+ */
+ public void setName(final String name)
+ {
+ this.name = name;
+ }
+
+ /**
+ * Get the description.
+ *
+ * @return the description.
+ */
+ public String getDescription()
+ {
+ return description;
+ }
+
+ /**
+ * Set the description.
+ *
+ * @param description the description.
+ */
+ public void setDescription(final String description)
+ {
+ this.description = description;
+ }
+
+ /**
+ * Get the short option.
+ *
+ * @return the short option.
+ */
+ public String getShortOption()
+ {
+ return shortOption;
+ }
+
+ /**
+ * Set the short option.
+ *
+ * @param shortOption the short option.
+ */
+ public void setShortOption(final String shortOption)
+ {
+ this.shortOption = shortOption;
+ }
+
+ /**
+ * Get the long option.
+ *
+ * @return the long option.
+ */
+ public String getLongOption()
+ {
+ return longOption;
+ }
+
+ /**
+ * Set the long option.
+ *
+ * @param longOption the long option.
+ */
+ public void setLongOption(String longOption)
+ {
+ this.longOption = longOption;
+ }
+
+ /**
+ * Get the argument name.
+ *
+ * @return the argument name.
+ */
+ public String getArgName()
+ {
+ return argName;
+ }
+
+ /**
+ * Set the argument name.
+ *
+ * @param argName the argument name.
+ */
+ public void setArgName(final String argName)
+ {
+ this.argName = argName;
+ }
+
+ /**
+ * Determine if this option has an argument.
+ *
+ * @return {@code true} if the option takes an argument, {@code false}
+ * if it doesn't; {@code null} if the argument has never been set.
+ */
+ public Boolean hasArg()
+ {
+ return hasArg;
+ }
+
+ /**
+ * Set that the option has an argument.
+ *
+ * @param hasArg {@code true} if the option takes an argument; {@code false}
+ * otherwise.
+ */
+ public void setHasArg(final boolean hasArg)
+ {
+ this.hasArg = hasArg;
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/config/OptionListener.java b/src/main/java/org/apache/commons/cli/config/OptionListener.java
new file mode 100644
index 000000000..7d49b0737
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/config/OptionListener.java
@@ -0,0 +1,100 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.commons.cli.config;
+
+/**
+ * Listener for receiving updates from the process of parsing a option
+ * configuration.
+ *
+ *
+ * An example option configuration is defined in {@link OptionConfiguration} and
+ * how this is processed is explained in {@link CommandLineConfiguration}. Once
+ * the call to
+ * {@link CommandLineConfiguration#process(java.io.InputStream, java.lang.String, java.lang.String[])}
+ * has been invoked, all registered listeners will have received all necessary
+ * updates from the command line. All that is left to do now is do something
+ * useful with the supplied arguments.
+ *
+ *
+ * In the following example, updating of the options and then doing something
+ * with them is all dealt with in the {@link OptionListener}; however in
+ * principle this can easily be split into different classes such that the
+ * {@link OptionListener} contains the values updated via the command line which
+ * a separate class then utilises to do something useful with.
+ *
+ *
+ * Continuing from the configuration defined in {@link OptionConfiguration} and
+ * the call to process the file defined in {@link CommandLineConfiguration},
+ * let's take a look at an example implementation of {@code MyAppListener}
+ * within the {@code option(String, Object)} method - members are {@code public}
+ * so that once processing of arguments is complete, external classes can obtain
+ * the values read from the command line:
+ *
+ * public File outFile;
+ *
+ * public String text;
+ *
+ * public boolean overwrite;
+ *
+ * @Override
+ * public void option(final String option, final Object value)
+ * {
+ * if ("help".equals(option))
+ * {
+ * System.exit(0);
+ * }
+ * else if ("file".equals(option))
+ * {
+ * outFile = new File(value.toString());
+ * }
+ * else if ("text".equals(option))
+ * {
+ * text = value.toString();
+ * }
+ * else if ("overwrite".equals(option))
+ * {
+ * overwrite = true;
+ * }
+ * }
+ *
+ *
+ *
+ * Note that since the example option configuration defines both short and long
+ * options, the listener will still receive an update in the above code for the
+ * {@code "file".equals(option)} test even if the user specified {@code -f} via
+ * the command line. Therefore it is up to listeners of configurations of both
+ * short and long options to decide which to cater for; in general long options
+ * aid readability in source files compared to single characters.
+ */
+public interface OptionListener
+{
+
+ /**
+ * Update with the specified option and it's value (if it has one). Note
+ * that the option will be a short option if only short options are defined,
+ * a long option if only long options are defined, or both if both short and
+ * long options are defined. In the latter case listeners will receive two
+ * updates and can decide which form to cater for; in general long options
+ * aid readability in source files compared to single characters.
+ *
+ * @param option non-{@code null} option, either in short form or long form.
+ *
+ * @param value the value of the argument; for options that do not have an
+ * argument, the value will be {@code null}.
+ */
+ void option(final String option, final Object value);
+}
diff --git a/src/main/java/org/apache/commons/cli/config/OptionsTypeEnum.java b/src/main/java/org/apache/commons/cli/config/OptionsTypeEnum.java
new file mode 100644
index 000000000..9436ba48d
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/config/OptionsTypeEnum.java
@@ -0,0 +1,62 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.commons.cli.config;
+
+/**
+ * Defines if the configuration takes a short, long or both options.
+ */
+public enum OptionsTypeEnum
+{
+
+ /**
+ * Both options used - short and long.
+ */
+ BOTH("BOTH"),
+ /**
+ * Short options only.
+ */
+ SHORT("SHORT"),
+ /**
+ * Long options only.
+ */
+ LONG("LONG");
+
+ /**
+ * One of BOTH, SHORT or LONG.
+ */
+ private final String type;
+
+ /**
+ * Create a new options type.
+ *
+ * @param type the name of the options type.
+ */
+ private OptionsTypeEnum(final String type)
+ {
+ this.type = type;
+ }
+
+ /**
+ * Get the type of the option.
+ *
+ * @return the option type.
+ */
+ public String getType()
+ {
+ return type;
+ }
+}
diff --git a/src/main/java/org/apache/commons/cli/config/package-info.java b/src/main/java/org/apache/commons/cli/config/package-info.java
new file mode 100644
index 000000000..22e406674
--- /dev/null
+++ b/src/main/java/org/apache/commons/cli/config/package-info.java
@@ -0,0 +1,22 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Apache Commons CLI config provides a means to reducing the amount of code written by defining CLI options via a configuration file.
+ */
+package org.apache.commons.cli.config;
+
diff --git a/src/test/java/org/apache/commons/cli/config/AbstractTestConfig.java b/src/test/java/org/apache/commons/cli/config/AbstractTestConfig.java
new file mode 100644
index 000000000..a35032f4f
--- /dev/null
+++ b/src/test/java/org/apache/commons/cli/config/AbstractTestConfig.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.commons.cli.config;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ */
+public abstract class AbstractTestConfig
+{
+
+ /**
+ *
+ */
+ private final Map options = new HashMap();
+
+ /**
+ *
+ * @return
+ */
+ public Map getOptions()
+ {
+ return Collections.unmodifiableMap(options);
+ }
+
+ /**
+ *
+ * @param option
+ * @param value
+ * @return
+ */
+ public String addOption(String option, String value)
+ {
+ return options.put(option, value);
+ }
+}
diff --git a/src/test/java/org/apache/commons/cli/config/CommandLineConfigurationTest.java b/src/test/java/org/apache/commons/cli/config/CommandLineConfigurationTest.java
new file mode 100644
index 000000000..1b9706f6f
--- /dev/null
+++ b/src/test/java/org/apache/commons/cli/config/CommandLineConfigurationTest.java
@@ -0,0 +1,386 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.commons.cli.config;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.List;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.UnrecognizedOptionException;
+import org.junit.After;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.junit.Before;
+
+/**
+ *
+ */
+public class CommandLineConfigurationTest
+{
+
+ /**
+ * Test that once the configuration is parsed, the command line
+ * configuration is created with the specified arguments and the listener
+ * has been updated with the values specified by the CLI call. Since the
+ * configuration is of type 'both' because options are specified as
+ * char/string, we expect both short and long options to have been updated
+ * in the listener.
+ */
+ @Test
+ public void testProcessShortAndLongOptions() throws Exception
+ {
+ ConfigListener listener = new ConfigListener();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_001_short_and_long_options.conf");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(listener);
+ String[] arguments = "-F -h 192.168.1.2 -p 80 -P /tmp".split(" ");
+ cliConfig.process(is, "UTF-8", arguments);
+ // short options:
+ assertEquals("80", listener.getOptions().get("p"));
+ assertEquals("192.168.1.2", listener.getOptions().get("h"));
+ assertEquals("/tmp", listener.getOptions().get("P"));
+ assertTrue(listener.getOptions().containsKey("F"));
+ // long options:
+ assertEquals("80", listener.getOptions().get("port"));
+ assertEquals("192.168.1.2", listener.getOptions().get("host"));
+ assertEquals("/tmp", listener.getOptions().get("path"));
+ assertTrue(listener.getOptions().containsKey("fail"));
+ }
+
+ /**
+ * Test of removeOptionListener method, ensuring the listener is not updated
+ * once the process is invoked.
+ */
+ @Test
+ public void testRemoveOptionListener() throws Exception
+ {
+ ConfigListener listener = new ConfigListener();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_001_short_and_long_options.conf");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(listener);
+ String[] arguments = "-F -h 192.168.1.2 -p 80 -P /tmp".split(" ");
+ cliConfig.removeOptionListener(listener);
+ cliConfig.process(is, "UTF-8", arguments);
+ // short options:
+ assertEquals(null, listener.getOptions().get("p"));
+ assertEquals(null, listener.getOptions().get("h"));
+ assertEquals(null, listener.getOptions().get("P"));
+ assertFalse(listener.getOptions().containsKey("F"));
+ // long options:
+ assertEquals(null, listener.getOptions().get("port"));
+ assertEquals(null, listener.getOptions().get("host"));
+ assertEquals(null, listener.getOptions().get("path"));
+ assertFalse(listener.getOptions().containsKey("fail"));
+ }
+
+ /**
+ * Test that once the configuration is parsed, the command line
+ * configuration is created with the specified arguments and the listener
+ * has been updated with the values specified by the CLI call. Since the
+ * configuration is of type 'short' because options are specified as a char,
+ * we expect only short options to have been updated in the listener.
+ */
+ @Test
+ public void testProcessShortOptionsConfig() throws Exception
+ {
+ ConfigListener listener = new ConfigListener();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_002_short_options.conf");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(listener);
+ String[] arguments = "-F -h 192.168.1.2 -p 80 -P /tmp".split(" ");
+ cliConfig.process(is, "UTF-8", arguments);
+ // short options:
+ assertEquals("80", listener.getOptions().get("p"));
+ assertEquals("192.168.1.2", listener.getOptions().get("h"));
+ assertEquals("/tmp", listener.getOptions().get("P"));
+ assertTrue(listener.getOptions().containsKey("F"));
+ // these long options should not be set:
+ assertEquals(null, listener.getOptions().get("port"));
+ assertEquals(null, listener.getOptions().get("host"));
+ assertEquals(null, listener.getOptions().get("path"));
+ assertFalse(listener.getOptions().containsKey("fail"));
+ }
+
+ /**
+ * Test that once the configuration is parsed, the command line
+ * configuration is created with the specified arguments and the listener
+ * has been updated with the values specified by the CLI call. Since the
+ * configuration is of type 'long' because options are specified as a
+ * string, we expect only long options to have been updated in the listener.
+ */
+ @Test
+ public void testProcessLongOptionsConfig() throws Exception
+ {
+ ConfigListener listener = new ConfigListener();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_003_long_options.conf");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(listener);
+ String[] arguments = "--fail --host 192.168.1.2 --port 80 --path /tmp".split(" ");
+ cliConfig.process(is, "UTF-8", arguments);
+ // long options:
+ assertEquals("80", listener.getOptions().get("port"));
+ assertEquals("192.168.1.2", listener.getOptions().get("host"));
+ assertEquals("/tmp", listener.getOptions().get("path"));
+ assertTrue(listener.getOptions().containsKey("fail"));
+ // these short options should not be set:
+ assertEquals(null, listener.getOptions().get("p"));
+ assertEquals(null, listener.getOptions().get("h"));
+ assertEquals(null, listener.getOptions().get("P"));
+ assertFalse(listener.getOptions().containsKey("F"));
+ }
+
+ /**
+ * Test that once the configuration is parsed, the command line
+ * configuration is created with the specified arguments and the listener
+ * has been updated with the values specified by the CLI call. Since the
+ * configuration is of type 'both' because options are specified as
+ * char/string, we expect both short and long options to have been updated
+ * in the listener.
+ */
+ @Test
+ public void testGetOptions() throws Exception
+ {
+ ConfigListener listener = new ConfigListener();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_001_short_and_long_options.conf");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(listener);
+ String[] arguments = "-F -h 192.168.1.2 -p 80 -P /tmp".split(" ");
+ cliConfig.process(is, "UTF-8", arguments);
+ List options = cliConfig.getOptions();
+ // short options:
+ assertEquals("80", getOptionValue(options, "p"));
+ assertEquals("192.168.1.2", getOptionValue(options, "h"));
+ assertEquals("/tmp", getOptionValue(options, "P"));
+ assertNotNull(getOptionValue(options, "F"));
+ }
+
+ /**
+ * Test of process method, ensuring that when an unknown option is provided,
+ * the appropriate exception is thrown.
+ */
+ @Test
+ public void testProcessFailsParseException() throws Exception
+ {
+ ConfigListener listener = new ConfigListener();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_001_short_and_long_options.conf");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(listener);
+ String[] arguments = "--no-such-option -F -h 192.168.1.2".split(" ");
+ try
+ {
+ cliConfig.process(is, "UTF-8", arguments);
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains("Unrecognized option"));
+ assertEquals(UnrecognizedOptionException.class,
+ ex.getCause().getClass());
+ }
+ }
+
+ /**
+ * Test that invoking help prints the help then quits with exit status 0.
+ */
+ @Test
+ public void testProcessPrintHelpShortOption() throws Exception
+ {
+ ConfigListener listener = new ConfigListener();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_015_print_short_option_help.conf");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(listener);
+ String[] arguments = "-h".split(" ");
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(os);
+ System.setOut(ps);
+ cliConfig.process(is, "UTF-8", arguments);
+ String output = os.toString("UTF8");
+ assertTrue(output.contains("Print this then quit."));
+ assertTrue(output.contains("Fail if no connection made, rather than retrying."));
+ assertTrue(output.contains("Specify the host; optional. Use localhost if not set."));
+ assertTrue(output.contains(" Protocol is optional, assumes HTTP."));
+ assertTrue(output.contains("Port number to use. Required."));
+ ps.close();
+ os.close();
+ }
+
+ /**
+ * Test that specifying auto-print help via the command line configuration
+ * without the arguments containing the help option does not print the help.
+ */
+ @Test
+ public void testProcessDoesNotPrintHelp() throws Exception
+ {
+ ConfigListener listener = new ConfigListener();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_015_print_short_option_help.conf");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(listener);
+ String[] arguments = "-f -H localhost".split(" ");
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(os);
+ System.setOut(ps);
+ cliConfig.process(is, "UTF-8", arguments);
+ String output = os.toString("UTF8");
+ assertFalse(output.contains("Print this then quit."));
+ assertFalse(output.contains("Fail if no connection made, rather than retrying."));
+ assertFalse(output.contains("Specify the host; optional. Use localhost if not set."));
+ assertFalse(output.contains(" Protocol is optional, assumes HTTP."));
+ assertFalse(output.contains("Port number to use. Required."));
+ ps.close();
+ os.close();
+ }
+
+ /**
+ * Test that invoking help prints the help then quits with exit status 0.
+ */
+ @Test
+ public void testProcessPrintHelpLongOption() throws Exception
+ {
+ ConfigListener listener = new ConfigListener();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_016_print_long_option_help.conf");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(listener);
+ String[] arguments = "--help".split(" ");
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(os);
+ System.setOut(ps);
+ cliConfig.process(is, "UTF-8", arguments);
+ String output = os.toString("UTF8");
+ assertTrue(output.contains("Print this then quit."));
+ assertTrue(output.contains("Fail if no connection made, rather than retrying."));
+ assertTrue(output.contains("Specify the host; optional. Use localhost if not set."));
+ assertTrue(output.contains(" Protocol is optional, assumes HTTP."));
+ assertTrue(output.contains("Port number to use. Required."));
+ ps.close();
+ os.close();
+ }
+
+ /**
+ * Test that invoking help when hasArg = true throws an error.
+ */
+ @Test
+ public void testProcessPrintHelpHasArgThrowsError() throws Exception
+ {
+ ConfigListener listener = new ConfigListener();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_017_bad_help_option.conf");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(listener);
+ String[] arguments = "--help foobarbaz".split(" ");
+ try
+ {
+ cliConfig.process(is, "UTF-8", arguments);
+ fail("Expected exception");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertEquals("Error: Option help cannot have an argument"
+ + " associated with it.", ex.getMessage());
+ }
+ }
+
+ /**
+ * Test that the specified header and footer are included.
+ */
+ @Test
+ public void testHelpHeaderFooter() throws Exception
+ {
+ ConfigListener listener = new ConfigListener();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_018_header_and_footer.conf");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(listener);
+ String[] arguments = "--help".split(" ");
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(os);
+ System.setOut(ps);
+ cliConfig.process(is, "UTF-8", arguments);
+ String output = os.toString("UTF8");
+ assertTrue(output.contains("foo_command"));
+ assertTrue(output.contains("Show some useful information"));
+ assertTrue(output.contains("Copyright Apache Software Foundation"));
+ ps.close();
+ os.close();
+ }
+
+ /**
+ * Test that the specified escaped header and footer are included.
+ */
+ @Test
+ public void testHelpHeaderFooterEscaped() throws Exception
+ {
+ ConfigListener listener = new ConfigListener();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_019_header_and_footer_escaped.conf");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(listener);
+ String[] arguments = "--help".split(" ");
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(os);
+ System.setOut(ps);
+ cliConfig.process(is, "UTF-8", arguments);
+ String output = os.toString("UTF8");
+ assertTrue(output.contains("foo_command"));
+ assertTrue(output.contains("Show some useful information, with some"
+ + " extra escaped lines"));
+ assertTrue(output.contains("Copyright Apache Software Foundation"
+ + " Submit escaped lines to System.out()"));
+ }
+
+ /**
+ * Get the option value from the list where the option name equals the
+ * specified key.
+ *
+ * @param options non-{@code null}, non-empty option list.
+ *
+ * @param key non-{@code null} key to search for.
+ *
+ * @return the option value if it could be retrieved, or the empty string if
+ * the option does not have an argument; {@code null} otherwise.
+ */
+ private String getOptionValue(final List options, String key)
+ {
+ String result = null;
+ for (Option option : options)
+ {
+ if (key.equals(option.getOpt()))
+ {
+ if (option.hasArg())
+ {
+ result = option.getValue();
+ }
+ else
+ {
+ result = "";
+ }
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/cli/config/ConfigListener.java b/src/test/java/org/apache/commons/cli/config/ConfigListener.java
new file mode 100644
index 000000000..e4aad482b
--- /dev/null
+++ b/src/test/java/org/apache/commons/cli/config/ConfigListener.java
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.commons.cli.config;
+
+/**
+ * Class for test classes to utilise; they add this listener before they make
+ * any calls to
+ * {@link CommandLineConfig#process(java.io.InputStream, java.lang.String[])}
+ * and interrogate this listener using {@link AbstractTestConfig#getOptions()}
+ * after processing has occurred, to determine that the specified values were
+ * registered during the parsing process.
+ */
+public class ConfigListener extends AbstractTestConfig
+ implements OptionListener
+{
+
+ /**
+ * Callers implementing this would normally set members or take actions when
+ * receiving updates.
+ *
+ * @param option non-{@code null} command line option updated from the
+ * result of the command line parsing process.
+ *
+ * @param value value of the command line value, if the command line option
+ * has an argument; {@code null} otherwise (and implies the command line
+ * option does not have an argument).
+ */
+ @Override
+ public void option(String option, Object value)
+ {
+ String optValue = null;
+ if (value != null)
+ {
+ optValue = value.toString();
+ /*
+ e.g.
+ if ("host".equals(option)) {
+ server.setHostname(optValue);
+ }
+ */
+ }
+ /*
+ Else if value is false, implies option doesn't have an argument, e.g
+
+ if ("fail".equals(option)) {
+ server.setFail(true);
+ }
+ */
+ addOption(option, optValue);
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/cli/config/ConfigurationParserTest.java b/src/test/java/org/apache/commons/cli/config/ConfigurationParserTest.java
new file mode 100644
index 000000000..1b42061d2
--- /dev/null
+++ b/src/test/java/org/apache/commons/cli/config/ConfigurationParserTest.java
@@ -0,0 +1,753 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.commons.cli.config;
+
+import java.io.InputStream;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import static org.apache.commons.cli.config.GlobalConfiguration.HELP_COMMAND_NAME;
+import static org.apache.commons.cli.config.GlobalConfiguration.OPTION_TYPE;
+import org.junit.After;
+import org.junit.AfterClass;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class ConfigurationParserTest
+{
+
+ public ConfigurationParserTest()
+ {
+ }
+
+ @BeforeClass
+ public static void setUpClass()
+ {
+ }
+
+ @AfterClass
+ public static void tearDownClass()
+ {
+ }
+
+ @Before
+ public void setUp()
+ {
+ }
+
+ @After
+ public void tearDown()
+ {
+ }
+
+ /**
+ * Test of parse method, of class ConfigurationParser.
+ */
+ @Test
+ public void testParseOpts() throws Exception
+ {
+ String text = "option.fail.opts=F/fail";
+ Pattern p = Pattern.compile(ConfigurationParser.OPTION_REGEX_BASIC_LINE);
+ Matcher m = p.matcher(text);
+ assertTrue(m.matches());
+ assertEquals("fail", m.group(1));
+ assertEquals("opts", m.group(2));
+ assertEquals("F/fail", m.group(3));
+ }
+
+ /**
+ * Test of parse method, of class ConfigurationParser.
+ */
+ @Test
+ public void testParseHasArg() throws Exception
+ {
+ String text = "option.fail.hasArg=true";
+ Pattern p = Pattern.compile(ConfigurationParser.OPTION_REGEX_BASIC_LINE);
+ Matcher m = p.matcher(text);
+ assertTrue(m.matches());
+ assertEquals("fail", m.group(1));
+ assertEquals("hasArg", m.group(2));
+ assertEquals("true", m.group(3));
+ }
+
+ /**
+ * Test of parse method, of class ConfigurationParser.
+ */
+ @Test
+ public void testParseDescription() throws Exception
+ {
+ String text = "option.fail.description=fail gracefully.";
+ Pattern p = Pattern.compile(ConfigurationParser.OPTION_REGEX_BASIC_LINE);
+ Matcher m = p.matcher(text);
+ assertTrue(m.matches());
+ assertEquals("fail", m.group(1));
+ assertEquals("description", m.group(2));
+ assertEquals("fail gracefully.", m.group(3));
+ }
+
+ /**
+ * Test of parse method, of class ConfigurationParser.
+ */
+ @Test
+ public void testParseDescriptionEscaped() throws Exception
+ {
+ String text = "option.fail.description=fail gracefully, \\";
+ Pattern p = Pattern.compile(ConfigurationParser.OPTION_REGEX_BASIC_LINE);
+ Matcher m = p.matcher(text);
+ assertTrue(m.matches());
+ assertEquals("fail", m.group(1));
+ assertEquals("description", m.group(2));
+ assertEquals("fail gracefully, \\", m.group(3));
+ }
+
+ /**
+ * Test of parse method, of class ConfigurationParser.
+ */
+ @Test
+ public void testParseInputStream() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_001_short_and_long_options.conf");
+ GlobalConfiguration globalConfig = configParser.parse(is, "UTF-8");
+ Map optionConfig = globalConfig.getOptionConfigurations();
+ is.close();
+ OptionConfiguration optConfig = optionConfig.get("fail");
+ checkOptionConfiguration(optConfig, "fail", "F", "fail",
+ "Fail if no connection made, rather than retrying.", false);
+ optConfig = optionConfig.get("host");
+ checkOptionConfiguration(optConfig, "host", "h", "host",
+ "Specify the host; optional. Use localhost if not set."
+ + " Protocol is optional, assumes HTTP.", true);
+ optConfig = optionConfig.get("port");
+ checkOptionConfiguration(optConfig, "port", "p", "port",
+ "Port number to use. Required.", true);
+ optConfig = optionConfig.get("path");
+ checkOptionConfiguration(optConfig, "path", "P", "path",
+ "Comma separated list of paths to test on the server."
+ + " If spaces are contained within the arguments, surround with"
+ + " double quotes. Have as many lines as required, so long as"
+ + " they are escaped.", true);
+ }
+
+ /**
+ * Test of parse method, of class ConfigurationParser.
+ */
+ @Test
+ public void testParseInputStreamShortOption() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_002_short_options.conf");
+ GlobalConfiguration globalConfig = configParser.parse(is, "UTF-8");
+ Map optionConfig = globalConfig.getOptionConfigurations();
+ is.close();
+ OptionConfiguration optConfig = optionConfig.get("fail");
+ checkOptionConfiguration(optConfig, "fail", "F", null,
+ "Fail if no connection made, rather than retrying.", false);
+ optConfig = optionConfig.get("host");
+ checkOptionConfiguration(optConfig, "host", "h", null,
+ "Specify the host; optional. Use localhost if not set."
+ + " Protocol is optional, assumes HTTP.", true);
+ optConfig = optionConfig.get("port");
+ checkOptionConfiguration(optConfig, "port", "p", null,
+ "Port number to use. Required.", true);
+ optConfig = optionConfig.get("path");
+ checkOptionConfiguration(optConfig, "path", "P", null,
+ "Comma separated list of paths to test on the server."
+ + " If spaces are contained within the arguments, surround with"
+ + " double quotes. Have as many lines as required, so long as"
+ + " they are escaped.", true);
+ }
+
+ /**
+ * Test of parse method, of class ConfigurationParser.
+ */
+ @Test
+ public void testParseInputStreamLongOption() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_003_long_options.conf");
+ GlobalConfiguration globalConfig = configParser.parse(is, "UTF-8");
+ Map optionConfig = globalConfig.getOptionConfigurations();
+ is.close();
+ OptionConfiguration optConfig = optionConfig.get("fail");
+ checkOptionConfiguration(optConfig, "fail", null, "fail",
+ "Fail if no connection made, rather than retrying.", false);
+ optConfig = optionConfig.get("host");
+ checkOptionConfiguration(optConfig, "host", null, "host",
+ "Specify the host; optional. Use localhost if not set."
+ + " Protocol is optional, assumes HTTP.", true);
+ optConfig = optionConfig.get("port");
+ checkOptionConfiguration(optConfig, "port", null, "port",
+ "Port number to use. Required.", true);
+ optConfig = optionConfig.get("path");
+ checkOptionConfiguration(optConfig, "path", null, "path",
+ "Comma separated list of paths to test on the server."
+ + " If spaces are contained within the arguments, surround with"
+ + " double quotes. Have as many lines as required, so long as"
+ + " they are escaped.", true);
+ }
+
+ /**
+ * Test that an invalid mix of characters when not using option type BOTH
+ * (so having a separate long option and short option) throws an exception.
+ */
+ @Test
+ public void testParseInputStreamBadOptionMix() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_004_bad_short_long_mix.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains("Configuration type specifies"
+ + " OPTION_TYPE as LONG but found SHORT"));
+ }
+ }
+
+ /**
+ * Test that empty opts value throws an exception.
+ */
+ @Test
+ public void testParseInputStreamZeroLengthOption() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_005_empty_opts_value.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains("Empty option value; must be a"
+ + " non-zero length string"));
+ }
+ }
+
+ /**
+ * Test that having no options at all throws an exception.
+ */
+ @Test
+ public void testParseInputStreamNoOptionsAtAll() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_006_no_options.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains("The configuration file"
+ + " contained no options to parse"));
+ }
+ }
+
+ /**
+ * Test that an unknown sub-option throws an exception.
+ */
+ @Test
+ public void testParseInputStreamBadSubOptionName() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_007_unknown_config_option.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains(
+ "Unknown configuration option: "));
+ }
+ }
+
+ /**
+ * Test that having no white space at the start of a succeeding line
+ * following in from an escaped line throws an exception.
+ */
+ @Test
+ public void testParseInputStreamBadEscapedLine() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_008_invalid_escaped_line.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains("Invalid escaped line: "));
+ }
+ }
+
+ /**
+ * Test that using type BOTH when not formatted correctly throws an
+ * exception.
+ */
+ @Test
+ public void testParseInputStreamBadLongShortOptionFormat() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_009_invalid_short_long_format.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains("Invalid short and"
+ + " long option format; must be [character]/"
+ + " [text] but found "));
+ }
+ }
+
+ /**
+ * Test that an invalid character for opts throws an exception for SHORT
+ * option.
+ */
+ @Test
+ public void testParseInputStreamBadShortOptionFormat() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_010_invalid_short_format.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains("Expected single"
+ + " character for short option but found "));
+ }
+ }
+
+ /**
+ * Test that invalid characters for LONG option fails.
+ */
+ @Test
+ public void testParseInputStreamBadLongOptionFormat() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_011_invalid_long_option_format.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains("Expected text"
+ + " for long option but found "));
+ }
+ }
+
+ /**
+ * Test that completely invalid option throws an exception.
+ */
+ @Test
+ public void testParseInputStreamBadLineOptionFormat() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_012_invalid_option_definition.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains("Invalid option definition: "));
+ }
+ }
+
+ /**
+ * Test that when the user defines a correct option, if a succeeding option
+ * contains an empty name, the error message will also inform them of what
+ * option type they're using (short, long, both).
+ */
+ @Test
+ public void testParseInputStreamZeroLength2ndOption() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_013_empty_option_value.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains("Empty option value; must be a"
+ + " non-zero length string; global configuration is defined as "));
+ }
+ }
+
+ /**
+ * Test that a repeated global option declaration throws the appropriate
+ * exception.
+ */
+ @Test
+ public void testParseInputStreamRepeatedGlobalConfig() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_014_option_type_defined_twice.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains(OPTION_TYPE
+ + " has already been defined as "
+ + OptionsTypeEnum.BOTH.getType()
+ + " but found second definition: "
+ + OptionsTypeEnum.BOTH.getType()));
+ }
+ }
+
+ /**
+ * Test that a global option defined after common "option." options throws
+ * an exception.
+ */
+ @Test
+ public void testParseInputStreamBadGlobalConfiguration() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_020_bad_global_config_order.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains("Invalid global"
+ + " configuration definition; global configurations"
+ + " must come BEFORE standard \"option...\" definitions"));
+ }
+ }
+
+ /**
+ * Test that a redefinition of a long option throws an exception.
+ */
+ @Test
+ public void testParseInputStreamReDefinitionLongOption() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_021_redefinition_long_option.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains(
+ "opts has already been defined for option help"));
+ }
+ }
+
+ /**
+ * Test that a redefinition of an option's description throws an exception.
+ */
+ @Test
+ public void testParseInputStreamDescriptionRedefinition() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_022_redefinition_description.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains(
+ "description has already been defined for option help"));
+ }
+ }
+
+ /**
+ * Test that a redefinition of an option's has argument throws an exception.
+ */
+ @Test
+ public void testParseInputStreamHasArgRedefinition() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_023_redefinition_hasArg.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains(
+ "hasArg has already been defined for option help"));
+ }
+ }
+
+ /**
+ * Test that a redefinition of an option's argument name throws an
+ * exception.
+ */
+ @Test
+ public void testParseInputStreamArgNameRedefinition() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_024_redefinition_argName.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains(
+ "argName has already been defined for option fubar"));
+ }
+ }
+
+ /**
+ * Test that a redefinition of a short option throws an exception.
+ */
+ @Test
+ public void testParseInputStreamReDefinitionShortOption() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_025_redefinition_short_option.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ assertTrue(ex.getMessage().contains(
+ " has already been defined for option help"));
+ }
+ }
+
+ /**
+ * Test that an option that is redefined later in the file throws an
+ * exception.
+ */
+ @Test
+ public void testParseInputStreamOptionsBadOrdering() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_026_redefinition_bad_ordering.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ System.out.println(ex.getMessage());
+ assertTrue(ex.getMessage().contains("Bad configuration ordering;"
+ + " options must be grouped together. Option 'dir' has"
+ + " been defined prior to the declaration of option"
+ + " 'file'"));
+ }
+ }
+
+ /**
+ * Test that an option that is redefined later in the file throws an
+ * exception.
+ */
+ @Test
+ public void testParseInputStreamHelpCommandDefinedTwice() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_027_help_command_defined_twice.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ System.out.println(ex.getMessage());
+ assertTrue(ex.getMessage().contains(
+ GlobalConfiguration.HELP_COMMAND_NAME
+ + " has already been defined."));
+ }
+ }
+
+ /**
+ * Test that an option that is redefined later in the file throws an
+ * exception.
+ */
+ @Test
+ public void testParseInputStreamHelpOptionNameDefinedTwice() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_028_help_option_defined_twice.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ System.out.println(ex.getMessage());
+ assertTrue(ex.getMessage().contains(
+ GlobalConfiguration.HELP_OPTION_NAME
+ + " has already been defined."));
+ }
+ }
+
+ /**
+ * Test that an option that is redefined later in the file throws an
+ * exception.
+ */
+ @Test
+ public void testParseInputStreamHelpFooterDefinedTwice() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_029_help_footer_defined_twice.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ System.out.println(ex.getMessage());
+ assertTrue(ex.getMessage().contains(
+ GlobalConfiguration.HELP_COMMAND_FOOTER
+ + " has already been defined."));
+ }
+ }
+
+ /**
+ * Test that an option that is redefined later in the file throws an
+ * exception.
+ */
+ @Test
+ public void testParseInputStreamHelpHeaderDefinedTwice() throws Exception
+ {
+ ConfigurationParser configParser = new ConfigurationParser();
+ InputStream is = ConfigurationParserTest.class.getResourceAsStream(
+ "/config/config_030_help_header_defined_twice.conf");
+ try
+ {
+ configParser.parse(is, "UTF-8");
+ fail("Expected exception.");
+ }
+ catch (ConfigurationException ex)
+ {
+ System.out.println(ex.getMessage());
+ assertTrue(ex.getMessage().contains(
+ GlobalConfiguration.HELP_COMMAND_HEADER
+ + " has already been defined."));
+ }
+ }
+
+ /**
+ * Test of addOptionListener method, of class ConfigurationParser.
+ */
+ @Test
+ public void testAddOptionListener()
+ {
+
+ }
+
+ /**
+ * Test of removeOptionListener method, of class ConfigurationParser.
+ */
+ @Test
+ public void testRemoveOptionListener()
+ {
+
+ }
+
+ /**
+ * Check that the specified option configuration values match the other
+ * arguments passed in.
+ *
+ * @param optConfig non-{@code null} option configuration.
+ *
+ * @param optionName non-{@code null} name of the option to match.
+ *
+ * @param shortOption short option name to match; if {@code null}, implies
+ * using long options.
+ *
+ * @param longOption long option name to match; if {@code null}, implies
+ * using short options.
+ *
+ * @param descrption non-{@code null} description to match.
+ *
+ * @param hasArg match if the option has an argument or not.
+ */
+ private void checkOptionConfiguration(OptionConfiguration optConfig,
+ String optionName, String shortOption, String longOption,
+ String descrption, boolean hasArg)
+ {
+ assertNotNull(optConfig);
+ assertEquals(shortOption, optConfig.getShortOption());
+ assertEquals(longOption, optConfig.getLongOption());
+ assertEquals(optionName, optConfig.getName());
+ assertEquals(descrption, optConfig.getDescription());
+ assertEquals(hasArg, optConfig.hasArg());
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/cli/config/GlobalConfigurationTest.java b/src/test/java/org/apache/commons/cli/config/GlobalConfigurationTest.java
new file mode 100644
index 000000000..164b25c0b
--- /dev/null
+++ b/src/test/java/org/apache/commons/cli/config/GlobalConfigurationTest.java
@@ -0,0 +1,227 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.commons.cli.config;
+
+import static org.apache.commons.cli.config.GlobalConfiguration.OPTION_TYPE;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+public class GlobalConfigurationTest
+{
+
+ /**
+ * Test of updateGlobalConfiguration method, of class GlobalConfiguration,
+ * for option type {@link GlobalConfiguration#GLOBAL_OPTION_TYPE_BOTH}.
+ */
+ @Test
+ public void testUpdateGlobalConfigurationOptionTypeBoth() throws Exception
+ {
+ final String data = GlobalConfiguration.OPTION_TYPE + "="
+ + OptionsTypeEnum.BOTH.getType();
+ GlobalConfiguration globalConfig = new GlobalConfiguration();
+ globalConfig.updateGlobalConfiguration(data);
+ assertEquals(OptionsTypeEnum.BOTH, globalConfig.getOptionsType());
+ }
+
+ /**
+ * Test of updateGlobalConfiguration method, of class GlobalConfiguration,
+ * for option type {@link GlobalConfiguration#GLOBAL_OPTION_TYPE_SHORT}.
+ */
+ @Test
+ public void testUpdateGlobalConfigurationOptionTypeShort() throws Exception
+ {
+ final String data = GlobalConfiguration.OPTION_TYPE + "="
+ + OptionsTypeEnum.SHORT.getType();
+ GlobalConfiguration globalConfig = new GlobalConfiguration();
+ globalConfig.updateGlobalConfiguration(data);
+ assertEquals(OptionsTypeEnum.SHORT, globalConfig.getOptionsType());
+ }
+
+ /**
+ * Test of updateGlobalConfiguration method, of class GlobalConfiguration,
+ * for option type {@link GlobalConfiguration#GLOBAL_OPTION_TYPE_LONG}.
+ */
+ @Test
+ public void testUpdateGlobalConfigurationOptionTypeLong() throws Exception
+ {
+ final String data = GlobalConfiguration.OPTION_TYPE + "="
+ + OptionsTypeEnum.LONG.getType();
+ GlobalConfiguration globalConfig = new GlobalConfiguration();
+ globalConfig.updateGlobalConfiguration(data);
+ assertEquals(OptionsTypeEnum.LONG, globalConfig.getOptionsType());
+ }
+
+ /**
+ * Test of updateGlobalConfiguration method, of class GlobalConfiguration,
+ * for option type {@link GlobalConfiguration#GLOBAL_OPTION_TYPE_LONG}.
+ */
+ @Test
+ public void testUpdateGlobalConfigurationCommandFooter() throws Exception
+ {
+ final String data = GlobalConfiguration.HELP_COMMAND_FOOTER + "="
+ + "Some useful footer information";
+ GlobalConfiguration globalConfig = new GlobalConfiguration();
+ globalConfig.updateGlobalConfiguration(data);
+ assertEquals("Some useful footer information",
+ globalConfig.getHelpCommandFooter());
+ }
+
+ /**
+ * Test of updateGlobalConfiguration method, of class GlobalConfiguration,
+ * for option type {@link GlobalConfiguration#GLOBAL_OPTION_TYPE_LONG}.
+ */
+ @Test
+ public void testUpdateGlobalConfigurationCommandHeader() throws Exception
+ {
+ final String data = GlobalConfiguration.HELP_COMMAND_HEADER + "="
+ + "Some useful header information";
+ GlobalConfiguration globalConfig = new GlobalConfiguration();
+ globalConfig.updateGlobalConfiguration(data);
+ assertEquals("Some useful header information",
+ globalConfig.getHelpCommandHeader());
+ }
+
+ /**
+ * Test of updateGlobalConfiguration method, of class GlobalConfiguration,
+ * for option type {@link GlobalConfiguration#GLOBAL_OPTION_TYPE_LONG}.
+ */
+ @Test
+ public void testUpdateGlobalConfigurationOptionTypeUnknown() throws Exception
+ {
+ // The Good, the Bad and the Ugly: Unknown grave, Bill Carson:
+ final String data = GlobalConfiguration.OPTION_TYPE
+ + "=Bill Carson";
+ GlobalConfiguration globalConfig = new GlobalConfiguration();
+ try
+ {
+ globalConfig.updateGlobalConfiguration(data);
+ fail("Expected an exception");
+ }
+ catch(ConfigurationException ex)
+ {
+ assertEquals(ex.getMessage(), "Unknown options type: Bill Carson");
+ }
+ }
+
+ /**
+ * Test of updateGlobalConfiguration method, of class GlobalConfiguration,
+ * for option type {@link GlobalConfiguration#GLOBAL_OPTION_TYPE_LONG}.
+ */
+ @Test
+ public void testUpdateGlobalConfigurationUnknownOptionType() throws Exception
+ {
+ // The Good, the Bad and the Ugly: Unknown grave, Bill Carson:
+ final String data = "BILL_CARSON=Bill Carson";
+ GlobalConfiguration globalConfig = new GlobalConfiguration();
+ try
+ {
+ globalConfig.updateGlobalConfiguration(data);
+ fail("Expected an exception");
+ }
+ catch(ConfigurationException ex)
+ {
+ assertEquals(ex.getMessage(),
+ "Unknown global configuration declaration: BILL_CARSON");
+ }
+ }
+
+ /**
+ * Test of updateGlobalConfiguration method, testing that when a global
+ * option type is specified more than once, an error occurs.
+ */
+ @Test
+ public void testUpdateGlobalConfigurationReDefinedOptionType() throws Exception
+ {
+ // The Good, the Bad and the Ugly: Unknown grave, Bill Carson:
+ final String data = GlobalConfiguration.OPTION_TYPE + "="
+ + OptionsTypeEnum.BOTH.getType();
+ GlobalConfiguration globalConfig = new GlobalConfiguration();
+ globalConfig.setOptionsType(OptionsTypeEnum.BOTH);
+ try
+ {
+ globalConfig.updateGlobalConfiguration(data);
+ fail("Expected an exception");
+ }
+ catch(ConfigurationException ex)
+ {
+ assertEquals(ex.getMessage(), OPTION_TYPE
+ + " has already been defined as "
+ + OptionsTypeEnum.BOTH.getType()
+ + " but found second definition: "
+ + OptionsTypeEnum.BOTH.getType());
+ }
+ }
+
+ /**
+ * Test of updateGlobalConfiguration method, testing that a badly named
+ * help option throws an exception
+ */
+ @Test
+ public void testUpdateGlobalConfigurationBadHelpOption() throws Exception
+ {
+ // The Good, the Bad and the Ugly: Unknown grave, Bill Carson:
+ final String data = "HELP_FOO=exception!";
+ GlobalConfiguration globalConfig = new GlobalConfiguration();
+ try
+ {
+ globalConfig.updateGlobalConfiguration(data);
+ fail("Expected an exception");
+ }
+ catch(ConfigurationException ex)
+ {
+ assertEquals(ex.getMessage(),
+ "Unknown help configuration: HELP_FOO=exception!");
+ }
+ }
+
+ /**
+ * Test of addOptionConfiguration method, of class GlobalConfiguration.
+ */
+ @Test
+ public void testAddOptionConfiguration()
+ {
+ }
+
+ /**
+ * Test of getOptionConfigurations method, of class GlobalConfiguration.
+ */
+ @Test
+ public void testGetOptionMap()
+ {
+ }
+
+ /**
+ * Test of getOptionsType method, of class GlobalConfiguration.
+ */
+ @Test
+ public void testGetOptionsType()
+ {
+ }
+
+ /**
+ * Test of setOptionsType method, of class GlobalConfiguration.
+ */
+ @Test
+ public void testSetOptionsType()
+ {
+ }
+
+}
diff --git a/src/test/java/org/apache/commons/cli/config/MainTester.java b/src/test/java/org/apache/commons/cli/config/MainTester.java
new file mode 100644
index 000000000..7bd5dc69b
--- /dev/null
+++ b/src/test/java/org/apache/commons/cli/config/MainTester.java
@@ -0,0 +1,91 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.commons.cli.config;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+
+/**
+ *
+ */
+public class MainTester implements OptionListener
+{
+
+ private String md5;
+ private String file;
+
+ public static void main(String[] args) throws Exception
+ {
+ new MainTester(args);
+ }
+
+ public MainTester(final String[] args) throws Exception
+ {
+ InputStream is = MainTester.class.getResourceAsStream(
+ "/config/config_real.conf");
+// "/config/config_real.csv");
+ CommandLineConfiguration cliConfig = new CommandLineConfiguration();
+ cliConfig.addOptionListener(this);
+ cliConfig.process(is, "UTF-8", args);
+ if (md5 != null && file != null)
+ {
+ checkMd5();
+ }
+ else
+ {
+ System.err.println("Invalid arguments; try -h/--help");
+ }
+ }
+
+ @Override
+ public void option(String option, Object value)
+ {
+ if ("md5".equals(option))
+ {
+ md5 = value.toString();
+ }
+ if ("file".equals(option))
+ {
+ file = value.toString();
+ }
+ }
+
+ private void checkMd5() throws Exception
+ {
+ FileInputStream is = new FileInputStream(new File(file));
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ byte[] buffer = new byte[0xFFFF];
+ for (int len = is.read(buffer); len != -1; len = is.read(buffer))
+ {
+ os.write(buffer, 0, len);
+ }
+ MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+ messageDigest.reset();
+ messageDigest.update(os.toString("UTF-8").getBytes());
+ byte[] digestData = messageDigest.digest();
+
+ BigInteger bigInt = new BigInteger(1, digestData);
+ String hashtext = bigInt.toString(16);
+ System.out.println("Digest of file: " + hashtext);
+ System.out.println("MATCHES: " + md5.equalsIgnoreCase(hashtext));
+ }
+}
diff --git a/src/test/resources/config/config_001_short_and_long_options.conf b/src/test/resources/config/config_001_short_and_long_options.conf
new file mode 100644
index 000000000..63fafd444
--- /dev/null
+++ b/src/test/resources/config/config_001_short_and_long_options.conf
@@ -0,0 +1,39 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+option.fail.opts=F/fail
+option.fail.hasArg=false
+option.fail.description=Fail if no connection made, rather than retrying.
+
+option.host.opts=h/host
+option.host.hasArg=true
+option.host.argName=host
+option.host.default=localhost
+option.host.description=Specify the host; optional. Use localhost if not set. \
+ Protocol is optional, assumes HTTP.
+
+option.port.opts=p/port
+option.port.hasArg=true
+option.port.argName=port
+option.port.description=Port number to use. Required.
+
+option.path.opts=P/path
+option.path.hasArg=true
+option.path.argName=paths
+option.path.description=Comma separated list of paths to test on the server. \
+ If spaces are contained within the arguments, surround with double quotes. \
+ Have as many lines as required, so long as they are escaped.
\ No newline at end of file
diff --git a/src/test/resources/config/config_002_short_options.conf b/src/test/resources/config/config_002_short_options.conf
new file mode 100644
index 000000000..34caf23c5
--- /dev/null
+++ b/src/test/resources/config/config_002_short_options.conf
@@ -0,0 +1,41 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# All short options
+
+option.fail.opts=F
+option.fail.hasArg=false
+option.fail.description=Fail if no connection made, rather than retrying.
+
+option.host.opts=h
+option.host.hasArg=true
+option.host.argName=host
+option.host.default=localhost
+option.host.description=Specify the host; optional. Use localhost if not set. \
+ Protocol is optional, assumes HTTP.
+
+option.port.opts=p
+option.port.hasArg=true
+option.port.argName=port
+option.port.description=Port number to use. Required.
+
+option.path.opts=P
+option.path.hasArg=true
+option.path.argName=paths
+option.path.description=Comma separated list of paths to test on the server. \
+ If spaces are contained within the arguments, surround with double quotes. \
+ Have as many lines as required, so long as they are escaped.
\ No newline at end of file
diff --git a/src/test/resources/config/config_003_long_options.conf b/src/test/resources/config/config_003_long_options.conf
new file mode 100644
index 000000000..6173a40d0
--- /dev/null
+++ b/src/test/resources/config/config_003_long_options.conf
@@ -0,0 +1,40 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# All long options
+
+option.fail.opts=fail
+option.fail.description=Fail if no connection made, rather than retrying.
+
+option.host.opts=host
+option.host.hasArg=true
+option.host.argName=host
+option.host.default=localhost
+option.host.description=Specify the host; optional. Use localhost if not set. \
+ Protocol is optional, assumes HTTP.
+
+option.port.opts=port
+option.port.hasArg=true
+option.port.argName=port
+option.port.description=Port number to use. Required.
+
+option.path.opts=path
+option.path.hasArg=true
+option.path.argName=paths
+option.path.description=Comma separated list of paths to test on the server. \
+ If spaces are contained within the arguments, surround with double quotes. \
+ Have as many lines as required, so long as they are escaped.
\ No newline at end of file
diff --git a/src/test/resources/config/config_004_bad_short_long_mix.conf b/src/test/resources/config/config_004_bad_short_long_mix.conf
new file mode 100644
index 000000000..cedeb94a1
--- /dev/null
+++ b/src/test/resources/config/config_004_bad_short_long_mix.conf
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+option.fail.opts=fail
+option.fail.hasArg=false
+option.fail.description=Fail if no connection made, rather than retrying.
+
+# long option used previously, so therefore expect same again but will fail:
+
+option.host.opts=h
\ No newline at end of file
diff --git a/src/test/resources/config/config_005_empty_opts_value.conf b/src/test/resources/config/config_005_empty_opts_value.conf
new file mode 100644
index 000000000..594ac9685
--- /dev/null
+++ b/src/test/resources/config/config_005_empty_opts_value.conf
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Empty options will throw an exception:
+
+option.fail.opts=
diff --git a/src/test/resources/config/config_006_no_options.conf b/src/test/resources/config/config_006_no_options.conf
new file mode 100644
index 000000000..fdcca096c
--- /dev/null
+++ b/src/test/resources/config/config_006_no_options.conf
@@ -0,0 +1,18 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Error, no options
\ No newline at end of file
diff --git a/src/test/resources/config/config_007_unknown_config_option.conf b/src/test/resources/config/config_007_unknown_config_option.conf
new file mode 100644
index 000000000..f6fdccfa1
--- /dev/null
+++ b/src/test/resources/config/config_007_unknown_config_option.conf
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+option.fail.opts=f
+option.fail.bad_suboption_name=No
diff --git a/src/test/resources/config/config_008_invalid_escaped_line.conf b/src/test/resources/config/config_008_invalid_escaped_line.conf
new file mode 100644
index 000000000..557afbcc3
--- /dev/null
+++ b/src/test/resources/config/config_008_invalid_escaped_line.conf
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+option.path.opts=P/path
+option.path.hasArg=true
+option.path.argName=paths
+option.path.description=Comma separated list of paths to test on the server. \
+Uh oh! we're not putting any white space at the start, this is going to crash \
+ and burn!
\ No newline at end of file
diff --git a/src/test/resources/config/config_009_invalid_short_long_format.conf b/src/test/resources/config/config_009_invalid_short_long_format.conf
new file mode 100644
index 000000000..905f94d4f
--- /dev/null
+++ b/src/test/resources/config/config_009_invalid_short_long_format.conf
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Bad format, will throw exception
+option.fail.opts=Foo/fail
\ No newline at end of file
diff --git a/src/test/resources/config/config_00X.csv b/src/test/resources/config/config_00X.csv
new file mode 100644
index 000000000..1dd4d4f0a
--- /dev/null
+++ b/src/test/resources/config/config_00X.csv
@@ -0,0 +1,116 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# OPTION_TYPE
+#
+# Optional; default is to use BOTH.
+#
+# BOTH: use short and long options. The option must be specified (below) as
+# SHORT|LONG|BOTH.
+# SHORT: use short options only.
+# LONG: use long options only.
+# BOTH: use both types (both MUST be present).
+#
+# OPTION_TYPE=BOTH
+#
+# Note that is is possible to either use a single character or multiple
+# characters to specify short or long options, respectively, so long as the
+# definitions are consistent. Specifying a mix without using a separator will
+# produce an error.
+
+#
+#
+#
+#
+# HELP_COMMAND_NAME=
+
+#
+#
+#
+#
+# HELP_HEADER=
+
+#
+#
+#
+#
+# HELP_FOOTER=
+
+#
+#
+#
+#
+# HELP_OPTION_NAME=h/help
+
+# CLI definitions
+#
+
+#
+# Message to display if there are no arguments supplied.
+#
+# ON_EMPTY=[message]
+#
+# Example:
+#
+# ON_EMPTY=No arguments supplied; try -h/--help
+#
+# Will print "No arguments supplied; try -h/--help" when args[] is null or empty.
+#
+
+option.help=h/help
+option.help.hasArg=false
+option.help.description=Display this help then quit.
+
+option.host=H/host
+option.host.hasArg=true
+option.host.argName=host
+option.host.type=url
+option.host.default=localhost
+option.host.description=Specify the host, use localhost if not set.
+
+option.port=p/port
+option.port.hasArg=true
+option.port.description=Port number to use. \
+ If not set, use port 8001.
+option.port.default=80
+option.port.type=int
+option.port.type.min=80
+option.port.type.max=65536
+
+option.path=P/paths
+option.path.hasArg=true
+option.path.description=Comma separated list of paths to test on the server. \
+ If spaces are contained within the arguments, surround with double quotes.
+option.path.type=list[string]
+
+option.path=c/config
+option.path.hasArg=true
+option.path.description=Configuration file to use.
+option.path.type=file
+option.path.type.fileType=file
+option.path.type.exist
+#option.path.type.notexist
+#option.path.type.create=true
+
+option.path=o/outputDir
+option.path.hasArg=true
+option.path.description=Directory to write results to; created if it doesn't exist.
+option.path.type=file
+option.path.type.fileType=dir
+option.path.type.create=true
+#option.path.type.notexist
+
diff --git a/src/test/resources/config/config_010_invalid_short_format.conf b/src/test/resources/config/config_010_invalid_short_format.conf
new file mode 100644
index 000000000..dbdc75116
--- /dev/null
+++ b/src/test/resources/config/config_010_invalid_short_format.conf
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Invalid short option format
+option.fail.opts=?
\ No newline at end of file
diff --git a/src/test/resources/config/config_011_invalid_long_option_format.conf b/src/test/resources/config/config_011_invalid_long_option_format.conf
new file mode 100644
index 000000000..bfa3b0f5b
--- /dev/null
+++ b/src/test/resources/config/config_011_invalid_long_option_format.conf
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Invalid long option format:
+option.host.opts=host!
\ No newline at end of file
diff --git a/src/test/resources/config/config_012_invalid_option_definition.conf b/src/test/resources/config/config_012_invalid_option_definition.conf
new file mode 100644
index 000000000..134f561cf
--- /dev/null
+++ b/src/test/resources/config/config_012_invalid_option_definition.conf
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Invalid option format:
+foo.bar.baz.opts=completely bad line format
\ No newline at end of file
diff --git a/src/test/resources/config/config_013_empty_option_value.conf b/src/test/resources/config/config_013_empty_option_value.conf
new file mode 100644
index 000000000..8bd20261e
--- /dev/null
+++ b/src/test/resources/config/config_013_empty_option_value.conf
@@ -0,0 +1,27 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Ensure we print a message out informing them of the option type (long) they
+# are using
+
+option.fail.opts=fail
+option.fail.hasArg=false
+option.fail.description=Fail if no connection made, rather than retrying.
+
+option.fail.opts=
+option.fail.hasArg=true
+option.fail.description=Exception thrown due to empty option
diff --git a/src/test/resources/config/config_014_option_type_defined_twice.conf b/src/test/resources/config/config_014_option_type_defined_twice.conf
new file mode 100644
index 000000000..c647cc7c0
--- /dev/null
+++ b/src/test/resources/config/config_014_option_type_defined_twice.conf
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# This will set to configuration type BOTH:
+OPTION_TYPE=BOTH
+# ... But a 2nd definition, regardless that it's the same, will throw an error:
+OPTION_TYPE=BOTH
+
+# No point defining anything else, exception has been thrown
\ No newline at end of file
diff --git a/src/test/resources/config/config_015_print_short_option_help.conf b/src/test/resources/config/config_015_print_short_option_help.conf
new file mode 100644
index 000000000..b92dd80af
--- /dev/null
+++ b/src/test/resources/config/config_015_print_short_option_help.conf
@@ -0,0 +1,52 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+HELP_COMMAND_NAME=foo
+
+# Implementors do not need to define HELP_OPTION_NAME - they can
+#
+# The name must:
+# - match the option.[name] option configuration. For example, if
+# HELP_OPTION_NAME is defined as 'help' (not including apostrophes), then
+# there must also be an option definition named 'option.help';
+# - option.[name] hasArg must be false;
+# - option.[name] must have a description
+#
+# Defining this will print the help for the defined options then quit with 0
+# status.
+#
+HELP_OPTION_NAME=help
+
+option.help.opts=h
+option.help.hasArg=false
+option.help.description=Print this then quit.
+
+option.fail.opts=f
+option.fail.hasArg=false
+option.fail.description=Fail if no connection made, rather than retrying.
+
+option.host.opts=H
+option.host.hasArg=true
+option.host.argName=host
+option.host.default=localhost
+option.host.description=Specify the host; optional. Use localhost if not set. \
+ Protocol is optional, assumes HTTP.
+
+option.port.opts=p
+option.port.hasArg=true
+option.port.argName=port
+option.port.description=Port number to use. Required.
\ No newline at end of file
diff --git a/src/test/resources/config/config_016_print_long_option_help.conf b/src/test/resources/config/config_016_print_long_option_help.conf
new file mode 100644
index 000000000..4ad55be29
--- /dev/null
+++ b/src/test/resources/config/config_016_print_long_option_help.conf
@@ -0,0 +1,52 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# When help is invoked, will print "Usage: foo" plus the rest of the options:
+HELP_COMMAND_NAME=foo
+
+#
+# The name must:
+# - match the option.[name] option configuration. For example, if
+# HELP_OPTION_NAME is defined as 'help' (not including apostrophes), then
+# there must also be an option definition named 'option.help';
+# - option.[name] hasArg must be false;
+# - option.[name] must have a description
+#
+# Defining this will print the help for the defined options then quit with 0
+# status.
+#
+HELP_OPTION_NAME=help
+
+option.help.opts=help
+option.help.hasArg=false
+option.help.description=Print this then quit.
+
+option.fail.opts=fail
+option.fail.hasArg=false
+option.fail.description=Fail if no connection made, rather than retrying.
+
+option.host.opts=host
+option.host.hasArg=true
+option.host.argName=host
+option.host.default=localhost
+option.host.description=Specify the host; optional. Use localhost if not set. \
+ Protocol is optional, assumes HTTP.
+
+option.port.opts=port
+option.port.hasArg=true
+option.port.argName=port
+option.port.description=Port number to use. Required.
\ No newline at end of file
diff --git a/src/test/resources/config/config_017_bad_help_option.conf b/src/test/resources/config/config_017_bad_help_option.conf
new file mode 100644
index 000000000..08aceeae4
--- /dev/null
+++ b/src/test/resources/config/config_017_bad_help_option.conf
@@ -0,0 +1,56 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# When help is invoked, will print "Usage: foo" plus the rest of the options:
+HELP_COMMAND_NAME=foo
+
+#
+# The name must:
+# - match the option.[name] option configuration. For example, if
+# HELP_OPTION_NAME is defined as 'help' (not including apostrophes), then
+# there must also be an option definition named 'option.help';
+# - option.[name] hasArg must be false;
+# - option.[name] must have a description
+#
+# Defining this will print the help for the defined options then quit with 0
+# status.
+#
+
+HELP_OPTION_NAME=help
+
+option.help.opts=help
+
+# Exception: help cannot have an argument
+
+option.help.hasArg=true
+option.help.description=Print this then quit.
+
+option.fail.opts=fail
+option.fail.hasArg=false
+option.fail.description=Fail if no connection made, rather than retrying.
+
+option.host.opts=host
+option.host.hasArg=true
+option.host.argName=host
+option.host.default=localhost
+option.host.description=Specify the host; optional. Use localhost if not set. \
+ Protocol is optional, assumes HTTP.
+
+option.port.opts=port
+option.port.hasArg=true
+option.port.argName=port
+option.port.description=Port number to use. Required.
\ No newline at end of file
diff --git a/src/test/resources/config/config_018_header_and_footer.conf b/src/test/resources/config/config_018_header_and_footer.conf
new file mode 100644
index 000000000..dee261444
--- /dev/null
+++ b/src/test/resources/config/config_018_header_and_footer.conf
@@ -0,0 +1,56 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# When help is invoked, will print "Usage: foo" plus the rest of the options:
+HELP_COMMAND_NAME=foo_command
+
+HELP_COMMAND_HEADER=Show some useful information
+
+HELP_COMMAND_FOOTER=Copyright Apache Software Foundation
+
+#
+# The name must:
+# - match the option.[name] option configuration. For example, if
+# HELP_OPTION_NAME is defined as 'help' (not including apostrophes), then
+# there must also be an option definition named 'option.help';
+# - option.[name] hasArg must be false;
+# - option.[name] must have a description
+#
+# Defining this will print the help for the defined options then quit with 0
+# status.
+#
+HELP_OPTION_NAME=help
+
+option.help.opts=help
+option.help.hasArg=false
+option.help.description=Print this then quit.
+
+option.fail.opts=fail
+option.fail.hasArg=false
+option.fail.description=Fail if no connection made, rather than retrying.
+
+option.host.opts=host
+option.host.hasArg=true
+option.host.argName=host
+option.host.default=localhost
+option.host.description=Specify the host; optional. Use localhost if not set. \
+ Protocol is optional, assumes HTTP.
+
+option.port.opts=port
+option.port.hasArg=true
+option.port.argName=port
+option.port.description=Port number to use. Required.
\ No newline at end of file
diff --git a/src/test/resources/config/config_019_header_and_footer_escaped.conf b/src/test/resources/config/config_019_header_and_footer_escaped.conf
new file mode 100644
index 000000000..852f0f223
--- /dev/null
+++ b/src/test/resources/config/config_019_header_and_footer_escaped.conf
@@ -0,0 +1,58 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# When help is invoked, will print "Usage: foo" plus the rest of the options:
+HELP_COMMAND_NAME=foo_command
+
+HELP_COMMAND_HEADER=Show some useful information, \
+ with some extra escaped lines
+
+HELP_COMMAND_FOOTER=Copyright Apache Software Foundation \
+ Submit escaped lines to System.out()
+
+#
+# The name must:
+# - match the option.[name] option configuration. For example, if
+# HELP_OPTION_NAME is defined as 'help' (not including apostrophes), then
+# there must also be an option definition named 'option.help';
+# - option.[name] hasArg must be false;
+# - option.[name] must have a description
+#
+# Defining this will print the help for the defined options then quit with 0
+# status.
+#
+HELP_OPTION_NAME=help
+
+option.help.opts=help
+option.help.hasArg=false
+option.help.description=Print this then quit.
+
+option.fail.opts=fail
+option.fail.hasArg=false
+option.fail.description=Fail if no connection made, rather than retrying.
+
+option.host.opts=host
+option.host.hasArg=true
+option.host.argName=host
+option.host.default=localhost
+option.host.description=Specify the host; optional. Use localhost if not set. \
+ Protocol is optional, assumes HTTP.
+
+option.port.opts=port
+option.port.hasArg=true
+option.port.argName=port
+option.port.description=Port number to use. Required.
\ No newline at end of file
diff --git a/src/test/resources/config/config_020_bad_global_config_order.conf b/src/test/resources/config/config_020_bad_global_config_order.conf
new file mode 100644
index 000000000..cf800f796
--- /dev/null
+++ b/src/test/resources/config/config_020_bad_global_config_order.conf
@@ -0,0 +1,24 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+option.help.opts=h
+option.help.hasArg=false
+option.help.description=Print this then quit.
+
+# This should appear /before/ the option.help declaration and will throw exception
+
+HELP_COMMAND_NAME=foo
diff --git a/src/test/resources/config/config_021_redefinition_long_option.conf b/src/test/resources/config/config_021_redefinition_long_option.conf
new file mode 100644
index 000000000..9961874ba
--- /dev/null
+++ b/src/test/resources/config/config_021_redefinition_long_option.conf
@@ -0,0 +1,25 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+option.help.opts=help
+option.help.hasArg=false
+option.help.description=Print this then quit.
+
+option.help.opts=foo
+
+# Re-definition of argument throws an exception:
+option.help.opts=help
diff --git a/src/test/resources/config/config_022_redefinition_description.conf b/src/test/resources/config/config_022_redefinition_description.conf
new file mode 100644
index 000000000..ede2ae08b
--- /dev/null
+++ b/src/test/resources/config/config_022_redefinition_description.conf
@@ -0,0 +1,22 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+option.help.opts=help
+option.help.hasArg=false
+option.help.description=Print this then quit.
+# Error with description re-definition:
+option.help.description=Print this then quit.
\ No newline at end of file
diff --git a/src/test/resources/config/config_023_redefinition_hasArg.conf b/src/test/resources/config/config_023_redefinition_hasArg.conf
new file mode 100644
index 000000000..02a5cf5d4
--- /dev/null
+++ b/src/test/resources/config/config_023_redefinition_hasArg.conf
@@ -0,0 +1,22 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+option.help.opts=help
+option.help.hasArg=false
+option.help.description=Print this then quit.
+# Error with hasArg re-definition:
+option.help.hasArg=true
\ No newline at end of file
diff --git a/src/test/resources/config/config_024_redefinition_argName.conf b/src/test/resources/config/config_024_redefinition_argName.conf
new file mode 100644
index 000000000..61c20a155
--- /dev/null
+++ b/src/test/resources/config/config_024_redefinition_argName.conf
@@ -0,0 +1,23 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+option.fubar.opts=foobarbaz
+option.fubar.hasArg=true
+option.fubar.argName=Boo
+option.fubar.description=Foo... Bar? BAZ!
+# Error with hasArg re-definition:
+option.fubar.argName=Foo
\ No newline at end of file
diff --git a/src/test/resources/config/config_025_redefinition_short_option.conf b/src/test/resources/config/config_025_redefinition_short_option.conf
new file mode 100644
index 000000000..a1dc84d7c
--- /dev/null
+++ b/src/test/resources/config/config_025_redefinition_short_option.conf
@@ -0,0 +1,28 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+option.help.opts=h
+option.help.hasArg=false
+option.help.description=Print this then quit.
+
+option.help.opts=f
+option.help.hasArg=true
+option.help.argName=file
+option.help.description=File to read
+
+# Re-definition of argument throws an exception:
+option.help.opts=h
diff --git a/src/test/resources/config/config_026_redefinition_bad_ordering.conf b/src/test/resources/config/config_026_redefinition_bad_ordering.conf
new file mode 100644
index 000000000..d565b3e35
--- /dev/null
+++ b/src/test/resources/config/config_026_redefinition_bad_ordering.conf
@@ -0,0 +1,27 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+option.dir.opts=d
+option.dir.hasArg=true
+option.dir.description=Directory of file.
+
+option.file.opts=f
+option.file.hasArg=true
+option.file.description=File to read.
+
+# Declaring old option after new option throws exception:
+option.dir.argName=dir
diff --git a/src/test/resources/config/config_027_help_command_defined_twice.conf b/src/test/resources/config/config_027_help_command_defined_twice.conf
new file mode 100644
index 000000000..551eb106d
--- /dev/null
+++ b/src/test/resources/config/config_027_help_command_defined_twice.conf
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Error, command name defined twice:
+HELP_COMMAND_NAME=foo_command
+HELP_COMMAND_NAME=foo_command
\ No newline at end of file
diff --git a/src/test/resources/config/config_028_help_option_defined_twice.conf b/src/test/resources/config/config_028_help_option_defined_twice.conf
new file mode 100644
index 000000000..7a23b96ff
--- /dev/null
+++ b/src/test/resources/config/config_028_help_option_defined_twice.conf
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Error, command name defined twice:
+HELP_OPTION_NAME=showHelp
+HELP_OPTION_NAME=showHelp
\ No newline at end of file
diff --git a/src/test/resources/config/config_029_help_footer_defined_twice.conf b/src/test/resources/config/config_029_help_footer_defined_twice.conf
new file mode 100644
index 000000000..8e1ce83bc
--- /dev/null
+++ b/src/test/resources/config/config_029_help_footer_defined_twice.conf
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Error, command name defined twice:
+HELP_COMMAND_FOOTER=Help footer
+HELP_COMMAND_FOOTER=Help footer
\ No newline at end of file
diff --git a/src/test/resources/config/config_030_help_header_defined_twice.conf b/src/test/resources/config/config_030_help_header_defined_twice.conf
new file mode 100644
index 000000000..59f82f70c
--- /dev/null
+++ b/src/test/resources/config/config_030_help_header_defined_twice.conf
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# Error, command name defined twice:
+HELP_COMMAND_HEADER=Help header
+HELP_COMMAND_HEADER=Help header
\ No newline at end of file
diff --git a/src/test/resources/config/config_real.conf b/src/test/resources/config/config_real.conf
new file mode 100644
index 000000000..82ef6354b
--- /dev/null
+++ b/src/test/resources/config/config_real.conf
@@ -0,0 +1,44 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+HELP_COMMAND_NAME=md5-check
+HELP_COMMAND_HEADER=Print the MD5 of the specified file, checking if it is \
+ equal to the specified MD5 string.
+HELP_COMMAND_FOOTER=Copyright 2019 Apache Software Foundation.
+
+# Implementors do not need to define HELP_OPTION_NAME - they can
+#
+# The name must:
+# - match the option.[name] option configuration. For example, if
+# HELP_OPTION_NAME is defined as 'help' (not including apostrophes), then
+# there must also be an option definition named 'option.help';
+# - option.[name] hasArg must be false;
+#
+HELP_OPTION_NAME=help
+
+option.help.opts=h/help
+option.help.hasArg=false
+option.help.description=Print this help then quit.
+
+option.md5.opts=m/md5
+option.md5.hasArg=true
+option.md5.description=MD5 to test against the file.
+
+option.file.opts=f/file
+option.file.hasArg=true
+option.file.argName=file
+option.file.description=Command line configuration file to parse.
\ No newline at end of file
diff --git a/src/test/resources/config/opt.config b/src/test/resources/config/opt.config
new file mode 100644
index 000000000..7b8d48be3
--- /dev/null
+++ b/src/test/resources/config/opt.config
@@ -0,0 +1,45 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+HELP_OPTION_NAME=showHelp
+HELP_COMMAND_NAME=writeData
+HELP_COMMAND_HEADER=Write the specified text to the specified file. If the \
+ options contain spaces or special characters, supply the arguments in \
+ double quotes.
+HELP_COMMAND_FOOTER=Copyleft BarBaz International.
+
+option.outfile.opts=f/file
+option.outfile.hasArg=true
+option.outfile.argName=file
+option.outfile.description=File to write to.
+
+option.textToWrite.opts=t/text
+option.textToWrite.hasArg=true
+option.textToWrite.argName=text
+option.textToWrite.description=Text to write to the file.
+
+option.writeover.opts=o/overwrite
+# We do not need to specify this since all options are false for hasArg if not specified
+# option.writeover.hasArg=false
+option.writeover.argName=overwrite
+option.writeover.description=If set, write over the existing file; \
+ otherwise, append to the file.
+
+option.showHelp.opts=h/help
+option.showHelp.hasArg=false
+option.showHelp.argName=help
+option.showHelp.description=Print this help then quit.
\ No newline at end of file