2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.bin.argparser;
24 import java.io.IOException;
25 import java.nio.file.Files;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.EnumSet;
31 import java.util.Enumeration;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Locale;
37 import jalview.bin.Cache;
38 import jalview.bin.Console;
39 import jalview.bin.Jalview;
40 import jalview.bin.Jalview.ExitCode;
41 import jalview.bin.argparser.Arg.Opt;
42 import jalview.bin.argparser.Arg.Type;
43 import jalview.util.FileUtils;
44 import jalview.util.HttpUtils;
46 public class ArgParser
48 protected static final String SINGLEDASH = "-";
50 protected static final String DOUBLEDASH = "--";
52 public static final char EQUALS = '=';
54 public static final String STDOUTFILENAME = "-";
56 protected static final String NEGATESTRING = "no";
59 * the default linked id prefix used for no id (ie when not even square braces
62 protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
65 * the linkedId string used to match all linkedIds seen so far
67 protected static final String MATCHALLLINKEDIDS = "*";
70 * the linkedId string used to match all of the last --open'ed linkedIds
72 protected static final String MATCHOPENEDLINKEDIDS = "open*";
75 * the counter added to the default linked id prefix
77 private int defaultLinkedIdCounter = 0;
80 * the substitution string used to use the defaultLinkedIdCounter
82 private static final String DEFAULTLINKEDIDCOUNTER = "{}";
85 * the linked id prefix used for --open files. NOW the same as DEFAULT
87 protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
90 * the counter used for {n} substitutions
92 private int linkedIdAutoCounter = 0;
95 * the linked id substitution string used to increment the idCounter (and use
96 * the incremented value)
98 private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
101 * the linked id substitution string used to use the idCounter
103 private static final String LINKEDIDAUTOCOUNTER = "{n}";
106 * the linked id substitution string used to use the filename extension of
109 private static final String LINKEDIDEXTENSION = "{extension}";
112 * the linked id substitution string used to use the base filename of --append
115 private static final String LINKEDIDBASENAME = "{basename}";
118 * the linked id substitution string used to use the dir path of --append or
121 private static final String LINKEDIDDIRNAME = "{dirname}";
124 * the current argfile
126 private String argFile = null;
129 * the linked id substitution string used to use the dir path of the latest
131 /** --argfile name */
132 private static final String ARGFILEBASENAME = "{argfilebasename}";
135 * the linked id substitution string used to use the dir path of the latest
138 private static final String ARGFILEDIRNAME = "{argfiledirname}";
141 * flag to say whether {n} subtitutions in output filenames should be made.
142 * Turn on and off with --substitutions and --nosubstitutions Start with it on
144 private boolean substitutions = true;
147 * flag to say whether the default linkedId is the current default linked id
151 private boolean allLinkedIds = false;
154 * flag to say whether the default linkedId is the current default linked id
155 * or OPENED linkedIds
157 private boolean openedLinkedIds = false;
160 * flag to say whether the structure arguments should be applied to all
161 * structures with this linked id
163 private boolean allStructures = false;
165 protected static final Map<String, Arg> argMap;
167 protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
169 protected List<String> linkedOrder = new ArrayList<>();
171 protected List<String> storedLinkedIds = new ArrayList<>();
173 protected List<Arg> argList = new ArrayList<>();
175 private static final char ARGFILECOMMENT = '#';
177 private int argIndex = 0;
179 private BootstrapArgs bootstrapArgs = null;
181 private boolean oldArguments = false;
183 private boolean mixedArguments = false;
186 * saved examples of mixed arguments
188 private String[] mixedExamples = new String[] { null, null };
192 argMap = new HashMap<>();
193 for (Arg a : EnumSet.allOf(Arg.class))
195 for (String argName : a.getNames())
197 if (argMap.containsKey(argName))
199 Console.warn("Trying to add argument name multiple times: '"
201 if (argMap.get(argName) != a)
204 "Trying to add argument name multiple times for different Args: '"
205 + argMap.get(argName).getName() + ":" + argName
206 + "' and '" + a.getName() + ":" + argName
211 argMap.put(argName, a);
216 public ArgParser(String[] args)
218 this(args, false, null);
221 public ArgParser(String[] args, boolean initsubstitutions,
225 * Make a mutable new ArrayList so that shell globbing parser works.
226 * (When shell file globbing is used, there are a sequence of non-Arg
227 * arguments (which are the expanded globbed filenames) that need to be
228 * consumed by the --append/--argfile/etc Arg which is most easily done
229 * by removing these filenames from the list one at a time. This can't be
230 * done with an ArrayList made with only Arrays.asList(String[] args) as
231 * that is not mutable. )
233 this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
237 public ArgParser(List<String> args, boolean initsubstitutions)
239 this(args, initsubstitutions, false, null);
242 public ArgParser(List<String> args, boolean initsubstitutions,
243 boolean allowPrivate, BootstrapArgs bsa)
245 // do nothing if there are no "--" args and (some "-" args || >0 arg is
249 for (String arg : args)
251 if (arg.startsWith(DOUBLEDASH))
254 if (mixedExamples[1] == null)
256 mixedExamples[1] = arg;
259 else if (arg.startsWith("-") || arg.equals("open"))
262 if (mixedExamples[0] == null)
264 mixedExamples[0] = arg;
272 mixedArguments = true;
280 if (oldArguments || mixedArguments)
282 // leave it to the old style -- parse an empty list
283 parse(new ArrayList<String>(), false, false);
288 this.bootstrapArgs = bsa;
290 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
291 parse(args, initsubstitutions, allowPrivate);
294 private void parse(List<String> args, boolean initsubstitutions,
295 boolean allowPrivate)
297 this.substitutions = initsubstitutions;
300 * If the first argument does not start with "--" or "-" or is not "open",
301 * and is a filename that exists or a URL, it is probably a file/list of
302 * files to open so we insert an Arg.OPEN argument before it. This will
303 * mean the list of files at the start of the arguments are all opened
308 String arg0 = args.get(0);
310 && (!arg0.startsWith(DOUBLEDASH) && !arg0.startsWith("-")
311 && !arg0.equals("open") && (new File(arg0).exists()
312 || HttpUtils.startsWithHttpOrHttps(arg0))))
314 // insert "--open" at the start
315 args.add(0, Arg.OPEN.argString());
319 for (int i = 0; i < args.size(); i++)
321 String arg = args.get(i);
323 // look for double-dash, e.g. --arg
324 if (arg.startsWith(DOUBLEDASH))
326 String argName = null;
328 List<String> globVals = null; // for Opt.GLOB only
329 SubVals globSubVals = null; // also for use by Opt.GLOB only
330 String linkedId = null;
333 // look for equals e.g. --arg=value
334 int equalPos = arg.indexOf(EQUALS);
337 argName = arg.substring(DOUBLEDASH.length(), equalPos);
338 val = arg.substring(equalPos + 1);
342 argName = arg.substring(DOUBLEDASH.length());
345 // look for linked ID e.g. --arg[linkedID]
346 int idOpen = argName.indexOf('[');
347 int idClose = argName.indexOf(']');
348 if (idOpen > -1 && idClose == argName.length() - 1)
350 linkedId = argName.substring(idOpen + 1, idClose);
351 argName = argName.substring(0, idOpen);
354 // look for type modification e.g. --help-opening
355 int dashPos = argName.indexOf(SINGLEDASH);
358 String potentialArgName = argName.substring(0, dashPos);
359 Arg potentialArg = argMap.get(potentialArgName);
360 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
362 String typeName = argName.substring(dashPos + 1);
365 type = Type.valueOf(typeName);
366 } catch (IllegalArgumentException e)
370 argName = argName.substring(0, dashPos);
374 Arg a = argMap.get(argName);
375 // check for boolean prepended by "no" e.g. --nowrap
376 boolean negated = false;
379 if (argName.startsWith(NEGATESTRING) && argMap
380 .containsKey(argName.substring(NEGATESTRING.length())))
382 argName = argName.substring(NEGATESTRING.length());
383 a = argMap.get(argName);
388 // after all other args, look for Opt.PREFIXKEV args if still not
390 for (Arg potentialArg : EnumSet.allOf(Arg.class))
392 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
393 && argName.startsWith(potentialArg.getName())
396 val = argName.substring(potentialArg.getName().length())
398 argName = argName.substring(0,
399 potentialArg.getName().length());
407 // check for config errors
411 Console.error("Argument '" + arg + "' not recognised. Exiting.");
413 "Invalid argument used." + System.lineSeparator() + "Use"
414 + System.lineSeparator() + "jalview "
415 + Arg.HELP.argString() + System.lineSeparator()
416 + "for a usage statement.",
417 ExitCode.INVALID_ARGUMENT);
420 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
423 "Argument '" + a.argString() + "' is private. Ignoring.");
426 if (!a.hasOption(Opt.BOOLEAN) && negated)
428 // used "no" with a non-boolean option
429 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
430 + "' not a boolean option. Ignoring.");
433 if (!a.hasOption(Opt.STRING) && equalPos > -1)
435 // set --argname=value when arg does not accept values
436 Console.error("Argument '" + a.argString()
437 + "' does not expect a value (given as '" + arg
441 if (!a.hasOption(Opt.LINKED) && linkedId != null)
443 // set --argname[linkedId] when arg does not use linkedIds
444 Console.error("Argument '" + a.argString()
445 + "' does not expect a linked id (given as '" + arg
451 if (a.hasOption(Opt.STRING))
455 if (a.hasOption(Opt.GLOB))
457 // strip off and save the SubVals to be added individually later
458 globSubVals = new SubVals(val);
459 // make substitutions before looking for files
460 String fileGlob = makeSubstitutions(globSubVals.getContent(),
462 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
466 // val is already set -- will be saved in the ArgValue later in
472 // There is no "=" so value is next arg or args (possibly shell
474 if (i + 1 >= args.size())
476 // no value to take for arg, which wants a value
477 Console.error("Argument '" + a.getName()
478 + "' requires a value, none given. Ignoring.");
481 // deal with bash globs here (--arg val* is expanded before reaching
482 // the JVM). Note that SubVals cannot be used in this case.
483 // If using the --arg=val then the glob is preserved and Java globs
484 // will be used later. SubVals can be used.
485 if (a.hasOption(Opt.GLOB))
487 // if this is the first argument with a file list at the start of
488 // the args we add filenames from index i instead of i+1
489 globVals = getShellGlobbedFilenameValues(a, args, i + 1);
493 val = args.get(i + 1);
498 // make NOACTION adjustments
499 // default and auto counter increments
502 linkedIdAutoCounter++;
504 else if (a == Arg.SUBSTITUTIONS)
506 substitutions = !negated;
508 else if (a == Arg.SETARGFILE)
512 else if (a == Arg.UNSETARGFILE)
516 else if (a == Arg.ALL)
518 allLinkedIds = !negated;
519 openedLinkedIds = false;
521 else if (a == Arg.OPENED)
523 openedLinkedIds = !negated;
524 allLinkedIds = false;
526 else if (a == Arg.ALLSTRUCTURES)
528 allStructures = !negated;
531 if (a.hasOption(Opt.STORED))
533 // reset the lastOpenedLinkedIds list
534 this.storedLinkedIds = new ArrayList<>();
537 // this is probably only Arg.NEW and Arg.OPEN
538 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
540 // use the next default prefixed OPENLINKEDID
541 defaultLinkedId(true);
544 String autoCounterString = null;
545 String defaultLinkedId = defaultLinkedId(false);
546 boolean usingDefaultLinkedId = false;
547 if (a.hasOption(Opt.LINKED))
549 if (linkedId == null)
551 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWALL)
552 && val.startsWith(MATCHALLLINKEDIDS))
554 // --output=*.ext is shorthand for --all --output {basename}.ext
555 // (or --image=*.ext)
557 openedLinkedIds = false;
558 linkedId = MATCHALLLINKEDIDS;
559 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
560 + val.substring(MATCHALLLINKEDIDS.length());
562 else if (a.hasOption(Opt.OUTPUTFILE)
563 && a.hasOption(Opt.ALLOWALL)
564 && val.startsWith(MATCHOPENEDLINKEDIDS))
566 // --output=open*.ext is shorthand for --opened --output
568 // (or --image=open*.ext)
569 openedLinkedIds = true;
570 allLinkedIds = false;
571 linkedId = MATCHOPENEDLINKEDIDS;
572 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
573 + val.substring(MATCHOPENEDLINKEDIDS.length());
575 else if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
577 linkedId = MATCHALLLINKEDIDS;
579 else if (openedLinkedIds && a.hasOption(Opt.ALLOWALL))
581 linkedId = MATCHOPENEDLINKEDIDS;
585 // use default linkedId for linked arguments
586 linkedId = defaultLinkedId;
587 usingDefaultLinkedId = true;
588 Console.debug("Changing linkedId to '" + linkedId + "' from "
594 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
596 // turn {n} to the autoCounter
597 autoCounterString = Integer.toString(linkedIdAutoCounter);
598 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
600 Console.debug("Changing linkedId to '" + linkedId + "' from "
603 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
605 // turn {++n} to the incremented autoCounter
606 autoCounterString = Integer.toString(++linkedIdAutoCounter);
607 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
609 Console.debug("Changing linkedId to '" + linkedId + "' from "
615 // do not continue in this block for NOACTION args
616 if (a.hasOption(Opt.NOACTION))
619 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
621 // not dealing with both NODUPLICATEVALUES and GLOB
622 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
624 Console.error("Argument '" + a.argString()
625 + "' cannot contain a duplicate value ('" + val
626 + "'). Ignoring this and subsequent occurrences.");
630 // check for unique id
631 SubVals subvals = new SubVals(val);
632 boolean addNewSubVals = false;
633 String id = subvals.get(ArgValues.ID);
634 if (id != null && avm.hasId(a, id))
636 Console.error("Argument '" + a.argString()
637 + "' has a duplicate id ('" + id + "'). Ignoring.");
641 // set allstructures to all non-primary structure options in this linked
642 // id if --allstructures has been set
644 && (a.getType() == Type.STRUCTURE
645 || a.getType() == Type.STRUCTUREIMAGE)
646 && !a.hasOption(Opt.PRIMARY))
648 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
649 // && !subvals.has("structureid"))
651 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
652 addNewSubVals = true;
656 ArgValues avs = avm.getOrCreateArgValues(a);
658 // store appropriate String value(s)
659 if (a.hasOption(Opt.STRING))
661 if (a.hasOption(Opt.GLOB) && globVals != null
662 && globVals.size() > 0)
664 Enumeration<String> gve = Collections.enumeration(globVals);
665 while (gve.hasMoreElements())
667 String v = gve.nextElement();
668 SubVals vsv = new SubVals(globSubVals, v);
669 addValue(linkedId, type, avs, vsv, v, argIndex++, true);
670 // if we're using defaultLinkedId and the arg increments the
672 if (gve.hasMoreElements() && usingDefaultLinkedId
673 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
675 // increment the default linkedId
676 linkedId = defaultLinkedId(true);
677 // get new avm and avs
678 avm = linkedArgs.get(linkedId);
679 avs = avm.getOrCreateArgValues(a);
685 // addValue(linkedId, type, avs, val, argIndex, true);
686 addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
687 val, argIndex, true);
690 else if (a.hasOption(Opt.BOOLEAN))
692 setBoolean(linkedId, type, avs, !negated, argIndex);
693 setNegated(linkedId, avs, negated);
695 else if (a.hasOption(Opt.UNARY))
697 setBoolean(linkedId, type, avs, true, argIndex);
700 // remove the '*' or 'open*' linkedId that should be empty if it was
702 if ((MATCHALLLINKEDIDS.equals(linkedId)
703 && linkedArgs.containsKey(linkedId))
704 || (MATCHOPENEDLINKEDIDS.equals(linkedId)
705 && linkedArgs.containsKey(linkedId)))
707 linkedArgs.remove(linkedId);
713 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
716 incrementCount(linkedId, avs);
719 // store in appropriate place
720 if (a.hasOption(Opt.LINKED))
722 // store the order of linkedIds
723 if (!linkedOrder.contains(linkedId))
724 linkedOrder.add(linkedId);
727 // store arg in the list of args used
728 if (!argList.contains(a))
732 private String defaultLinkedId(boolean increment)
734 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
735 .append(Integer.toString(defaultLinkedIdCounter)).toString();
738 while (linkedArgs.containsKey(defaultLinkedId))
740 defaultLinkedIdCounter++;
741 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
742 .append(Integer.toString(defaultLinkedIdCounter))
746 getOrCreateLinkedArgValuesMap(defaultLinkedId);
747 return defaultLinkedId;
750 public String makeSubstitutions(String val, String linkedId)
752 if (!this.substitutions || val == null)
757 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
759 int closeBracket = val.indexOf(']');
760 if (val.length() == closeBracket)
762 subvals = val.substring(0, closeBracket + 1);
763 rest = val.substring(closeBracket + 1);
770 if (rest.contains(LINKEDIDAUTOCOUNTER))
771 rest = rest.replace(LINKEDIDAUTOCOUNTER,
772 String.valueOf(linkedIdAutoCounter));
773 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
774 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
775 String.valueOf(++linkedIdAutoCounter));
776 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
777 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
778 String.valueOf(defaultLinkedIdCounter));
779 ArgValuesMap avm = linkedArgs.get(linkedId);
782 if (rest.contains(LINKEDIDBASENAME))
784 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
786 if (rest.contains(LINKEDIDEXTENSION))
788 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
790 if (rest.contains(LINKEDIDDIRNAME))
792 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
797 if (rest.contains(ARGFILEBASENAME))
799 rest = rest.replace(ARGFILEBASENAME,
800 FileUtils.getBasename(new File(argFile)));
802 if (rest.contains(ARGFILEDIRNAME))
804 rest = rest.replace(ARGFILEDIRNAME,
805 FileUtils.getDirname(new File(argFile)));
809 return new StringBuilder(subvals).append(rest).toString();
813 * A helper method to take a list of String args where we're expecting
814 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
815 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
816 * "file2", "file3"} *and remove these from the original list object* so that
817 * processing can continue from where it has left off, e.g. args has become
818 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
819 * carries on from the next --arg if available.
821 protected static List<String> getShellGlobbedFilenameValues(Arg a,
822 List<String> args, int i)
824 List<String> vals = new ArrayList<>();
825 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
827 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
828 if (!a.hasOption(Opt.GLOB))
834 public BootstrapArgs getBootstrapArgs()
836 return bootstrapArgs;
839 public boolean isSet(Arg a)
841 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
844 public boolean isSetAtAll(Arg a)
846 for (String linkedId : linkedOrder)
848 if (isSet(linkedId, a))
854 public boolean isSet(String linkedId, Arg a)
856 ArgValuesMap avm = linkedArgs.get(linkedId);
857 return avm == null ? false : avm.containsArg(a);
860 public boolean getBoolean(Arg a)
862 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
864 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
867 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
870 public boolean getBool(String linkedId, Arg a)
872 ArgValuesMap avm = linkedArgs.get(linkedId);
874 return a.getDefaultBoolValue();
875 ArgValues avs = avm.getArgValues(a);
876 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
879 public List<String> getLinkedIds()
884 public ArgValuesMap getLinkedArgs(String id)
886 return linkedArgs.get(id);
890 public String toString()
892 StringBuilder sb = new StringBuilder();
893 sb.append("UNLINKED\n");
894 sb.append(argValuesMapToString(linkedArgs.get(null)));
895 if (getLinkedIds() != null)
897 sb.append("LINKED\n");
898 for (String id : getLinkedIds())
900 // already listed these as UNLINKED args
904 ArgValuesMap avm = getLinkedArgs(id);
905 sb.append("ID: '").append(id).append("'\n");
906 sb.append(argValuesMapToString(avm));
909 return sb.toString();
912 private static String argValuesMapToString(ArgValuesMap avm)
916 StringBuilder sb = new StringBuilder();
917 for (Arg a : avm.getArgKeys())
919 ArgValues v = avm.getArgValues(a);
920 sb.append(v.toString());
923 return sb.toString();
926 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
927 boolean initsubstitutions, BootstrapArgs bsa)
929 List<File> argFiles = new ArrayList<>();
931 for (String pattern : argFilenameGlobs)
933 // I don't think we want to dedup files, making life easier
934 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
937 return parseArgFileList(argFiles, initsubstitutions, bsa);
940 public static ArgParser parseArgFileList(List<File> argFiles,
941 boolean initsubstitutions, BootstrapArgs bsa)
943 List<String> argsList = new ArrayList<>();
944 for (File argFile : argFiles)
946 if (!argFile.exists())
948 String message = Arg.ARGFILE.argString() + EQUALS + "\""
949 + argFile.getPath() + "\": File does not exist.";
950 Jalview.exit(message, ExitCode.FILE_NOT_FOUND);
954 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
955 .append(EQUALS).append(argFile.getCanonicalPath())
957 argsList.add(setargfile);
958 argsList.addAll(readArgFile(argFile));
959 argsList.add(Arg.UNSETARGFILE.argString());
960 } catch (IOException e)
962 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
963 + "\": File could not be read.";
964 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
967 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
969 return new ArgParser(argsList, initsubstitutions, true, bsa);
972 protected static List<String> readArgFile(File argFile)
974 List<String> args = new ArrayList<>();
975 if (argFile != null && argFile.exists())
979 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
981 if (line != null && line.length() > 0
982 && line.charAt(0) != ARGFILECOMMENT)
985 } catch (IOException e)
987 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
988 + "\": File could not be read.";
989 Console.debug(message, e);
990 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
996 public static enum Position
1002 * get from following Arg of type a or subval of same name (lowercase)
1004 public static String getValueFromSubValOrArg(ArgValuesMap avm,
1005 ArgValue av, Arg a, SubVals sv)
1007 return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
1011 * get from following Arg of type a or subval key or preference pref or
1014 public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
1015 Arg a, SubVals sv, String key, String pref, String def)
1017 return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref,
1022 * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
1023 * Arg of type a or subval key or preference pref or default def
1025 public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1026 Position pos, ArgValue av, SubVals sv, String key, String pref,
1029 return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
1030 sv, key, pref, def);
1033 public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
1034 ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
1035 String key, String pref, String def)
1039 String value = null;
1040 if (sv != null && sv.has(key) && sv.get(key) != null)
1042 value = ap == null ? sv.get(key)
1043 : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
1045 else if (avm != null && avm.containsArg(a))
1047 if (pos == Position.FIRST && avm.getValue(a) != null)
1048 value = avm.getValue(a);
1049 else if (pos == Position.BEFORE
1050 && avm.getClosestPreviousArgValueOfArg(av, a) != null)
1051 value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
1052 else if (pos == Position.AFTER
1053 && avm.getClosestNextArgValueOfArg(av, a) != null)
1054 value = avm.getClosestNextArgValueOfArg(av, a).getValue();
1056 // look for allstructures subval for Type.STRUCTURE*
1057 Arg arg = av.getArg();
1058 if (value == null && arg.hasOption(Opt.PRIMARY)
1059 && arg.getType() == Type.STRUCTURE
1060 && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
1061 || a.getType() == Type.STRUCTUREIMAGE))
1063 ArgValue av2 = avm.getArgValueOfArgWithSubValKey(a,
1064 Arg.ALLSTRUCTURES.getName());
1067 value = av2.getValue();
1073 value = pref != null ? Cache.getDefault(pref, def) : def;
1078 public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
1081 return getFromSubValArgOrPref(avm, a, sv, null, null, false);
1084 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1085 SubVals sv, String key, String pref, boolean def)
1087 return getFromSubValArgOrPref(avm, a, sv, key, pref, def, false);
1090 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1091 SubVals sv, String key, String pref, boolean def,
1094 if ((key == null && a == null) || (sv == null && a == null))
1097 boolean usingArgKey = false;
1104 String nokey = ArgParser.NEGATESTRING + key;
1106 // look for key or nokey in subvals first (if using Arg check options)
1109 // check for true boolean
1110 if (sv.has(key) && sv.get(key) != null)
1114 if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
1117 "Looking for boolean in subval from non-boolean/non-unary Arg "
1122 return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
1125 // check for negative boolean (subval "no..." will be "true")
1126 if (sv.has(nokey) && sv.get(nokey) != null)
1130 if (!(a.hasOption(Opt.BOOLEAN)))
1133 "Looking for negative boolean in subval from non-boolean Arg "
1138 return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
1143 if (avm != null && avm.containsArg(a))
1144 return avm.getBoolean(a);
1146 // return preference or default
1147 boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
1148 return pref != null ? (invertPref ? !prefVal : prefVal) : def;
1151 // the following methods look for the "*" linkedId and add the argvalue to all
1152 // linkedId ArgValues if it does.
1154 * This version inserts the subvals sv into all created values
1156 private void addValue(String linkedId, Type type, ArgValues avs,
1157 SubVals sv, String v, int argIndex, boolean doSubs)
1159 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
1163 private void addValue(String linkedId, Type type, ArgValues avs, String v,
1164 int argIndex, boolean doSubs)
1166 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, null, v, false,
1170 private void setBoolean(String linkedId, Type type, ArgValues avs,
1171 boolean b, int argIndex)
1173 this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
1174 b, argIndex, false);
1177 private void setNegated(String linkedId, ArgValues avs, boolean b)
1179 this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
1183 private void incrementCount(String linkedId, ArgValues avs)
1185 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
1186 null, false, 0, false);
1191 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1194 private void argValueOperation(Op op, String linkedId, Type type,
1195 ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
1198 // default to merge subvals if subvals are provided
1199 argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
1204 * The following operations look for the "*" and "open*" linkedIds and add the
1205 * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1206 * supplied, they are inserted into all new set values.
1209 * The ArgParser.Op operation
1211 * The String linkedId from the ArgValuesMap
1213 * The Arg.Type to attach to this ArgValue
1215 * The ArgValues for this linkedId
1217 * Use these SubVals on the ArgValue
1219 * Merge the SubVals with any existing on the value. False will
1220 * replace unless sv is null
1222 * The value of the ArgValue (may contain subvals).
1224 * The boolean value of the ArgValue.
1226 * The argIndex for the ArgValue.
1228 * Whether to perform substitutions on the subvals and value.
1230 private void argValueOperation(Op op, String linkedId, Type type,
1231 ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
1232 int argIndex, boolean doSubs)
1236 List<String> wildcardLinkedIds = null;
1237 if (a.hasOption(Opt.ALLOWALL))
1241 case MATCHALLLINKEDIDS:
1242 wildcardLinkedIds = getLinkedIds();
1244 case MATCHOPENEDLINKEDIDS:
1245 wildcardLinkedIds = this.storedLinkedIds;
1250 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1251 // to storedLinkedIds
1252 if (linkedId != null && wildcardLinkedIds == null
1253 && a.hasOption(Opt.STORED)
1254 && !storedLinkedIds.contains(linkedId))
1256 storedLinkedIds.add(linkedId);
1259 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1261 if (wildcardLinkedIds != null)
1263 for (String id : wildcardLinkedIds)
1265 // skip incorrectly stored wildcard ids!
1266 if (id == null || MATCHALLLINKEDIDS.equals(id)
1267 || MATCHOPENEDLINKEDIDS.equals(id))
1269 ArgValuesMap avm = linkedArgs.get(id);
1270 // don't set an output if there isn't an input
1271 if (a.hasOption(Opt.REQUIREINPUT)
1272 && !avm.hasArgWithOption(Opt.INPUT))
1275 ArgValues tavs = avm.getOrCreateArgValues(a);
1285 sv = new SubVals(sv, val, merge);
1286 val = makeSubstitutions(sv.getContent(), id);
1288 tavs.addValue(sv, type, val, argIndex, true);
1294 val = makeSubstitutions(v, id);
1296 tavs.addValue(type, val, argIndex, true);
1298 finaliseStoringArgValue(id, tavs);
1302 tavs.setBoolean(type, b, argIndex, true);
1303 finaliseStoringArgValue(id, tavs);
1307 tavs.setNegated(b, true);
1310 case INCREMENTCOUNT:
1311 tavs.incrementCount();
1321 else // no wildcard linkedId -- do it simpler
1331 val = makeSubstitutions(v, linkedId);
1332 sv = new SubVals(sv, val);
1334 avs.addValue(sv, type, val, argIndex, false);
1340 val = makeSubstitutions(v, linkedId);
1342 avs.addValue(type, val, argIndex, false);
1344 finaliseStoringArgValue(linkedId, avs);
1348 avs.setBoolean(type, b, argIndex, false);
1349 finaliseStoringArgValue(linkedId, avs);
1353 avs.setNegated(b, false);
1356 case INCREMENTCOUNT:
1357 avs.incrementCount();
1366 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1368 if (linkedArgs.containsKey(linkedId)
1369 && linkedArgs.get(linkedId) != null)
1370 return linkedArgs.get(linkedId);
1372 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1373 return linkedArgs.get(linkedId);
1376 public boolean isOldStyle()
1378 return oldArguments;
1381 public boolean isMixedStyle()
1383 return mixedArguments;
1386 public String[] getMixedExamples()
1388 return mixedExamples;