import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
import java.util.EnumSet;
-import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
public class ArgParser
{
+ private static final String DOUBLEDASH = "--";
+
private static final String NEGATESTRING = "no";
+ // the linked id used for no id (not even square braces)
private static final String DEFAULTLINKEDID = "";
+ // the linked id used to increment the idCounter (and use the incremented
+ // value)
+ private static final String INCREMENTAUTOCOUNTERLINKEDID = "{++n}";
+
+ // the linked id used to use the idCounter
+ private static final String AUTOCOUNTERLINKEDID = "{n}";
+
+ private int idCounter = 0;
+
private static enum Opt
{
BOOLEAN, STRING, UNARY, MULTI, LINKED, NODUPLICATEVALUES, BOOTSTRAP,
GLOB
}
- // These bootstrap args are simply parsed before a full parse of arguments and
- // so are accessible at an earlier stage to (e.g.) set debug log leve, provide
- // a props file (that might set log level), run headlessly, read an argfile
- // instead of other args.
- private static final List<Arg> bootstrapArgs;
-
public enum Arg
{
/*
// expects a string value
SORTBYTREE.setOptions(true, Opt.BOOLEAN);
USAGESTATS.setOptions(true, Opt.BOOLEAN);
- OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
+ OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB);
OPEN2.setOptions(Opt.STRING, Opt.LINKED);
PROPS.setOptions(Opt.STRING, Opt.BOOTSTRAP);
QUESTIONNAIRE.setOptions(Opt.STRING);
DEBUG.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP);
QUIET.setOptions(Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP);
ARGFILE.setOptions(Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB);
+ // BOOTSTRAP args are parsed before a full parse of arguments and
+ // so are accessible at an earlier stage to (e.g.) set debug log level,
+ // provide a props file (that might set log level), run headlessly, read
+ // an argfile instead of other args.
+
}
private final String[] argNames;
return ret;
}
- /*
- public Object getAppletValue(String key, String def, boolean asString)
- {
- Object value;
- return (appletParams == null ? null
- : (value = appletParams.get(key.toLowerCase())) == null ? def
- : asString ? "" + value : value);
- }
- */
-
// new style
private static final Map<String, Arg> argMap;
static
{
argMap = new HashMap<>();
- bootstrapArgs = new ArrayList<>();
for (Arg a : EnumSet.allOf(Arg.class))
{
- if (a.hasOption(Opt.BOOTSTRAP))
- bootstrapArgs.add(a);
for (String argName : a.getNames())
{
if (argMap.containsKey(argName))
public ArgParser(String[] args)
{
- this(Arrays.asList(args));
+ // make a mutable new ArrayList so that shell globbing parser works
+ this(new ArrayList<>(Arrays.asList(args)));
}
public ArgParser(List<String> args)
{
- Enumeration<String> argE = Collections.enumeration(args);
+ init(args);
+ }
+
+ private void init(List<String> args)
+ {
+ // Enumeration<String> argE = Collections.enumeration(args);
int argIndex = 0;
- while (argE.hasMoreElements())
+ // while (argE.hasMoreElements())
+ for (int i = 0; i < args.size(); i++)
{
- String arg = argE.nextElement();
+ // String arg = argE.nextElement();
+ String arg = args.get(i);
String argName = null;
String val = null;
+ List<String> vals = null; // for Opt.GLOB only
String linkedId = null;
- if (arg.startsWith("--"))
+ if (arg.startsWith(DOUBLEDASH))
{
int equalPos = arg.indexOf('=');
if (equalPos > -1)
{
- argName = arg.substring(2, equalPos);
+ argName = arg.substring(DOUBLEDASH.length(), equalPos);
val = arg.substring(equalPos + 1);
}
else
{
- argName = arg.substring(2);
+ argName = arg.substring(DOUBLEDASH.length());
}
int idOpen = argName.indexOf('[');
int idClose = argName.indexOf(']');
if (a.hasOption(Opt.STRING) && equalPos == -1)
{
// take next arg as value if required, and '=' was not found
- if (!argE.hasMoreElements())
+ // if (!argE.hasMoreElements())
+ if (i + 1 >= args.size())
{
// no value to take for arg, which wants a value
Console.error("Argument '" + a.getName()
+ "' requires a value, none given. Ignoring.");
continue;
}
- val = argE.nextElement();
+ // deal with bash globs here (--arg val* is expanded before reaching
+ // the JVM). Note that SubVals cannot be used in this case.
+ // If using the --arg=val then the glob is preserved and Java globs
+ // will be used later. SubVals can be used.
+ if (a.hasOption(Opt.GLOB))
+ {
+ vals.addAll(getShellGlobbedFilenameValues(a, args, i + 1));
+ }
+ else
+ {
+ val = args.get(i + 1);
+ }
}
- // use default linkedId for linked arguments
- if (a.hasOption(Opt.LINKED) && linkedId == null)
- linkedId = DEFAULTLINKEDID;
+ String autoCounterString = null;
+ if (a.hasOption(Opt.LINKED))
+ {
+ if (linkedId == null)
+ {
+ // use default linkedId for linked arguments
+ linkedId = DEFAULTLINKEDID;
+ Console.debug(
+ "Changing linkedId to '" + linkedId + "' from " + arg);
+ }
+ else if (linkedId.equals(AUTOCOUNTERLINKEDID))
+ {
+ // turn {n} to the autoCounter
+ autoCounterString = Integer.toString(idCounter);
+ linkedId = autoCounterString;
+ Console.debug(
+ "Changing linkedId to '" + linkedId + "' from " + arg);
+ }
+ else if (linkedId.equals(INCREMENTAUTOCOUNTERLINKEDID))
+ {
+ // turn {++n} to the incremented autoCounter
+ autoCounterString = Integer.toString(++idCounter);
+ linkedId = autoCounterString;
+ Console.debug(
+ "Changing linkedId to '" + linkedId + "' from " + arg);
+ }
+ }
if (!linkedArgs.containsKey(linkedId))
linkedArgs.put(linkedId, new ArgValuesMap());
ArgValuesMap avm = linkedArgs.get(linkedId);
+ // not dealing with both NODUPLICATEVALUES and GLOB
if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
{
Console.error("Argument '--" + argName
// store appropriate value
if (a.hasOption(Opt.STRING))
{
- avs.addValue(val, argIndex);
+ if (a.hasOption(Opt.GLOB) && vals != null && vals.size() > 0)
+ {
+ for (String v : vals)
+ avs.addValue(val, argIndex++);
+ }
+ else
+ {
+ avs.addValue(val, argIndex);
+ }
}
else if (a.hasOption(Opt.BOOLEAN))
{
}
}
+ /*
+ * A helper method to take a list of String args where we're expecting
+ * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
+ * and the index of the globbed arg, here 1. It returns a
+ * List<String> {"file1", "file2", "file3"}
+ * *and remove these from the original list object* so that processing
+ * can continue from where it has left off, e.g. args has become
+ * {"--previousargs", "--arg", "--otheroptionsornot"}
+ * so the next increment carries on from the next --arg if available.
+ */
+ private static List<String> getShellGlobbedFilenameValues(Arg a,
+ List<String> args, int i)
+ {
+ List<String> vals = new ArrayList<>();
+ while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
+ {
+ vals.add(args.remove(i));
+ if (!a.hasOption(Opt.GLOB))
+ break;
+ }
+ return vals;
+ }
+
public boolean isSet(Arg a)
{
return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
{
if (!argFile.exists())
{
- System.err.println(
- "--" + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
- + argFile.getPath() + "\": File does not exist.");
+ System.err.println(DOUBLEDASH
+ + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
+ + argFile.getPath() + "\": File does not exist.");
System.exit(2);
}
try
argsList.addAll(Files.readAllLines(Paths.get(argFile.getPath())));
} catch (IOException e)
{
- System.err.println("--"
+ System.err.println(DOUBLEDASH
+ Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
+ argFile.getPath() + "\": File could not be read.");
System.exit(3);
public static BootstrapArgs getBootstrapArgs(String[] args)
{
- return new BootstrapArgs(args);
+ List<String> argList = new ArrayList<>(Arrays.asList(args));
+ return new BootstrapArgs(argList);
}
- private BootstrapArgs(String[] args)
+ private BootstrapArgs(List<String> args)
{
init(args);
}
- private void init(String[] args)
+ private void init(List<String> args)
{
if (args == null)
return;
- Enumeration<String> argE = Collections
- .enumeration(Arrays.asList(args));
- while (argE.hasMoreElements())
+ for (int i = 0; i < args.size(); i++)
{
- String arg = argE.nextElement();
+ String arg = args.get(i);
String argName = null;
String val = null;
- if (arg.startsWith("--"))
+ if (arg.startsWith(ArgParser.DOUBLEDASH))
{
int equalPos = arg.indexOf('=');
if (equalPos > -1)
{
- argName = arg.substring(2, equalPos);
+ argName = arg.substring(ArgParser.DOUBLEDASH.length(),
+ equalPos);
val = arg.substring(equalPos + 1);
}
else
{
- argName = arg.substring(2);
+ argName = arg.substring(ArgParser.DOUBLEDASH.length());
val = "true";
}
+
Arg a = argMap.get(argName);
- if (a != null && bootstrapArgs.contains(a))
+
+ if (a == null || !a.hasOption(Opt.BOOTSTRAP))
+ {
+ // not a valid bootstrap arg
+ continue;
+ }
+
+ if (a.hasOption(Opt.STRING))
{
- if (!bootstrapArgMap.containsKey(a)
- || bootstrapArgMap.get(a) == null)
+ if (equalPos == -1)
{
- List<String> aL = new ArrayList<>();
- aL.add(val);
- bootstrapArgMap.put(a, aL);
+ addAll(a, ArgParser.getShellGlobbedFilenameValues(a, args,
+ i + 1));
}
- else if (a.hasOption(Opt.MULTI))
+ else
{
- List<String> aL = bootstrapArgMap.get(a); // already established
- // this is not null
- aL.add(val);
- bootstrapArgMap.put(a, aL);
+ if (a.hasOption(Opt.GLOB))
+ addAll(a, FileUtils.getFilenamesFromGlob(val));
+ else
+ add(a, val);
}
}
+ else
+ {
+ add(a, val);
+ }
}
}
}
return bootstrapArgMap.get(a);
}
- public String get(Arg a)
+ private List<String> getOrCreateList(Arg a)
{
- if (bootstrapArgMap.containsKey(a))
+ List<String> l = getList(a);
+ if (l == null)
{
- List<String> aL = bootstrapArgMap.get(a);
- return (aL == null || aL.size() == 0) ? null : aL.get(0);
+ l = new ArrayList<>();
+ putList(a, l);
}
- return null;
+ return l;
+ }
+
+ private void putList(Arg a, List<String> l)
+ {
+ bootstrapArgMap.put(a, l);
+ }
+
+ /*
+ * Creates a new list if not used before,
+ * adds the value unless the existing list is non-empty
+ * and the arg is not MULTI (so first expressed value is
+ * retained).
+ */
+ private void add(Arg a, String s)
+ {
+ List<String> l = getOrCreateList(a);
+ if (a.hasOption(Opt.MULTI) || l.size() == 0)
+ {
+ l.add(s);
+ }
+ }
+
+ private void addAll(Arg a, List<String> al)
+ {
+ List<String> l = getOrCreateList(a);
+ if (a.hasOption(Opt.MULTI))
+ {
+ l.addAll(al);
+ }
+ }
+
+ /*
+ * Retrieves the first value even if MULTI.
+ * A convenience for non-MULTI args.
+ */
+ public String get(Arg a)
+ {
+ if (!bootstrapArgMap.containsKey(a))
+ return null;
+ List<String> aL = bootstrapArgMap.get(a);
+ return (aL == null || aL.size() == 0) ? null : aL.get(0);
}
}
}
\ No newline at end of file
import jalview.bin.ArgParser.Arg;
import jalview.bin.ArgParser.BootstrapArgs;
-import jalview.util.FileUtils;
public class ArgParserTest
{
@Test(groups = "Functional", dataProvider = "argLines")
- public void parseArgsAndSubValsTest(String commandLineArgs)
+ public void parseArgsAndSubValsTest(String commandLineArgs, Arg a,
+ String other)
{
String[] args = commandLineArgs.split("\\s*");
ArgParser argparser = new ArgParser(args);
{
Properties bP = Cache.bootstrapProperties(b.get(Arg.PROPS));
Assert.assertTrue(other.equals(bP.get(Cache.BOOTSTRAP_TEST)));
+ Assert.assertFalse(bP.contains("NOT" + Cache.BOOTSTRAP_TEST));
}
else if (a == Arg.ARGFILE)
{
- List<File> files = FileUtils.getFilesFromGlob(b.get(a));
+ List<String> filenames = b.getList(a);
boolean found = false;
- for (File f : files)
+ for (String s : filenames)
{
+ File f = new File(s);
File fo = new File(other);
try
{
{
}
}
- Assert.assertTrue(found);
+ Assert.assertTrue(found,
+ "File '" + other + "' not found in shell expanded glob '"
+ + commandLineArgs + "'");
}
}
@Test(groups = "Functional", dataProvider = "argFiles")
- public void argFilesTest(String commandLineArgs, Arg a)
+ public void argFilesTest(String commandLineArgs, Arg a, String other)
{
String[] args = commandLineArgs.split("\\s+");
BootstrapArgs b = BootstrapArgs.getBootstrapArgs(args);
Assert.assertTrue(b.contains(a));
+ Assert.assertFalse(b.contains(Arg.OPEN));
if (a == Arg.PROPS)
{
Properties bP = Cache.bootstrapProperties(b.get(Arg.PROPS));
Assert.assertTrue("true".equals(bP.get(Cache.BOOTSTRAP_TEST)));
}
+ }
+ @DataProvider(name = "argLinesTest")
+ public Object[][] argLinesTest()
+ {
+ return new Object[][] {
+ // can't use this one yet as it doesn't get shell expanded
+ { "--argfile test/jalview/bin/argparser/argfile*.txt", Arg.ARGFILE,
+ "test/jalview/bin/argparser/argfile0.txt" }, };
}
@DataProvider(name = "argLines")
{ "--open=test/jalview/bin/argparser/test1.fa --props=test/jalview/bin/argparser/testProps.jvprops",
Arg.PROPS, "true" },
{ "--argfile=test/jalview/bin/argparser/argfile*.txt", Arg.ARGFILE,
- "test/jalview/bin/argparser/argfile1.txt" } };
+ "test/jalview/bin/argparser/argfile0.txt" },
+ { "--argfile=test/jalview/bin/argparser/argfile*.txt", Arg.ARGFILE,
+ "test/jalview/bin/argparser/argfile1.txt" },
+ { "--argfile=test/jalview/bin/argparser/argfile*.txt", Arg.ARGFILE,
+ "test/jalview/bin/argparser/argfile2.txt" } };
}
@DataProvider(name = "argFiles")
public Object[][] argFiles()
{
- return new Object[][] {
- { "--argfile=test/jalview/bin/argparser/argfile0.txt", Arg.OPEN,
- "test/jalview/bin/argfiles/test1.fa" } };
+ return new Object[][] { {
+ "--argfile=test/jalview/bin/argparser/argfile0.txt --open=shouldntbeabootstrap",
+ Arg.ARGFILE, "test/jalview/bin/argfiles/test1.fa" } };
}
}