// Args.java v0.9 - Unix-style commandline parsing
// Copyright (C) 1999 j.p.lewis
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
// Boston, MA 02111-1307, USA.
//
// Primary author contact info: www.idiom.com/~zilla zilla@computer.org
package gnu;
import java.util.*;
/**
* The Args
class does Unix-style parsing of
* commandline arguments. The single args.parse()
* method returns a hashtable containing the parsed and
* type-checked arguments.
* The hashtable can be queried for the parsed arguments,
* or accessor methods can be called for this purpose.
* If parsing fails the method prints a formatted message and returns null.
*
* This doc page reflects version "0.9" of this code. *
* Usage example: *
* public static void main(String[] cmdline) * { * Hashtable args * = new args(cmdline).parse("+:str:output file:" + * "-frameno:int:frame number:" + * "+:str:username:" + * "-f:int:frame rate:" + * "-v:flg:verbose"); ** ** if (args == null) * System.exit(1); *
* // first and second required arguments * String Path = args.getRequiredString(0); * String Username = args.getRequiredString(1); *
* // optional integer argument introduced by "-framenum" * Integer Framenum = args.getOptionalInteger("-framenum"); *
* // optional integer argument introduced by "-f". This time * // query the hashtable directly rather than using accessor function * // as in the previous example. * int framerate = -1; * if (args.get("-f") != null) * framerate = ((Integer)args.get("-f")).intValue(); *
* // optional flag * Boolean Verbose = args.getFlag("-v"); * } *
Args
considers arguments to be one of three sorts:
* getopts
routine,
* the option and flag strings are not restricted
* to single characters; this also means that "-framenum"
* is not the same thing as "-f -r -a -m -e -n -u -m".
*
* The argument to the parse
method is a string template
* defining the desired arguments. The string contains
* one or more argument specifications, each of which consists of
* a key, a type, and a description, each separated by a colon character.
* The key is "+" for required arguments and is the desired
* string (such as "--verbose" or "-n") for optional and flag arguments.
* The type is one of the strings "int", "flt", "dbl", "str", or "flg",
* with the last string meaning a boolean flag argument such as "-verbose".
* The description is a phrase describing the argument;
* this phrase is printed in the usage message.
*
* For a single required string argument specifying an output file * the argument template string would be *
* "+:str:output file:" **
* If we add a second optional integer argument specifying a * "number of passes" the argument template string would be *
* "+:str:output file:"+ * "-np:int:number of passes:" ** Note that the description field should be terminated with a colon. * *
* /usr/tmp zilla -v -f abc **
Args
prints a formatted error message and returns null:
* (Note - the formatting is messed up in this html quote for
* some reason)
* * * error in args.parse: * bad argument, expected type int, got 'abc' for argument 'frame rate' * Usage: * **
* The usage message can also be generated by calling the method *
* args.usageMessage(argtemplate) ** * @author jplewis */ public class Args { /** * Returns a Hashtable containing the parsed arguments. * The return value is not needed if the accessor functions * (below) are used. */ public Hashtable parse(String argtemplate) { try { String[] argstrings = new String[cmdline.length]; for( int i=0; i < argstrings.length; i++ ) argstrings[i] = cmdline[i]; StringTokenizer st = new StringTokenizer(argtemplate, ":\n"); while( st.hasMoreTokens() ) { String key = st.nextToken(); String typestr = st.nextToken(); String usagestr = st.nextToken(); if (verbose) System.out.println("Parsing " + key +":"+ typestr +":"+ usagestr); Object val = argScan(argstrings, key, typestr, usagestr); if (val != null) { if (key.equals(TOK_required)) { args.put(new Integer(reqargcnt), val); reqargcnt ++; } else args.put(key, val); } } //while // check that all arguments were accepted for( int i=0; i < argstrings.length; i++ ) if (argstrings[i] != null) throw new Exception("argument '" + argstrings[i] + "' not recognized"); args.put("#args", new Integer(reqargcnt)); return args; } catch(Exception ex) { System.err.println("error in args.parse:"); System.err.println(ex.getMessage()); if (verbose) ex.printStackTrace(); usageMessage(argtemplate); return null; } } //parse //---------------------------------------------------------------- /** * The
getRequiredXX
and getOptionalXX
* accessor functions encapsulate the hashtable lookup for convenience.
*
* The hashtable can also be queried directly. In the case of
* required arguments, the key is 'new Integer(* Args args = new Args(commandline); * Hashtable h = args.parse(argtemplate); * Object argval = h.get(new Integer(2)); // 3rd required arg * if (argval != null) * f = ((Float)argval).floatValue(); ** * @exception Exception a descriptive exception. */ public String getRequiredString(int num) throws Exception { checkArgnum(num); return (String)(args.get(new Integer(num))); } public String getOptionalString(String key) { return (String)(args.get(key)); } public Integer getRequiredInteger(int num) throws Exception { checkArgnum(num); return (Integer)(args.get(new Integer(num))); } public Integer getOptionalInteger(String key) { return (Integer)(args.get(key)); } public Float getRequiredFloat(int num) throws Exception { checkArgnum(num); return (Float)(args.get(new Integer(num))); } public Float getOptionalFloat(String key) { return (Float)(args.get(key)); } public Double getRequiredDouble(int num) throws Exception { checkArgnum(num); return (Double)(args.get(new Integer(num))); } public Double getOptionalDouble(String key) { return (Double)(args.get(key)); } public Boolean getFlag(String key) { Object argval = args.get(key); return (Boolean)argval; } //---------------------------------------------------------------- private Hashtable args = new Hashtable(); private int reqargcnt; private String[] cmdline; final static String TOK_required = "+"; final static String TOK_flag = "flg"; final static String TOK_integer = "int"; final static String TOK_float = "flt"; final static String TOK_double = "dbl"; final static String TOK_string = "str"; private static Hashtable gTypeTab; static { gTypeTab = new Hashtable(); gTypeTab.put(TOK_string, "string"); gTypeTab.put(TOK_integer, "integer"); gTypeTab.put(TOK_float, "float"); gTypeTab.put(TOK_double, "double"); gTypeTab.put(TOK_flag, "true|false"); } private final static boolean verbose = false; /** * pass in the command line arguments obtained from
main
*/
public Args(String[] _cmdline)
{
cmdline = _cmdline;
reqargcnt = 0;
}
/** check that requested required argument number is in range.
*/
private void checkArgnum(int num)
throws Exception
{
if ((num < 0) || (num >= reqargcnt))
throw new Exception("requested argument number " + num +
"is outside range 0.." + (reqargcnt-1));
}
private static Object typeArg(String typestr, String val, String usagemsg)
throws Exception
{
if (verbose) System.out.println("typeArg " + typestr +" "+ val);
try {
if (typestr.equals(TOK_float))
return new Float(val);
else if (typestr.equals(TOK_double))
return new Double(val);
else if (typestr.equals(TOK_integer))
return new Integer(val);
else if (typestr.equals(TOK_flag))
return new Boolean(val);
else if (typestr.equals(TOK_string))
return val;
else
throw new Exception("unknown type: " + typestr);
}
catch(Exception ex)
{
String utypestr = (String)gTypeTab.get(typestr);
if (utypestr == null) utypestr = typestr;
throw new Exception("bad argument, expected type " + typestr +", got " +
val + " for argument '" + usagemsg + "'");
}
}
private static Object argScan(String[] args,
String key,
String typestr,
String usagestr)
throws Exception
{
Object val = null;
// required argument
if (key.equals(TOK_required)) {
int i=0;
while( i < args.length ) {
// for a required argument do not match option keys or flags.
// also do not match the arguments to the above.
if ((args[i] != null) && !args[i].startsWith("-") &&
!((i > 0) && (args[i-1]!=null) && args[i-1].startsWith("-")))
{
val = typeArg(typestr, args[i], usagestr);
args[i] = null;
break;
}
i++;
}
if (val == null)
throw new Exception("missing argument: '" + usagestr + "'");
}
// optional or flag argument
// val is null if not found
else {
int i=0;
while( i < args.length ) {
if (args[i] == null) { i++; continue; }
if (args[i].equals(key)) {
// optional argument
if (!typestr.equals(TOK_flag)) {
if ((i+1) == args.length)
throw new Exception("missing argument for option " + key);
val = typeArg(typestr, args[i+1], usagestr);
args[i] = args[i+1] = null;
i++;
}
// flag argument
else {
args[i] = null;
val = new Boolean(true);
}
}
i++;
}
}
return val;
} //argScan
// string of spaces, used to do column-formatted printing
static byte[] spaces = new byte[80];
static {
for( int i=0; i < 80; i++ ) spaces[i] = ' ';
}
/** print the argument template as a usage message
*/
public static void usageMessage(String argtemplate)
{
System.err.println("Usage: ");
StringTokenizer st = new StringTokenizer(argtemplate, ":\n");
while( st.hasMoreTokens() ) {
String key = st.nextToken();
String typestr = st.nextToken();
String usagestr = st.nextToken();
String utypestr = (String)gTypeTab.get(typestr);
if (utypestr == null) utypestr = typestr;
int len = 4;
System.err.write(spaces, 0, 4);
if (!key.equals(TOK_required)) {
System.err.print(key);
len += key.length();
if ((20-len) > 0) {
System.err.write(spaces, 0, 20-len);
len = 20;
}
else
System.err.print(" ");
}
System.err.print("<"+usagestr+">");
len += (usagestr.length()+2);
if (len < 65)
System.err.write(spaces, 0, 65-len);
else {
System.err.println("");
System.err.write(spaces,0, 20);
}
System.err.println("(" + utypestr + ")");
}
} //usageMessage
//----------------------------------------------------------------
/** a sample main for testing the Args parser
*/
public static void main(String[] cmdline)
{
try {
Args args = new Args(cmdline);
Hashtable h = args.parse("+:str:path to file:" +
"-framenum:int:frame number:" +
"+:str:username:" +
"-f:int:framerate:" +
"-v:flg:verbose");
if (h == null)
System.exit(1);
{
// get required arguments 1 and 2
String username = (String)h.get(new Integer(1));
String path = (String)h.get(new Integer(0));
// get optional integer argument bound to "-framenum"
int framenum = -1;
if (h.get("-framenum") != null)
framenum = ((Integer)h.get("-framenum")).intValue();
// get optional integer argument bound to "-f"
int framerate = -1;
if (h.get("-f") != null)
framerate = ((Integer)h.get("-f")).intValue();
// get optional flag argument "-v"
boolean verbose = false;
if (h.get("-v") != null)
verbose = ((Boolean)h.get("-v")).booleanValue();
// print results
System.out.println("path = " + path);
System.out.println("username = " + username);
System.out.println("framenum = " + framenum);
System.out.println("verbose = " + verbose);
}
{
// also test using our access wrapper functions
String Path = args.getRequiredString(0);
String Username = args.getRequiredString(1);
Integer Framenum = args.getOptionalInteger("-framenum");
Integer Framerate = args.getOptionalInteger("-f");
Boolean Verbose = args.getFlag("-v");
System.out.println("\nrepeat using access functions");
System.out.println("path = " + Path);
System.out.println("username = " + Username);
if (Framenum != null)
System.out.println("framenum = " + Framenum);
if (Framerate != null)
System.out.println("framerate = " + Framerate);
System.out.println("verbose = " + Verbose);
}
}
catch (Exception ex) {
System.err.println(ex.getMessage());
}
} //main
}