X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fbin%2Fargparser%2FArgParser.java;h=fd42bb24a506e3903c3fbbac38317c9d1cdc98d6;hb=85240f268c71a9d0dc91dc196a93cb10d29794d3;hp=c6580a7f53c7d4b79745dba2e61c5ca933f93425;hpb=5bbd8870c4840d4092e69a1fb772f453dab9245d;p=jalview.git diff --git a/src/jalview/bin/argparser/ArgParser.java b/src/jalview/bin/argparser/ArgParser.java index c6580a7..fd42bb2 100644 --- a/src/jalview/bin/argparser/ArgParser.java +++ b/src/jalview/bin/argparser/ArgParser.java @@ -26,49 +26,158 @@ import java.nio.file.Files; 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 jalview.bin.Console; import jalview.bin.Jalview; +import jalview.bin.Jalview.ExitCode; import jalview.bin.argparser.Arg.Opt; +import jalview.bin.argparser.Arg.Type; import jalview.util.FileUtils; +import jalview.util.HttpUtils; public class ArgParser { + protected static final String SINGLEDASH = "-"; + protected static final String DOUBLEDASH = "--"; + public static final char EQUALS = '='; + + public static final String STDOUTFILENAME = "-"; + protected static final String NEGATESTRING = "no"; - // the default linked id prefix used for no id (not even square braces) + /** + * the default linked id prefix used for no id (ie when not even square braces + * are provided) + */ protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:"; - // the counter added to the default linked id prefix + /** + * the linkedId string used to match all linkedIds seen so far + */ + protected static final String MATCHALLLINKEDIDS = "*"; + + /** + * the linkedId string used to match all of the last --open'ed linkedIds + */ + protected static final String MATCHOPENEDLINKEDIDS = "open*"; + + /** + * 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 substitution string used to use the defaultLinkedIdCounter + */ + private static final String DEFAULTLINKEDIDCOUNTER = "{}"; + + /** + * the linked id prefix used for --open files. NOW the same as DEFAULT + */ + protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX; + + /** + * the counter used for {n} substitutions + */ + private int linkedIdAutoCounter = 0; + + /** + * the linked id substitution string used to increment the idCounter (and use + * the incremented value) + */ + private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}"; + + /** + * the linked id substitution string used to use the idCounter + */ + private static final String LINKEDIDAUTOCOUNTER = "{n}"; + + /** + * the linked id substitution string used to use the filename extension of + * --append or --open + */ + private static final String LINKEDIDEXTENSION = "{extension}"; + + /** + * the linked id substitution string used to use the base filename of --append + */ + /** or --open */ + private static final String LINKEDIDBASENAME = "{basename}"; + + /** + * the linked id substitution string used to use the dir path of --append or + * --open + */ + private static final String LINKEDIDDIRNAME = "{dirname}"; - // the linked id used to use the idCounter - private static final String AUTOCOUNTERLINKEDID = "{n}"; + /** + * the current argfile + */ + private String argFile = null; - private int idCounter = 0; + /** + * the linked id substitution string used to use the dir path of the latest + */ + /** --argfile name */ + private static final String ARGFILEBASENAME = "{argfilebasename}"; - // flag to say whether {n} subtitutions in output filenames should be made. - // Turn on and off with --subs and --nosubs - private boolean substitutions = false; + /** + * the linked id substitution string used to use the dir path of the latest + * --argfile name + */ + private static final String ARGFILEDIRNAME = "{argfiledirname}"; + + /** + * flag to say whether {n} subtitutions in output filenames should be made. + * Turn on and off with --substitutions and --nosubstitutions Start with it on + */ + private boolean substitutions = true; + + /** + * flag to say whether the default linkedId is the current default linked id + * + * or ALL linkedIds + */ + private boolean allLinkedIds = false; + + /** + * flag to say whether the structure arguments should be applied to all + * structures with this linked id + */ + private boolean allStructures = false; protected static final Map argMap; protected Map linkedArgs = new HashMap<>(); - protected List linkedOrder = null; + protected List linkedOrder = new ArrayList<>(); + + protected List storedLinkedIds = new ArrayList<>(); + + protected List argList = new ArrayList<>(); + + private static final char ARGFILECOMMENT = '#'; + + private int argIndex = 0; + + private BootstrapArgs bootstrapArgs = null; - protected List argList; + private boolean oldArguments = false; + + private boolean mixedArguments = false; + + /** + * saved examples of mixed arguments + */ + private String[] mixedExamples = new String[] { null, null }; static { @@ -80,7 +189,7 @@ public class ArgParser if (argMap.containsKey(argName)) { Console.warn("Trying to add argument name multiple times: '" - + argName + "'"); // RESTORE THIS WHEN MERGED + + argName + "'"); if (argMap.get(argName) != a) { Console.error( @@ -98,52 +207,124 @@ public class ArgParser public ArgParser(String[] args) { - // Make a mutable new ArrayList so that shell globbing parser works. - // (When shell file globbing is used, there are a sequence of non-Arg - // arguments (which are the expanded globbed filenames) that need to be - // consumed by the --open/--argfile/etc Arg which is most easily done by - // removing these filenames from the list one at a time. This can't be done - // with an ArrayList made with only Arrays.asList(String[] args). ) - this(new ArrayList<>(Arrays.asList(args))); + this(args, false, null); } - public ArgParser(List args) + public ArgParser(String[] args, boolean initsubstitutions, + BootstrapArgs bsa) { - parse(args); + /* + * Make a mutable new ArrayList so that shell globbing parser works. + * (When shell file globbing is used, there are a sequence of non-Arg + * arguments (which are the expanded globbed filenames) that need to be + * consumed by the --append/--argfile/etc Arg which is most easily done + * by removing these filenames from the list one at a time. This can't be + * done with an ArrayList made with only Arrays.asList(String[] args) as + * that is not mutable. ) + */ + this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false, + bsa); } - private void parse(List args) + public ArgParser(List args, boolean initsubstitutions) { - int argIndex = 0; - boolean initialFilenameArgs = true; - for (int i = 0; i < args.size(); i++) - { - String arg = args.get(i); + this(args, initsubstitutions, false, null); + } - // If the first arguments do not start with "--" or "-" or is "open" and - // is a filename that exists it is probably a file/list of files to open - // so we fake an Arg.OPEN argument and when adding files only add the - // single arg[i] and increment the defaultLinkedIdCounter so that each of - // these files is opened separately. - if (initialFilenameArgs && !arg.startsWith(DOUBLEDASH) - && !arg.startsWith("-") && new File(arg).exists()) + public ArgParser(List args, boolean initsubstitutions, + boolean allowPrivate, BootstrapArgs bsa) + { + // do nothing if there are no "--" args and (some "-" args || >0 arg is + // "open") + boolean d = false; + boolean dd = false; + for (String arg : args) + { + if (arg.startsWith(DOUBLEDASH)) + { + dd = true; + if (mixedExamples[1] == null) + { + mixedExamples[1] = arg; + } + } + else if ((arg.startsWith("-") && !arg.equals(STDOUTFILENAME)) + || arg.equals("open")) + { + d = true; + if (mixedExamples[0] == null) + { + mixedExamples[0] = arg; + } + } + } + if (d) + { + if (dd) { - arg = DOUBLEDASH + Arg.OPEN.getName(); - Console.debug("Adding argument '" + args.get(i) - + "' as a file to be opened"); + mixedArguments = true; } else { - initialFilenameArgs = false; + oldArguments = true; } + } + + if (oldArguments || mixedArguments) + { + // leave it to the old style -- parse an empty list + parse(new ArrayList(), false, false); + return; + } + + if (bsa != null) + this.bootstrapArgs = bsa; + else + this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args); + parse(args, initsubstitutions, allowPrivate); + } + + private void parse(List args, boolean initsubstitutions, + boolean allowPrivate) + { + this.substitutions = initsubstitutions; + + /* + * If the first argument does not start with "--" or "-" or is not "open", + * and is a filename that exists or a URL, it is probably a file/list of + * files to open so we insert an Arg.OPEN argument before it. This will + * mean the list of files at the start of the arguments are all opened + * separately. + */ + if (args.size() > 0) + { + String arg0 = args.get(0); + if (arg0 != null + && (!arg0.startsWith(DOUBLEDASH) && !arg0.startsWith("-") + && !arg0.equals("open") && (new File(arg0).exists() + || HttpUtils.startsWithHttpOrHttps(arg0)))) + { + // insert "--open" at the start + args.add(0, Arg.OPEN.argString()); + } + } + + for (int i = 0; i < args.size(); i++) + { + String arg = args.get(i); - String argName = null; - String val = null; - List vals = null; // for Opt.GLOB only - String linkedId = null; + // look for double-dash, e.g. --arg if (arg.startsWith(DOUBLEDASH)) { - int equalPos = arg.indexOf('='); + String argName = null; + String val = null; + List globVals = null; // for Opt.GLOB only + SubVals globSubVals = null; // also for use by Opt.GLOB only + String linkedId = null; + Type type = null; + + // look for equals e.g. --arg=value + int equalPos = arg.indexOf(EQUALS); if (equalPos > -1) { argName = arg.substring(DOUBLEDASH.length(), equalPos); @@ -153,44 +334,99 @@ public class ArgParser { argName = arg.substring(DOUBLEDASH.length()); } + + // look for linked ID e.g. --arg[linkedID] int idOpen = argName.indexOf('['); int idClose = argName.indexOf(']'); - if (idOpen > -1 && idClose == argName.length() - 1) { linkedId = argName.substring(idOpen + 1, idClose); argName = argName.substring(0, idOpen); } + // look for type modification e.g. --help-opening + int dashPos = argName.indexOf(SINGLEDASH); + if (dashPos > -1) + { + String potentialArgName = argName.substring(0, dashPos); + Arg potentialArg = argMap.get(potentialArgName); + if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE)) + { + String typeName = argName.substring(dashPos + 1); + try + { + type = Type.valueOf(typeName); + } catch (IllegalArgumentException e) + { + type = Type.INVALID; + } + argName = argName.substring(0, dashPos); + } + } + Arg a = argMap.get(argName); - // check for boolean prepended by "no" + // check for boolean prepended by "no" e.g. --nowrap boolean negated = false; - if (a == null && argName.startsWith(NEGATESTRING) && argMap - .containsKey(argName.substring(NEGATESTRING.length()))) + if (a == null) { - argName = argName.substring(NEGATESTRING.length()); - a = argMap.get(argName); - negated = true; + if (argName.startsWith(NEGATESTRING) && argMap + .containsKey(argName.substring(NEGATESTRING.length()))) + { + argName = argName.substring(NEGATESTRING.length()); + a = argMap.get(argName); + negated = true; + } + else + { + // after all other args, look for Opt.PREFIXKEV args if still not + // found + for (Arg potentialArg : EnumSet.allOf(Arg.class)) + { + if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null + && argName.startsWith(potentialArg.getName()) + && equalPos > -1) + { + val = argName.substring(potentialArg.getName().length()) + + EQUALS + val; + argName = argName.substring(0, + potentialArg.getName().length()); + a = potentialArg; + break; + } + } + } } // check for config errors if (a == null) { // arg not found - Console.error("Argument '" + arg + "' not recognised. Ignoring."); + Console.error("Argument '" + arg + "' not recognised. Exiting."); + Jalview.exit( + "Invalid argument used." + System.lineSeparator() + "Use" + + System.lineSeparator() + "jalview " + + Arg.HELP.argString() + System.lineSeparator() + + "for a usage statement.", + ExitCode.INVALID_ARGUMENT); + continue; + } + if (a.hasOption(Opt.PRIVATE) && !allowPrivate) + { + Console.error( + "Argument '" + a.argString() + "' is private. Ignoring."); continue; } if (!a.hasOption(Opt.BOOLEAN) && negated) { // used "no" with a non-boolean option - Console.error("Argument '--" + NEGATESTRING + argName + Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName + "' not a boolean option. Ignoring."); continue; } if (!a.hasOption(Opt.STRING) && equalPos > -1) { // set --argname=value when arg does not accept values - Console.error("Argument '--" + argName + Console.error("Argument '" + a.argString() + "' does not expect a value (given as '" + arg + "'). Ignoring."); continue; @@ -198,188 +434,297 @@ public class ArgParser if (!a.hasOption(Opt.LINKED) && linkedId != null) { // set --argname[linkedId] when arg does not use linkedIds - Console.error("Argument '--" + argName + Console.error("Argument '" + a.argString() + "' does not expect a linked id (given as '" + arg + "'). Ignoring."); continue; } - if (a.hasOption(Opt.STRING) && equalPos == -1) + // String value(s) + if (a.hasOption(Opt.STRING)) { - // take next arg as value if required, and '=' was not found - // 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; - } - // 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)) + if (equalPos >= 0) { - // if this is the first argument with a file list at the start of - // the args we add filenames from index i instead of i+1 - // and assume they should be opened separately - if (initialFilenameArgs) + if (a.hasOption(Opt.GLOB)) { - val = args.get(i); - defaultLinkedIdCounter++; + // strip off and save the SubVals to be added individually later + globSubVals = new SubVals(val); + // make substitutions before looking for files + String fileGlob = makeSubstitutions(globSubVals.getContent(), + linkedId); + globVals = FileUtils.getFilenamesFromGlob(fileGlob); } else { - vals = getShellGlobbedFilenameValues(a, args, i + 1); + // val is already set -- will be saved in the ArgValue later in + // the normal way } } else { - val = args.get(i + 1); + // There is no "=" so value is next arg or args (possibly shell + // glob-expanded) + 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; + } + // 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)) + { + // if this is the first argument with a file list at the start of + // the args we add filenames from index i instead of i+1 + globVals = getShellGlobbedFilenameValues(a, args, i + 1); + } + else + { + val = args.get(i + 1); + } } } // make NOACTION adjustments // default and auto counter increments - if (a == Arg.INCREMENT) - { - defaultLinkedIdCounter++; - } - else if (a == Arg.NPP) + if (a == Arg.NPP) { - idCounter++; + linkedIdAutoCounter++; } else if (a == Arg.SUBSTITUTIONS) { substitutions = !negated; } + else if (a == Arg.SETARGFILE) + { + argFile = val; + } + else if (a == Arg.UNSETARGFILE) + { + argFile = null; + } + else if (a == Arg.ALL) + { + allLinkedIds = !negated; + } + else if (a == Arg.ALLSTRUCTURES) + { + allStructures = !negated; + } + + if (a.hasOption(Opt.STORED)) + { + // reset the lastOpenedLinkedIds list + this.storedLinkedIds = new ArrayList<>(); + } + + // this is probably only Arg.NEW and Arg.OPEN + if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER)) + { + // use the next default prefixed OPENLINKEDID + defaultLinkedId(true); + } String autoCounterString = null; - boolean usingAutoCounterLinkedId = false; - String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX) - .append(Integer.toString(defaultLinkedIdCounter)) - .toString(); + String defaultLinkedId = defaultLinkedId(false); 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); + if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWMULTIID) + && val.contains(MATCHALLLINKEDIDS)) + { + // --output=*.ext is shorthand for --output {basename}.ext + // --output=*/*.ext is shorthand for + // --output {dirname}/{basename}.ext + // (or --image=*.ext) + linkedId = allLinkedIds ? MATCHALLLINKEDIDS + : MATCHOPENEDLINKEDIDS; + val = FileUtils.convertWildcardsToPath(val, MATCHALLLINKEDIDS, + LINKEDIDDIRNAME, LINKEDIDBASENAME); + } + else if (allLinkedIds && a.hasOption(Opt.ALLOWMULTIID)) + { + linkedId = MATCHALLLINKEDIDS; + } + else if (a.hasOption(Opt.ALLOWMULTIID) + && this.storedLinkedIds != null + && this.storedLinkedIds.size() > 0) + { + linkedId = MATCHOPENEDLINKEDIDS; + } + else + { + // use default linkedId for linked arguments + linkedId = defaultLinkedId; + usingDefaultLinkedId = true; + Console.debug("Changing linkedId to '" + linkedId + "' from " + + arg); + } } - else if (linkedId.equals(INCREMENTAUTOCOUNTERLINKEDID)) + else { - // turn {++n} to the incremented autoCounter - autoCounterString = Integer.toString(++idCounter); - linkedId = autoCounterString; - usingAutoCounterLinkedId = true; - Console.debug( - "Changing linkedId to '" + linkedId + "' from " + arg); + if (linkedId.contains(LINKEDIDAUTOCOUNTER)) + { + // turn {n} to the autoCounter + autoCounterString = Integer.toString(linkedIdAutoCounter); + linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER, + autoCounterString); + Console.debug("Changing linkedId to '" + linkedId + "' from " + + arg); + } + if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER)) + { + // turn {++n} to the incremented autoCounter + autoCounterString = Integer.toString(++linkedIdAutoCounter); + linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER, + autoCounterString); + Console.debug("Changing linkedId to '" + linkedId + "' from " + + arg); + } } } - if (!linkedArgs.containsKey(linkedId)) - linkedArgs.put(linkedId, new ArgValuesMap()); - - // do not continue for NOACTION args + // do not continue in this block for NOACTION args if (a.hasOption(Opt.NOACTION)) continue; - ArgValuesMap avm = linkedArgs.get(linkedId); + ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId); // not dealing with both NODUPLICATEVALUES and GLOB if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val)) { - Console.error("Argument '--" + argName + Console.error("Argument '" + a.argString() + "' cannot contain a duplicate value ('" + val + "'). Ignoring this and subsequent occurrences."); continue; } // check for unique id - SubVals sv = ArgParser.getSubVals(val); - String id = sv.get(ArgValues.ID); + SubVals subvals = new SubVals(val); + boolean addNewSubVals = false; + String id = subvals.get(ArgValues.ID); if (id != null && avm.hasId(a, id)) { - Console.error("Argument '--" + argName + "' has a duplicate id ('" - + id + "'). Ignoring."); + Console.error("Argument '" + a.argString() + + "' has a duplicate id ('" + id + "'). Ignoring."); continue; } - ArgValues avs = avm.getOrCreateArgValues(a); - if (avs == null) + // set allstructures to all non-primary structure options in this linked + // id if --allstructures has been set + if (allStructures && (a.getType() == Type.STRUCTURE + // || a.getType() == Type.STRUCTUREIMAGE) + ) && !a.hasOption(Opt.PRIMARY)) { - avs = new ArgValues(a); + if (!subvals.has(Arg.ALLSTRUCTURES.getName())) + // && !subvals.has("structureid")) + { + subvals.put(Arg.ALLSTRUCTURES.getName(), "true"); + addNewSubVals = true; + } } - boolean argIndexIncremented = false; - // store appropriate value + ArgValues avs = avm.getOrCreateArgValues(a); + + // store appropriate String value(s) if (a.hasOption(Opt.STRING)) { - if (a.hasOption(Opt.GLOB) && vals != null && vals.size() > 0) + if (a.hasOption(Opt.GLOB) && globVals != null + && globVals.size() > 0) { - for (String v : vals) + Enumeration gve = Collections.enumeration(globVals); + while (gve.hasMoreElements()) { - avs.addValue(makeSubstitutions(v), argIndex++); - argIndexIncremented = true; + String v = gve.nextElement(); + SubVals vsv = new SubVals(globSubVals, v); + addValue(linkedId, type, avs, vsv, v, argIndex++, true); + // if we're using defaultLinkedId and the arg increments the + // counter: + if (gve.hasMoreElements() && usingDefaultLinkedId + && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER)) + { + // increment the default linkedId + linkedId = defaultLinkedId(true); + // get new avm and avs + avm = linkedArgs.get(linkedId); + avs = avm.getOrCreateArgValues(a); + } } } else { - avs.addValue(makeSubstitutions(val), argIndex); + // addValue(linkedId, type, avs, val, argIndex, true); + addValue(linkedId, type, avs, addNewSubVals ? subvals : null, + val, argIndex, true); } } else if (a.hasOption(Opt.BOOLEAN)) { - avs.setBoolean(!negated, argIndex); - avs.setNegated(negated); + setBoolean(linkedId, type, avs, !negated, argIndex); + setNegated(linkedId, avs, negated); } else if (a.hasOption(Opt.UNARY)) { - avs.setBoolean(true, argIndex); + setBoolean(linkedId, type, avs, true, argIndex); } - avs.incrementCount(); - if (!argIndexIncremented) - argIndex++; - // store in appropriate place - if (a.hasOption(Opt.LINKED)) + // remove the '*' or 'open*' linkedId that should be empty if it was + // created + if ((MATCHALLLINKEDIDS.equals(linkedId) + || MATCHOPENEDLINKEDIDS.equals(linkedId)) + && linkedArgs.containsKey(linkedId)) { - // allow a default linked id for single usage - if (linkedId == null) - linkedId = defaultLinkedId; - // store the order of linkedIds - if (linkedOrder == null) - linkedOrder = new ArrayList<>(); - if (!linkedOrder.contains(linkedId)) - linkedOrder.add(linkedId); - } - - // store arg in the list of args used - if (argList == null) - argList = new ArrayList<>(); - if (!argList.contains(a)) - argList.add(a); + linkedArgs.remove(linkedId); + } } } } - private String makeSubstitutions(String val) + private void finaliseStoringArgValue(String linkedId, ArgValues avs) { - if (!this.substitutions) + Arg a = avs.arg(); + incrementCount(linkedId, avs); + argIndex++; + + // store in appropriate place + if (a.hasOption(Opt.LINKED)) + { + // store the order of linkedIds + if (!linkedOrder.contains(linkedId)) + linkedOrder.add(linkedId); + } + + // store arg in the list of args used + if (!argList.contains(a)) + argList.add(a); + } + + private String defaultLinkedId(boolean increment) + { + String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX) + .append(Integer.toString(defaultLinkedIdCounter)).toString(); + if (increment) + { + while (linkedArgs.containsKey(defaultLinkedId)) + { + defaultLinkedIdCounter++; + defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX) + .append(Integer.toString(defaultLinkedIdCounter)) + .toString(); + } + } + getOrCreateLinkedArgValuesMap(defaultLinkedId); + return defaultLinkedId; + } + + public String makeSubstitutions(String val, String linkedId) + { + if (!this.substitutions || val == null) return val; String subvals; @@ -397,9 +742,44 @@ public class ArgParser subvals = ""; rest = val; } - rest.replace(AUTOCOUNTERLINKEDID, String.valueOf(idCounter)); - rest.replace(INCREMENTAUTOCOUNTERLINKEDID, String.valueOf(++idCounter)); - rest.replace("{}", String.valueOf(defaultLinkedIdCounter)); + if (rest.contains(LINKEDIDAUTOCOUNTER)) + rest = rest.replace(LINKEDIDAUTOCOUNTER, + String.valueOf(linkedIdAutoCounter)); + if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER)) + rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER, + String.valueOf(++linkedIdAutoCounter)); + if (rest.contains(DEFAULTLINKEDIDCOUNTER)) + rest = rest.replace(DEFAULTLINKEDIDCOUNTER, + String.valueOf(defaultLinkedIdCounter)); + ArgValuesMap avm = linkedArgs.get(linkedId); + if (avm != null) + { + if (rest.contains(LINKEDIDBASENAME)) + { + rest = rest.replace(LINKEDIDBASENAME, avm.getBasename()); + } + if (rest.contains(LINKEDIDEXTENSION)) + { + rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension()); + } + if (rest.contains(LINKEDIDDIRNAME)) + { + rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname()); + } + } + if (argFile != null) + { + if (rest.contains(ARGFILEBASENAME)) + { + rest = rest.replace(ARGFILEBASENAME, + FileUtils.getBasename(new File(argFile))); + } + if (rest.contains(ARGFILEDIRNAME)) + { + rest = rest.replace(ARGFILEDIRNAME, + FileUtils.getDirname(new File(argFile))); + } + } return new StringBuilder(subvals).append(rest).toString(); } @@ -407,12 +787,11 @@ public class ArgParser /* * 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 {"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. + * and the index of the globbed arg, here 1. It returns a List {"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. */ protected static List getShellGlobbedFilenameValues(Arg a, List args, int i) @@ -420,16 +799,31 @@ public class ArgParser List vals = new ArrayList<>(); while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH)) { - vals.add(args.remove(i)); + vals.add(FileUtils.substituteHomeDir(args.remove(i))); if (!a.hasOption(Opt.GLOB)) break; } return vals; } + public BootstrapArgs getBootstrapArgs() + { + return bootstrapArgs; + } + public boolean isSet(Arg a) { - return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a); + return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a); + } + + public boolean isSetAtAll(Arg a) + { + for (String linkedId : linkedOrder) + { + if (isSet(linkedId, a)) + return true; + } + return false; } public boolean isSet(String linkedId, Arg a) @@ -438,7 +832,7 @@ public class ArgParser return avm == null ? false : avm.containsArg(a); } - public boolean getBool(Arg a) + public boolean getBoolean(Arg a) { if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY)) { @@ -457,12 +851,12 @@ public class ArgParser return avs == null ? a.getDefaultBoolValue() : avs.getBoolean(); } - public List linkedIds() + public List getLinkedIds() { return linkedOrder; } - public ArgValuesMap linkedArgs(String id) + public ArgValuesMap getLinkedArgs(String id) { return linkedArgs.get(id); } @@ -473,16 +867,16 @@ public class ArgParser StringBuilder sb = new StringBuilder(); sb.append("UNLINKED\n"); sb.append(argValuesMapToString(linkedArgs.get(null))); - if (linkedIds() != null) + if (getLinkedIds() != null) { sb.append("LINKED\n"); - for (String id : linkedIds()) + for (String id : getLinkedIds()) { // already listed these as UNLINKED args if (id == null) continue; - ArgValuesMap avm = linkedArgs(id); + ArgValuesMap avm = getLinkedArgs(id); sb.append("ID: '").append(id).append("'\n"); sb.append(argValuesMapToString(avm)); } @@ -504,12 +898,8 @@ public class ArgParser return sb.toString(); } - public static SubVals getSubVals(String item) - { - return new SubVals(item); - } - - public static ArgParser parseArgFiles(List argFilenameGlobs) + public static ArgParser parseArgFiles(List argFilenameGlobs, + boolean initsubstitutions, BootstrapArgs bsa) { List argFiles = new ArrayList<>(); @@ -519,33 +909,298 @@ public class ArgParser argFiles.addAll(FileUtils.getFilesFromGlob(pattern)); } - return parseArgFileList(argFiles); + return parseArgFileList(argFiles, initsubstitutions, bsa); } - public static ArgParser parseArgFileList(List argFiles) + public static ArgParser parseArgFileList(List argFiles, + boolean initsubstitutions, BootstrapArgs bsa) { List argsList = new ArrayList<>(); for (File argFile : argFiles) { if (!argFile.exists()) { - String message = DOUBLEDASH - + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\"" + String message = Arg.ARGFILE.argString() + EQUALS + "\"" + argFile.getPath() + "\": File does not exist."; - Jalview.exit(message, 2); + Jalview.exit(message, ExitCode.FILE_NOT_FOUND); } try { - argsList.addAll(Files.readAllLines(Paths.get(argFile.getPath()))); + String setargfile = new StringBuilder(Arg.SETARGFILE.argString()) + .append(EQUALS).append(argFile.getCanonicalPath()) + .toString(); + argsList.add(setargfile); + argsList.addAll(readArgFile(argFile)); + argsList.add(Arg.UNSETARGFILE.argString()); } catch (IOException e) { - String message = DOUBLEDASH - + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\"" - + argFile.getPath() + "\": File could not be read."; - Jalview.exit(message, 3); + String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath() + + "\": File could not be read."; + Jalview.exit(message, ExitCode.FILE_NOT_READABLE); + } + } + // Third param "true" uses Opt.PRIVATE args --setargile=argfile and + // --unsetargfile + return new ArgParser(argsList, initsubstitutions, true, bsa); + } + + protected static List readArgFile(File argFile) + { + List args = new ArrayList<>(); + if (argFile != null && argFile.exists()) + { + try + { + for (String line : Files.readAllLines(Paths.get(argFile.getPath()))) + { + if (line != null && line.length() > 0 + && line.charAt(0) != ARGFILECOMMENT) + args.add(line); + } + } catch (IOException e) + { + String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath() + + "\": File could not be read."; + Console.debug(message, e); + Jalview.exit(message, ExitCode.FILE_NOT_READABLE); + } + } + return args; + } + + // the following methods look for the "*" linkedId and add the argvalue to all + // linkedId ArgValues if it does. + /** + * This version inserts the subvals sv into all created values + */ + private void addValue(String linkedId, Type type, ArgValues avs, + SubVals sv, String v, int argIndex, boolean doSubs) + { + this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false, + argIndex, doSubs); + } + + private void setBoolean(String linkedId, Type type, ArgValues avs, + boolean b, int argIndex) + { + this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null, + b, argIndex, false); + } + + private void setNegated(String linkedId, ArgValues avs, boolean b) + { + this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null, + b, 0, false); + } + + private void incrementCount(String linkedId, ArgValues avs) + { + this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null, + null, false, 0, false); + } + + private enum Op + { + ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT + } + + private void argValueOperation(Op op, String linkedId, Type type, + ArgValues avs, SubVals sv, String v, boolean b, int argIndex, + boolean doSubs) + { + // default to merge subvals if subvals are provided + argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex, + doSubs); + } + + /** + * The following operations look for the "*" and "open*" linkedIds and add the + * argvalue to all appropriate linkedId ArgValues if it does. If subvals are + * supplied, they are inserted into all new set values. + * + * @param op + * The ArgParser.Op operation + * @param linkedId + * The String linkedId from the ArgValuesMap + * @param type + * The Arg.Type to attach to this ArgValue + * @param avs + * The ArgValues for this linkedId + * @param sv + * Use these SubVals on the ArgValue + * @param merge + * Merge the SubVals with any existing on the value. False will + * replace unless sv is null + * @param v + * The value of the ArgValue (may contain subvals). + * @param b + * The boolean value of the ArgValue. + * @param argIndex + * The argIndex for the ArgValue. + * @param doSubs + * Whether to perform substitutions on the subvals and value. + */ + private void argValueOperation(Op op, String linkedId, Type type, + ArgValues avs, SubVals sv, boolean merge, String v, boolean b, + int argIndex, boolean doSubs) + { + Arg a = avs.arg(); + + List wildcardLinkedIds = null; + if (a.hasOption(Opt.ALLOWMULTIID)) + { + switch (linkedId) + { + case MATCHALLLINKEDIDS: + wildcardLinkedIds = getLinkedIds(); + break; + case MATCHOPENEDLINKEDIDS: + wildcardLinkedIds = this.storedLinkedIds; + break; + } + } + + // if we're not a wildcard linkedId and the arg is marked to be stored, add + // to storedLinkedIds + if (linkedId != null && wildcardLinkedIds == null + && a.hasOption(Opt.STORED) + && !storedLinkedIds.contains(linkedId)) + { + storedLinkedIds.add(linkedId); + } + + // if we are a wildcard linkedId, apply the arg and value to all appropriate + // linkedIds + if (wildcardLinkedIds != null) + { + for (String id : wildcardLinkedIds) + { + // skip incorrectly stored wildcard ids! + if (id == null || MATCHALLLINKEDIDS.equals(id) + || MATCHOPENEDLINKEDIDS.equals(id)) + { + continue; + } + ArgValuesMap avm = linkedArgs.get(id); + // don't set an output if there isn't an input + if (a.hasOption(Opt.REQUIREINPUT) + && !avm.hasArgWithOption(Opt.INPUT)) + continue; + + ArgValues tavs = avm.getOrCreateArgValues(a); + switch (op) + { + + case ADDVALUE: + String val = v; + if (sv != null) + { + if (doSubs) + { + sv = new SubVals(sv, val, merge); + val = makeSubstitutions(sv.getContent(), id); + } + tavs.addValue(sv, type, val, argIndex, true); + } + else + { + if (doSubs) + { + val = makeSubstitutions(v, id); + } + tavs.addValue(type, val, argIndex, true); + } + finaliseStoringArgValue(id, tavs); + break; + + case SETBOOLEAN: + tavs.setBoolean(type, b, argIndex, true); + finaliseStoringArgValue(id, tavs); + break; + + case SETNEGATED: + tavs.setNegated(b, true); + break; + + case INCREMENTCOUNT: + tavs.incrementCount(); + break; + + default: + break; + + } + + } + } + else // no wildcard linkedId -- do it simpler + { + switch (op) + { + case ADDVALUE: + String val = v; + if (sv != null) + { + if (doSubs) + { + val = makeSubstitutions(v, linkedId); + sv = new SubVals(sv, val); + } + avs.addValue(sv, type, val, argIndex, false); + } + else + { + if (doSubs) + { + val = makeSubstitutions(v, linkedId); + } + avs.addValue(type, val, argIndex, false); + } + finaliseStoringArgValue(linkedId, avs); + break; + + case SETBOOLEAN: + avs.setBoolean(type, b, argIndex, false); + finaliseStoringArgValue(linkedId, avs); + break; + + case SETNEGATED: + avs.setNegated(b, false); + break; + + case INCREMENTCOUNT: + avs.incrementCount(); + break; + + default: + break; } } - return new ArgParser(argsList); + } + + private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId) + { + if (linkedArgs.containsKey(linkedId) + && linkedArgs.get(linkedId) != null) + return linkedArgs.get(linkedId); + + linkedArgs.put(linkedId, new ArgValuesMap(linkedId)); + return linkedArgs.get(linkedId); + } + + public boolean isOldStyle() + { + return oldArguments; + } + + public boolean isMixedStyle() + { + return mixedArguments; + } + + public String[] getMixedExamples() + { + return mixedExamples; } } \ No newline at end of file