From: <chr...@us...> - 2006-10-03 23:32:18
|
Revision: 179 http://svn.sourceforge.net/japi/?rev=179&view=rev Author: christianhujer Date: 2006-10-03 16:31:53 -0700 (Tue, 03 Oct 2006) Log Message: ----------- Adding new feature: BasicCommand which handles --help and --version, i18n/l10n for --help output. Modified Paths: -------------- libs/argparser/trunk/src/net/sf/japi/io/args/ArgParser.java libs/argparser/trunk/src/net/sf/japi/io/args/Command.java libs/argparser/trunk/src/net/sf/japi/io/args/Option.java libs/argparser/trunk/src/net/sf/japi/io/args/OptionType.java libs/argparser/trunk/src/test/net/sf/japi/io/args/ArgParserTest.java Added Paths: ----------- libs/argparser/trunk/src/net/sf/japi/io/args/BasicCommand.java libs/argparser/trunk/src/net/sf/japi/io/args/messages.properties libs/argparser/trunk/src/net/sf/japi/io/args/messages_de.properties Modified: libs/argparser/trunk/src/net/sf/japi/io/args/ArgParser.java =================================================================== --- libs/argparser/trunk/src/net/sf/japi/io/args/ArgParser.java 2006-09-25 22:42:46 UTC (rev 178) +++ libs/argparser/trunk/src/net/sf/japi/io/args/ArgParser.java 2006-10-03 23:31:53 UTC (rev 179) @@ -28,10 +28,11 @@ /** * Parser for command line arguments. * @author <a href="mailto:ch...@ri...">Christian Hujer</a> - * TODO: automatic argument conversion for option method invokation. + * TODO: automatic argument conversion for option method invocation. * TODO: Handling of --help * TODO: Handling of --version - * TODO: Handling of - for STDIN as input argument filestrea + * TODO: Handling of - for STDIN as input argument filestream + * TODO: Support for I18N/L10N */ public class ArgParser { @@ -83,7 +84,7 @@ for (final Method requiredMethod : requiredMethods) { final Option option = requiredMethod.getAnnotation(Option.class); assert option != null; - missingOptions.add(option.name()[0]); + missingOptions.add(option.names()[0]); } throw new RequiredOptionsMissingException(missingOptions.toArray(new String[missingOptions.size()])); } @@ -95,24 +96,48 @@ * All required methods are additionally stored in {@link #requiredMethods}. */ private void initMethods() { - for (final Method method : commandClass.getMethods()) { + for (final Method method : getOptionMethods(commandClass)) { final Option option = method.getAnnotation(Option.class); - if (option != null) { - for (final String optionName : option.name()) { - if (argumentMethods.containsKey(optionName)) { - throw new IllegalArgumentException(commandClass.getName() + " declared option " + optionName + " twice."); - } - argumentMethods.put(optionName, method); + assert option != null; + for (final String optionName : option.names()) { + if (argumentMethods.containsKey(optionName)) { + throw new IllegalArgumentException(commandClass.getName() + " declared option " + optionName + " twice."); } - final OptionType type = option.type(); - if (type == OptionType.REQUIRED) { - requiredMethods.add(method); - } + argumentMethods.put(optionName, method); } + final OptionType type = option.type(); + if (type == OptionType.REQUIRED) { + requiredMethods.add(method); + } } } /** + * Get all option methods from a command. + * @param command Command to get option methods for + * @return option methods for the command. + */ + public static Set<Method> getOptionMethods(final Command command) { + return getOptionMethods(command.getClass()); + } + + /** + * Get all option methods from a command class. + * @param commandClass Class of the Command to get option methods for + * @return Option methods for the command class. + */ + public static Set<Method> getOptionMethods(final Class<? extends Command> commandClass) { + final Method[] methods = commandClass.getMethods(); + final Set<Method> optionMethods = new HashSet<Method>(); + for (final Method method : methods) { + if (method.isAnnotationPresent(Option.class)) { + optionMethods.add(method); + } + } + return optionMethods; + } + + /** * Parses arguments into an arguments container and invokes the Command's {@link Command#run(List<String>)} method. * @throws TerminalException in case argument parsing was stopped */ Added: libs/argparser/trunk/src/net/sf/japi/io/args/BasicCommand.java =================================================================== --- libs/argparser/trunk/src/net/sf/japi/io/args/BasicCommand.java (rev 0) +++ libs/argparser/trunk/src/net/sf/japi/io/args/BasicCommand.java 2006-10-03 23:31:53 UTC (rev 179) @@ -0,0 +1,100 @@ +package net.sf.japi.io.args; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Formatter; +import java.util.HashSet; +import java.util.List; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; + +/** + * BasicCommand is a base class for commands that provides the options --help and --version. + * @author <a href="mailto:ch...@ri...">Christian Hujer</a> + */ +public abstract class BasicCommand implements Command { + + /** The ResourceBundle for locale-specific output. */ + private final ResourceBundle resourceBundle = ResourceBundle.getBundle("net.sf.japi.io.args.messages"); + + /** The maximum width of the option type field. */ + private final int maxOptionTypeWidth; + + /** Create a BasicCommand. */ + protected BasicCommand() { + int tmpMaxOptionTypeWidth = 0; + for (final OptionType optionType : OptionType.values()) { + tmpMaxOptionTypeWidth = Math.max(tmpMaxOptionTypeWidth, optionType.toString().length()); + } + maxOptionTypeWidth = tmpMaxOptionTypeWidth; + } + + /** Version Option. */ + @Option(type = OptionType.TERMINAL, names = {"v", "version"}) + public void version() { + // TODO + } + + /** Help Option. */ + @Option(type = OptionType.TERMINAL, names = {"h", "help"}) + public void help() { + final Set<Method> optionMethods = ArgParser.getOptionMethods(this); + final Set<Class<?>> parameterTypes = new HashSet<Class<?>>(); + int maxLong = 0; + int maxShort = 0; + for (final Method optionMethod : optionMethods) { + final Option option = optionMethod.getAnnotation(Option.class); + final String[] names = option.names(); + int currentLong = 0; + int currentShort = 0; + for (final String name : names) { + if (name.length() > 1) { + currentLong += name.length() + ", --".length(); + } else { + currentShort += name.length() + ", -".length(); + } + } + maxLong = Math.max(maxLong, currentLong - ", ".length()); + maxShort = Math.max(maxShort, currentShort - ", ".length()); + for (final Class<?> parameterType : optionMethod.getParameterTypes()) { + parameterTypes.add(parameterType); + } + } + final String formatString = "%-" + maxShort + "s%s%-" + maxLong + "s: (%-" + maxOptionTypeWidth + "s) %s%n"; + final Formatter format = new Formatter(System.err); + for (final Method optionMethod : optionMethods) { + final Option option = optionMethod.getAnnotation(Option.class); + final OptionType optionType = option.type(); + final String[] names = option.names(); + final List<String> shortNames = new ArrayList<String>(); + final List<String> longNames = new ArrayList<String>(); + for (final String name : names) { + if (name.length() > 1) { + longNames.add("--" + name); + } else { + shortNames.add("-" + name); + } + } + final String delim = shortNames.size() > 0 && longNames.size() > 0 ? ", " : " "; + String description; + try { + final String optionKey = option.key().equals("") ? optionMethod.getName() : option.key(); + description = getBundle().getString(optionKey); + } catch (final MissingResourceException ignore) { + description = ""; + } + format.format(formatString, StringJoiner.join(", ", shortNames), delim, StringJoiner.join(", ", longNames), optionType.toString(), description); + } + format.flush(); + } + + /** + * Get the ResourceBundle for the default locale. + * If you override this method be sure to declare the bundle returned by the overridden method as parent of your bundle. + */ + public ResourceBundle getBundle() { + return resourceBundle; + } + +} // class BasicCommand Property changes on: libs/argparser/trunk/src/net/sf/japi/io/args/BasicCommand.java ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:eol-style + LF Modified: libs/argparser/trunk/src/net/sf/japi/io/args/Command.java =================================================================== --- libs/argparser/trunk/src/net/sf/japi/io/args/Command.java 2006-09-25 22:42:46 UTC (rev 178) +++ libs/argparser/trunk/src/net/sf/japi/io/args/Command.java 2006-10-03 23:31:53 UTC (rev 179) @@ -22,6 +22,7 @@ package net.sf.japi.io.args; import java.util.List; +import org.jetbrains.annotations.NotNull; /** * Shell commands can implement this interface and make use of ArgParser. @@ -30,11 +31,11 @@ public interface Command { /** - * Run the command. + * Run this command. * This method is invoked by {@link ArgParser} once it is finnished with parsing the arguments. * @param args the argument strings that were not parsed away by {@link ArgParser}. */ @SuppressWarnings({"InstanceMethodNamingConvention"}) - void run(List<String> args); + void run(@NotNull List<String> args); } // interface Command Modified: libs/argparser/trunk/src/net/sf/japi/io/args/Option.java =================================================================== --- libs/argparser/trunk/src/net/sf/japi/io/args/Option.java 2006-09-25 22:42:46 UTC (rev 178) +++ libs/argparser/trunk/src/net/sf/japi/io/args/Option.java 2006-10-03 23:31:53 UTC (rev 179) @@ -48,6 +48,13 @@ * @note the supplied string values MUST consist of ASCII-letters only (match regex <code>[a-zA-Z]+</code>). * @return option names */ - String[] name(); + String[] names(); + /** + * The option key, used for i18n/l10n. + * Default is <code>""</code> (empty String) which is interpreted as the associated method's name being the key. + * @return option key + */ + String key() default ""; + } // @interface Option Modified: libs/argparser/trunk/src/net/sf/japi/io/args/OptionType.java =================================================================== --- libs/argparser/trunk/src/net/sf/japi/io/args/OptionType.java 2006-09-25 22:42:46 UTC (rev 178) +++ libs/argparser/trunk/src/net/sf/japi/io/args/OptionType.java 2006-10-03 23:31:53 UTC (rev 179) @@ -20,6 +20,10 @@ */ package net.sf.japi.io.args; +import java.util.ResourceBundle; +import java.util.MissingResourceException; +import java.util.Locale; + /** * The type of an option. * @author <a href="mailto:ch...@ri...">Christian Hujer</a> @@ -33,6 +37,43 @@ OPTIONAL, /** Terminal options terminate argument parsing, no matter what happens. */ - TERMINAL + TERMINAL; + /** + * Returns the localized name of this OptionType in the default locale if available, otherwise the lowercase enum constant name. + * @return The localized name of this OptionType. + */ + public String getName() { + String name; + try { + name = ResourceBundle.getBundle("net.sf.japi.io.args.messages").getString(getClass().getName() + "." + name()); + } catch (final MissingResourceException e) { + name = name(); + } + return name; + } + + /** + * Returns the localized name of this OptionType in the specified locale if available, otherwise the lowercase enum constant name. + * @param locale Locale + * @return The localized name of this OptionType. + */ + public String getName(final Locale locale) { + String name; + try { + name = ResourceBundle.getBundle("net.sf.japi.io.args.messages", locale).getString(getClass().getName() + "." + name()); + } catch (final MissingResourceException e) { + name = name(); + } + return name; + } + + /** + * {@inheritDoc} + * Returns the same as {@link #getName()}. + */ + @Override public String toString() { + return getName(); + } + } // OptionType Added: libs/argparser/trunk/src/net/sf/japi/io/args/messages.properties =================================================================== --- libs/argparser/trunk/src/net/sf/japi/io/args/messages.properties (rev 0) +++ libs/argparser/trunk/src/net/sf/japi/io/args/messages.properties 2006-10-03 23:31:53 UTC (rev 179) @@ -0,0 +1,5 @@ +net.sf.japi.io.args.OptionType.REQUIRED=required +net.sf.japi.io.args.OptionType.OPTIONAL=optional +net.sf.japi.io.args.OptionType.TERMINAL=terminal +help=Display this help and exit. +version=Display version information and exit. \ No newline at end of file Property changes on: libs/argparser/trunk/src/net/sf/japi/io/args/messages.properties ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:eol-style + LF Added: libs/argparser/trunk/src/net/sf/japi/io/args/messages_de.properties =================================================================== --- libs/argparser/trunk/src/net/sf/japi/io/args/messages_de.properties (rev 0) +++ libs/argparser/trunk/src/net/sf/japi/io/args/messages_de.properties 2006-10-03 23:31:53 UTC (rev 179) @@ -0,0 +1,5 @@ +net.sf.japi.io.args.OptionType.REQUIRED=erforderlich +net.sf.japi.io.args.OptionType.OPTIONAL=optional +net.sf.japi.io.args.OptionType.TERMINAL=abbrechend +help=Diese Hilfe anzeigen und beenden. +version=Versionsinformation anzeigen und beenden. \ No newline at end of file Property changes on: libs/argparser/trunk/src/net/sf/japi/io/args/messages_de.properties ___________________________________________________________________ Name: svn:mime-type + text/plain Name: svn:eol-style + LF Modified: libs/argparser/trunk/src/test/net/sf/japi/io/args/ArgParserTest.java =================================================================== --- libs/argparser/trunk/src/test/net/sf/japi/io/args/ArgParserTest.java 2006-09-25 22:42:46 UTC (rev 178) +++ libs/argparser/trunk/src/test/net/sf/japi/io/args/ArgParserTest.java 2006-10-03 23:31:53 UTC (rev 179) @@ -1,7 +1,7 @@ package test.net.sf.japi.io.args; import net.sf.japi.io.args.ArgParser; -import net.sf.japi.io.args.Command; +import net.sf.japi.io.args.BasicCommand; import net.sf.japi.io.args.MissingArgumentException; import net.sf.japi.io.args.Option; import net.sf.japi.io.args.OptionType; @@ -22,7 +22,7 @@ * This MockCommand serves as a command for performing simple tests. * @author <a href="mailto:ch...@ri...">Christian Hujer</a> */ - public static class MockCommand implements Command { + public static class MockCommand extends BasicCommand { /** The input option value. */ private String input; @@ -52,12 +52,21 @@ * Set the value of the input option. * @param input Value of the input option. */ - @Option(type = OptionType.REQUIRED, name = {"i", "input"}) + @Option(type = OptionType.REQUIRED, names = {"i", "input"}) public void setInput(final String input) { this.input = input; } /** + * Set the value of the foo option. + * @param foo Value of the foo option. + */ + @Option(names = {"f", "b", "foo", "bar", "buzz"}) + public void setFoo(final String foo) { + // ignored + } + + /** * Get the value of the input option. * @return Value of the input option. */ @@ -190,4 +199,17 @@ Assert.assertEquals("Argument list for invocation without arguments must be empty.", 0, command.getArgs().size()); } + /** + * Tests whether help works. + * @throws RequiredOptionsMissingException + * @throws TerminalException + * @throws UnknownOptionException + * @throws MissingArgumentException + */ + @Test(expected = TerminalException.class) + public void testHelp() throws RequiredOptionsMissingException, MissingArgumentException, TerminalException, UnknownOptionException { + final MockCommand command = new MockCommand(); + ArgParser.parseAndRun(command, "-h"); + } + } // class ArgParserTest This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |