// Copyright (c) 2011, David J. Pearce (djp@ecs.vuw.ac.nz)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of the <organization> nor the
// names of its contributors may be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL DAVID J. PEARCE BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package jasm.util;
import java.io.File;
import java.io.PrintStream;
import java.util.*;
/**
* A small utility for parsing command-line options. It helps to take some of
* the hassle out of building the front-end of a Whiley compiler.
*
* @author David J. Pearce
*
*/
public final class OptArg {
/**
* The long form of the option. (e.g. for "-version", the long form is
* "version")
*/
public final String option;
/**
* The short form of the option. (e.g. for "-version", the short form might
* be "v" as in "-v")
*/
public final String shortForm;
/**
* The kind of argument accepted by this option (if any).
*/
public final Kind argument;
/**
* A description of the option. This is used when printing out "usage"
* information.
*/
public final String description;
/**
* A default value for the option (assuming it accepts an argument). This
* may be null if there is no default value.
*/
public final Object defaultValue;
/**
* Construct an option object which does not accept an argument.
*
* @param option
* Long form of option (e.g. "version" for "-version").
* @param description
* Description of the option.
*/
public OptArg(String option, String description) {
this.option = option;
this.shortForm = null;
this.argument = null;
this.description = description;
this.defaultValue = null;
}
/**
* Construct an option object with a short form which does not accept an
* argument.
*
* @param option
* Long form of option (e.g. "version" for "-version").
* @param shortForm
* Short form of the option (e.g. "v" for "-v").
* @param description
* Description of the option used for printing usage information.
*/
public OptArg(String option, String shortForm, String description) {
this.option = option;
this.shortForm = shortForm;
this.argument = null;
this.description = description;
this.defaultValue = null;
}
/**
* Construct an option object which accepts an argument.
*
* @param option
* Long form of option (e.g. "version" for "-version").
* @param argument
* Kind of argument accepted by this option (if any).
* @param description
* Description of the option used for printing usage information.
*/
public OptArg(String option, Kind argument, String description) {
this.option = option;
this.shortForm = null;
this.argument = argument;
this.description = description;
this.defaultValue = null;
}
/**
* Construct an option object with a short form which accepts an argument.
*
* @param option
* Long form of option (e.g. "version" for "-version").
* @param shortForm
* Short form of the option (e.g. "v" for "-v").
* @param argument
* Kind of argument accepted by this option (if any).
* @param description
* Description of the option used for printing usage information.
*/
public OptArg(String option, String shortForm, Kind argument, String description) {
this.option = option;
this.shortForm = shortForm;
this.argument = argument;
this.description = description;
this.defaultValue = null;
}
/**
* Construct an option object which accepts an argument and has a default value.
*
* @param option
* Long form of option (e.g. "version" for "-version").
* @param argument
* Kind of argument accepted by this option (if any).
* @param description
* Description of the option used for printing usage information.
* @param defaultValue.
* Default value of argument, or null if no default value.
*/
public OptArg(String option, Kind argument, String description, Object defaultValue) {
this.option = option;
this.shortForm = null;
this.argument = argument;
this.description = description;
this.defaultValue = defaultValue;
}
/**
* Construct an option object with a short form which accepts an argument and has a default value.
*
* @param option
* Long form of option (e.g. "version" for "-version").
* @param shortForm
* Short form of the option (e.g. "v" for "-v").
* @param argument
* Kind of argument accepted by this option (if any).
* @param description
* Description of the option used for printing usage information.
* @param defaultValue.
* Default value of argument, or null if no default value.
*/
public OptArg(String option, String shortForm, Kind argument, String description, Object defaultValue) {
this.option = option;
this.shortForm = shortForm;
this.argument = argument;
this.description = description;
this.defaultValue = defaultValue;
}
interface Kind {
void process(String arg, String option, Map<String,Object> options);
}
public final static STRING STRING = new STRING();
public final static INT INT = new INT();
public final static FILE FILE = new FILE();
public final static FILEDIR FILEDIR = new FILEDIR();
public final static FILELIST FILELIST = new FILELIST();
private static final class STRING implements Kind {
public void process(String arg, String option, Map<String,Object> options) {
options.put(arg,option);
}
public String toString() {
return "<string>";
}
}
private static final class INT implements Kind {
public void process(String arg, String option, Map<String,Object> options) {
options.put(arg,Integer.parseInt(option));
}
public String toString() {
return "<int>";
}
}
private static final class FILE implements Kind {
public void process(String arg, String option,
Map<String, Object> options) {
options.put(arg, new File(option));
}
public String toString() {
return "<file>";
}
}
private static final class FILEDIR implements Kind {
public void process(String arg, String option,
Map<String, Object> options) {
File dir = new File(option);
if(!dir.isDirectory()) {
throw new IllegalArgumentException("invalid path --- " + arg);
}
options.put(arg, dir);
}
public String toString() {
return "<filedir>";
}
}
private static final class FILELIST implements Kind {
public void process(String arg, String option, Map<String,Object> options) {
ArrayList<File> rs = new ArrayList<File>();
for(String r : option.split(File.pathSeparator)) {
rs.add(new File(r));
}
options.put(arg, rs);
}
public String toString() {
return "<filelist>";
}
}
/**
* Parse options from the list of arguments, removing those which are
* recognised. Anything which is not recognised is left as is. Throws
* <code>RuntimeException</code> if an unrecognised option is encountered
* (that is, a token starting with '-').
*
* @param args
* --- the list of argument strings. This is modified by removing
* those which are processed.
* @param options
* --- the list of OptArg defining which options should be
* processed
*/
public static Map<String,Object> parseOptions(List<String> args, OptArg... options) {
HashMap<String,Object> result = new HashMap<String,Object>();
HashMap<String,OptArg> optmap = new HashMap<String,OptArg>();
for(OptArg opt : options) {
if(opt.defaultValue != null) {
result.put(opt.option, opt.defaultValue);
}
optmap.put(opt.option, opt);
optmap.put(opt.shortForm, opt);
}
Iterator<String> iter = args.iterator();
while(iter.hasNext()) {
String arg = iter.next();
if (arg.startsWith("-")) {
arg = arg.substring(1,arg.length());
OptArg opt = optmap.get(arg);
if(opt != null) {
// matched
iter.remove(); // remove option from args list
Kind k = opt.argument;
if(k != null) {
String param = iter.next();
iter.remove();
k.process(opt.option,param,result);
} else {
result.put(opt.option,null);
}
} else {
throw new RuntimeException("unknown command-line option: -" + arg);
}
}
}
return result;
}
public static void usage(PrintStream output, OptArg...options) {
// first, work out gap information
int gap = 0;
ArrayList<OptArg> opts = new ArrayList();
for (OptArg opt : options) {
opts.add(opt);
int len = opt.option.length();
if(opt.argument != null) {
len = len + opt.argument.toString().length();
}
if(opt.shortForm != null) {
len = len + opt.shortForm.length();
opts.add(new OptArg(opt.shortForm,opt.argument,opt.description + " [short form]"));
}
gap = Math.max(gap, len);
}
gap = gap + 1;
// now, print the information
for (OptArg opt : opts) {
output.print(" -" + opt.option);
int rest = gap - opt.option.length();
output.print(" ");
if(opt.argument != null) {
String arg = opt.argument.toString();
rest -= arg.length();
output.print(arg);
}
for (int i = 0; i < rest; ++i) {
output.print(" ");
}
output.println(opt.description);
}
}
/**
* This splits strings of the form "x=y,v=w" into distinct components and
* puts them into a map. In the case of a string like "x,y=z" then x is
* loaded with the empty string.
*
* @param str
* @return
*/
private static Map<String, Object> splitConfig(String str) {
HashMap<String, Object> options = new HashMap<String, Object>();
String[] splits = str.split(",");
for (String s : splits) {
String[] p = s.split("=");
if (p.length == 1) {
options.put(p[0], Boolean.TRUE);
} else {
options.put(p[0], parseValue(p[1]));
}
}
return options;
}
private static Object parseValue(String str) {
if(str.equals("true")) {
return Boolean.TRUE;
} else if(str.equals("false")) {
return Boolean.FALSE;
} else if(Character.isDigit(str.charAt(0))) {
if(str.charAt(str.length()-1) == 'L') {
return Long.parseLong(str.substring(0,str.length()-1));
} else {
return Integer.parseInt(str);
}
} else {
return str;
}
}
}