/* * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * * Jalview is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * Jalview 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Jalview. If not, see . * The Jalview Authors are detailed in the 'AUTHORS' file. */ package jalview.bin.argparser; import java.io.File; import java.io.IOException; 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.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 (ie when not even square braces * are provided) */ protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:"; /** * 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 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}"; /** * On-the-fly substitution (not made at argument parsing time)! the current * structure filename extension */ private static final String STRUCTUREEXTENSION = "{structureextension}"; /** * On-the-fly substitution (not made at argument parsing time)! the current * structure filename base */ private static final String STRUCTUREBASENAME = "{structurebasename}"; /** * On-the-fly substitution (not made at argument parsing time)! the current * structure filename dir path */ private static final String STRUCTUREDIRNAME = "{structuredirname}"; /** * On-the-fly substitution (not made at argument parsing time)! increment the * on-the-fly counter and substitute the incremented value */ private static final String INCREMENTONTHEFLYCOUNTER = "{++m}"; /** * On-the-fly substitution (not made at argument parsing time)! the current * substitute with the on-the-fly counter */ private static final String ONTHEFLYCOUNTER = "{m}"; /** * the string used for on-the-fly structure filename substitutions */ private String currentStructureFilename = null; /** * the counter used for on-the-fly {m} substitutions */ private int ontheflyCounter = 0; /** * the current argfile */ private String argFile = null; /** * the linked id substitution string used to use the dir path of the latest */ /** --argfile name */ private static final String ARGFILEBASENAME = "{argfilebasename}"; /** * 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 = 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; private boolean oldArguments = false; private boolean mixedArguments = false; /** * saved examples of mixed arguments */ private String[] mixedExamples = new String[] { null, null }; static { argMap = new HashMap<>(); for (Arg a : EnumSet.allOf(Arg.class)) { for (String argName : a.getNames()) { if (argMap.containsKey(argName)) { Console.warn("Trying to add argument name multiple times: '" + argName + "'"); if (argMap.get(argName) != a) { Console.error( "Trying to add argument name multiple times for different Args: '" + argMap.get(argName).getName() + ":" + argName + "' and '" + a.getName() + ":" + argName + "'"); } continue; } argMap.put(argName, a); } } } public ArgParser(String[] args) { this(args, false, null); } public ArgParser(String[] args, boolean initsubstitutions, BootstrapArgs bsa) { /* * 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); } public ArgParser(List args, boolean initsubstitutions) { this(args, initsubstitutions, false, null); } 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) { mixedArguments = true; } else { 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); // look for double-dash, e.g. --arg if (arg.startsWith(DOUBLEDASH)) { 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); val = arg.substring(equalPos + 1); } else { 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" e.g. --nowrap boolean negated = false; if (a == null) { 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. 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 '" + 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 '" + a.argString() + "' does not expect a value (given as '" + arg + "'). Ignoring."); continue; } if (!a.hasOption(Opt.LINKED) && linkedId != null) { // set --argname[linkedId] when arg does not use linkedIds Console.error("Argument '" + a.argString() + "' does not expect a linked id (given as '" + arg + "'). Ignoring."); continue; } // String value(s) if (a.hasOption(Opt.STRING)) { if (equalPos >= 0) { if (a.hasOption(Opt.GLOB)) { // 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 { // val is already set -- will be saved in the ArgValue later in // the normal way } } else { // 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.NPP) { 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; String defaultLinkedId = defaultLinkedId(false); boolean usingDefaultLinkedId = false; if (a.hasOption(Opt.LINKED)) { if (linkedId == null) { 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.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); } } } // do not continue in this block for NOACTION args if (a.hasOption(Opt.NOACTION)) continue; ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId); // not dealing with both NODUPLICATEVALUES and GLOB if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val)) { Console.error("Argument '" + a.argString() + "' cannot contain a duplicate value ('" + val + "'). Ignoring this and subsequent occurrences."); continue; } // check for unique 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 '" + a.argString() + "' has a duplicate id ('" + id + "'). Ignoring."); continue; } // 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)) { if (!subvals.has(Arg.ALLSTRUCTURES.getName())) // && !subvals.has("structureid")) { subvals.put(Arg.ALLSTRUCTURES.getName(), "true"); addNewSubVals = true; } } ArgValues avs = avm.getOrCreateArgValues(a); // store appropriate String value(s) if (a.hasOption(Opt.STRING)) { if (a.hasOption(Opt.GLOB) && globVals != null && globVals.size() > 0) { Enumeration gve = Collections.enumeration(globVals); while (gve.hasMoreElements()) { 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 { // addValue(linkedId, type, avs, val, argIndex, true); addValue(linkedId, type, avs, addNewSubVals ? subvals : null, val, argIndex, true); } } else if (a.hasOption(Opt.BOOLEAN)) { setBoolean(linkedId, type, avs, !negated, argIndex); setNegated(linkedId, avs, negated); } else if (a.hasOption(Opt.UNARY)) { setBoolean(linkedId, type, avs, true, argIndex); } // remove the '*' or 'open*' linkedId that should be empty if it was // created if ((MATCHALLLINKEDIDS.equals(linkedId) || MATCHOPENEDLINKEDIDS.equals(linkedId)) && linkedArgs.containsKey(linkedId)) { linkedArgs.remove(linkedId); } } } } private void finaliseStoringArgValue(String linkedId, ArgValues avs) { 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) { return makeSubstitutions(val, linkedId, false); } public String makeSubstitutions(String val, String linkedId, boolean onthefly) { if (!this.substitutions || val == null) return val; String subvals; String rest; if (val.indexOf('[') == 0 && val.indexOf(']') > 1) { int closeBracket = val.indexOf(']'); if (val.length() == closeBracket) return val; subvals = val.substring(0, closeBracket + 1); rest = val.substring(closeBracket + 1); } else { subvals = ""; rest = val; } 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))); } } if (onthefly) { if (rest.contains(ONTHEFLYCOUNTER)) { rest = rest.replace(ONTHEFLYCOUNTER, String.valueOf(ontheflyCounter)); } if (rest.contains(INCREMENTONTHEFLYCOUNTER)) { rest = rest.replace(INCREMENTONTHEFLYCOUNTER, String.valueOf(++ontheflyCounter)); } if (currentStructureFilename != null) { if (rest.contains(STRUCTUREBASENAME)) { rest = rest.replace(STRUCTUREBASENAME, FileUtils .getBasename(new File(currentStructureFilename))); } if (rest.contains(STRUCTUREDIRNAME)) { rest = rest.replace(STRUCTUREDIRNAME, FileUtils.getDirname(new File(currentStructureFilename))); } } } return new StringBuilder(subvals).append(rest).toString(); } /* * 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. */ protected static List getShellGlobbedFilenameValues(Arg a, List args, int i) { List vals = new ArrayList<>(); while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH)) { 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) ? 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) { ArgValuesMap avm = linkedArgs.get(linkedId); return avm == null ? false : avm.containsArg(a); } public boolean getBoolean(Arg a) { if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY)) { Console.warn("Getting boolean from non boolean Arg '" + a.getName() + "'."); } return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a); } public boolean getBool(String linkedId, Arg a) { ArgValuesMap avm = linkedArgs.get(linkedId); if (avm == null) return a.getDefaultBoolValue(); ArgValues avs = avm.getArgValues(a); return avs == null ? a.getDefaultBoolValue() : avs.getBoolean(); } public List getLinkedIds() { return linkedOrder; } public ArgValuesMap getLinkedArgs(String id) { return linkedArgs.get(id); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("UNLINKED\n"); sb.append(argValuesMapToString(linkedArgs.get(null))); if (getLinkedIds() != null) { sb.append("LINKED\n"); for (String id : getLinkedIds()) { // already listed these as UNLINKED args if (id == null) continue; ArgValuesMap avm = getLinkedArgs(id); sb.append("ID: '").append(id).append("'\n"); sb.append(argValuesMapToString(avm)); } } return sb.toString(); } private static String argValuesMapToString(ArgValuesMap avm) { if (avm == null) return null; StringBuilder sb = new StringBuilder(); for (Arg a : avm.getArgKeys()) { ArgValues v = avm.getArgValues(a); sb.append(v.toString()); sb.append("\n"); } return sb.toString(); } public static ArgParser parseArgFiles(List argFilenameGlobs, boolean initsubstitutions, BootstrapArgs bsa) { List 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, initsubstitutions, bsa); } public static ArgParser parseArgFileList(List argFiles, boolean initsubstitutions, BootstrapArgs bsa) { List argsList = new ArrayList<>(); for (File argFile : argFiles) { if (!argFile.exists()) { String message = Arg.ARGFILE.argString() + EQUALS + "\"" + argFile.getPath() + "\": File does not exist."; Jalview.exit(message, ExitCode.FILE_NOT_FOUND); } try { 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 = 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; } } } 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; } public void setStructureFilename(String s) { this.currentStructureFilename = s; } }