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;
import java.util.Map;
import java.util.Set;
+import jalview.util.FileUtils;
+
public class ArgParser
{
+ private static final String DOUBLEDASH = "--";
+
private static final String NEGATESTRING = "no";
- private static final String DEFAULTLINKEDID = "";
+ // the default linked id prefix used for no id (not even square braces)
+ private static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
+
+ // the counter added to the default linked id prefix
+ private int defaultLinkedIdCounter = 0;
+
+ // 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
+ 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
{
/*
VSESS, OUTPUT, OUTPUTTYPE, SSANNOTATION, NOTEMPFAC, TEMPFAC,
TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP,
NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, CLOSE, DEBUG("d"), QUIET("q"),
- ARGFILE;
+ ARGFILE, INCREMENT, NPP("n++");
static
{
// 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);
CLOSE.setOptions(Opt.UNARY, Opt.LINKED);
DEBUG.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP);
QUIET.setOptions(Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP);
- ARGFILE.setOptions(Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP);
+ ARGFILE.setOptions(Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB);
+ INCREMENT.setOptions(Opt.UNARY, Opt.MULTI);
+ NPP.setOptions(Opt.UNARY, Opt.MULTI);
+ // 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;
+ // default and auto counter increments
+ if (a == Arg.INCREMENT)
+ {
+ defaultLinkedIdCounter++;
+ continue;
+ }
+ else if (a == Arg.NPP)
+ {
+ idCounter++;
+ continue;
+ }
+
+ String autoCounterString = null;
+ boolean usingAutoCounterLinkedId = false;
+ String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
+ .append(Integer.toString(defaultLinkedIdCounter))
+ .toString();
+ boolean usingDefaultLinkedId = false;
+ if (a.hasOption(Opt.LINKED))
+ {
+ if (linkedId == null)
+ {
+ // use default linkedId for linked arguments
+ linkedId = defaultLinkedId;
+ usingDefaultLinkedId = true;
+ Console.debug(
+ "Changing linkedId to '" + linkedId + "' from " + arg);
+ }
+ else if (linkedId.equals(AUTOCOUNTERLINKEDID))
+ {
+ // turn {n} to the autoCounter
+ autoCounterString = Integer.toString(idCounter);
+ linkedId = autoCounterString;
+ usingAutoCounterLinkedId = true;
+ 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;
+ usingAutoCounterLinkedId = true;
+ 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))
{
{
// allow a default linked id for single usage
if (linkedId == null)
- linkedId = DEFAULTLINKEDID;
+ linkedId = defaultLinkedId;
// store the order of linkedIds
if (linkedOrder == null)
linkedOrder = new ArrayList<>();
}
}
+ /*
+ * 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);
}
}
- public static ArgParser parseArgFiles(List<String> argFilenames)
+ public static ArgParser parseArgFiles(List<String> argFilenameGlobs)
+ {
+ List<File> argFiles = new ArrayList<>();
+
+ for (String pattern : argFilenameGlobs)
+ {
+ // I don't think we want to dedup files, making life easier
+ argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
+ }
+
+ return parseArgFileList(argFiles);
+ }
+
+ public static ArgParser parseArgFileList(List<File> argFiles)
{
List<String> argsList = new ArrayList<>();
- for (String argFilename : argFilenames)
+ for (File argFile : argFiles)
{
- File argFile = new File(argFilename);
if (!argFile.exists())
{
- System.err
- .println("--" + Arg.ARGFILE.name().toLowerCase(Locale.ROOT)
- + "=\"" + argFilename + "\": 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(argFilename)));
+ argsList.addAll(Files.readAllLines(Paths.get(argFile.getPath())));
} catch (IOException e)
{
- System.err.println(
- "--" + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
- + argFilename + "\": File could not be read.");
+ System.err.println(DOUBLEDASH
+ + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
+ + argFile.getPath() + "\": File could not be read.");
System.exit(3);
}
}
// only need one
private static Map<Arg, List<String>> bootstrapArgMap = new HashMap<>();
- private BootstrapArgs(String[] args)
+ public static BootstrapArgs getBootstrapArgs(String[] args)
{
- init(args);
+ List<String> argList = new ArrayList<>(Arrays.asList(args));
+ return new BootstrapArgs(argList);
}
- public static BootstrapArgs getBootstrapArgs(String[] args)
+ private BootstrapArgs(List<String> args)
{
- return new BootstrapArgs(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))
{
- if (!bootstrapArgMap.containsKey(a)
- || bootstrapArgMap.get(a) == null)
+ // not a valid bootstrap arg
+ continue;
+ }
+
+ if (a.hasOption(Opt.STRING))
+ {
+ 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