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.argparser.Arg.Opt;
41 import jalview.bin.argparser.Arg.Type;
42 import jalview.util.FileUtils;
43 import jalview.util.HttpUtils;
45 public class ArgParser
47 protected static final String SINGLEDASH = "-";
49 protected static final String DOUBLEDASH = "--";
51 public static final char EQUALS = '=';
53 public static final String STDOUTFILENAME = "-";
55 protected static final String NEGATESTRING = "no";
58 * the default linked id prefix used for no id (ie when not even square braces
61 protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
64 * the linkedId string used to match all linkedIds seen so far
66 protected static final String MATCHALLLINKEDIDS = "*";
69 * the linkedId string used to match all of the last --open'ed linkedIds
71 protected static final String MATCHOPENEDLINKEDIDS = "open*";
74 * the counter added to the default linked id prefix
76 private int defaultLinkedIdCounter = 0;
79 * the substitution string used to use the defaultLinkedIdCounter
81 private static final String DEFAULTLINKEDIDCOUNTER = "{}";
84 * the linked id prefix used for --open files. NOW the same as DEFAULT
86 protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
89 * the counter used for {n} substitutions
91 private int linkedIdAutoCounter = 0;
94 * the linked id substitution string used to increment the idCounter (and use
95 * the incremented value)
97 private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
100 * the linked id substitution string used to use the idCounter
102 private static final String LINKEDIDAUTOCOUNTER = "{n}";
105 * the linked id substitution string used to use the filename extension of
108 private static final String LINKEDIDEXTENSION = "{extension}";
111 * the linked id substitution string used to use the base filename of --append
114 private static final String LINKEDIDBASENAME = "{basename}";
117 * the linked id substitution string used to use the dir path of --append or
120 private static final String LINKEDIDDIRNAME = "{dirname}";
123 * the current argfile
125 private String argFile = null;
128 * the linked id substitution string used to use the dir path of the latest
130 /** --argfile name */
131 private static final String ARGFILEBASENAME = "{argfilebasename}";
134 * the linked id substitution string used to use the dir path of the latest
137 private static final String ARGFILEDIRNAME = "{argfiledirname}";
140 * flag to say whether {n} subtitutions in output filenames should be made.
141 * Turn on and off with --substitutions and --nosubstitutions Start with it on
143 private boolean substitutions = true;
146 * flag to say whether the default linkedId is the current default linked id
150 private boolean allLinkedIds = false;
153 * flag to say whether the default linkedId is the current default linked id
154 * or OPENED linkedIds
156 private boolean openedLinkedIds = false;
159 * flag to say whether the structure arguments should be applied to all
160 * structures with this linked id
162 private boolean allStructures = false;
164 protected static final Map<String, Arg> argMap;
166 protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
168 protected List<String> linkedOrder = new ArrayList<>();
170 protected List<String> storedLinkedIds = new ArrayList<>();
172 protected List<Arg> argList = new ArrayList<>();
174 private static final char ARGFILECOMMENT = '#';
176 private int argIndex = 0;
178 private BootstrapArgs bootstrapArgs = null;
182 argMap = new HashMap<>();
183 for (Arg a : EnumSet.allOf(Arg.class))
185 for (String argName : a.getNames())
187 if (argMap.containsKey(argName))
189 Console.warn("Trying to add argument name multiple times: '"
191 if (argMap.get(argName) != a)
194 "Trying to add argument name multiple times for different Args: '"
195 + argMap.get(argName).getName() + ":" + argName
196 + "' and '" + a.getName() + ":" + argName
201 argMap.put(argName, a);
206 public ArgParser(String[] args)
208 this(args, false, null);
211 public ArgParser(String[] args, boolean initsubstitutions,
214 // Make a mutable new ArrayList so that shell globbing parser works.
215 // (When shell file globbing is used, there are a sequence of non-Arg
216 // arguments (which are the expanded globbed filenames) that need to be
217 // consumed by the --append/--argfile/etc Arg which is most easily done by
218 // removing these filenames from the list one at a time. This can't be done
219 // with an ArrayList made with only Arrays.asList(String[] args). )
220 this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
224 public ArgParser(List<String> args, boolean initsubstitutions)
226 this(args, initsubstitutions, false, null);
229 public ArgParser(List<String> args, boolean initsubstitutions,
230 boolean allowPrivate, BootstrapArgs bsa)
232 // do nothing if there are no "--" args and (some "-" args || >0 arg is
236 for (String arg : args)
238 if (arg.startsWith(DOUBLEDASH))
243 else if (arg.startsWith("-") || arg.equals("open"))
250 // leave it to the old style -- parse an empty list
251 parse(new ArrayList<String>(), false, false);
255 this.bootstrapArgs = bsa;
257 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
258 parse(args, initsubstitutions, allowPrivate);
261 private void parse(List<String> args, boolean initsubstitutions,
262 boolean allowPrivate)
264 this.substitutions = initsubstitutions;
265 boolean openEachInitialFilenames = true;
266 for (int i = 0; i < args.size(); i++)
268 String arg = args.get(i);
270 // If the first arguments do not start with "--" or "-" or is not "open"
271 // and` is a filename that exists it is probably a file/list of files to
272 // open so we fake an Arg.OPEN argument and when adding files only add the
273 // single arg[i] and increment the defaultLinkedIdCounter so that each of
274 // these files is opened separately.
275 if (openEachInitialFilenames && !arg.startsWith(DOUBLEDASH)
276 && !arg.startsWith("-") && !arg.equals("open")
277 && (new File(arg).exists()
278 || HttpUtils.startsWithHttpOrHttps(arg)))
280 arg = Arg.OPEN.argString();
284 openEachInitialFilenames = false;
287 // look for double-dash, e.g. --arg
288 if (arg.startsWith(DOUBLEDASH))
290 String argName = null;
292 List<String> globVals = null; // for Opt.GLOB only
293 SubVals globSubVals = null; // also for use by Opt.GLOB only
294 String linkedId = null;
297 // look for equals e.g. --arg=value
298 int equalPos = arg.indexOf(EQUALS);
301 argName = arg.substring(DOUBLEDASH.length(), equalPos);
302 val = arg.substring(equalPos + 1);
306 argName = arg.substring(DOUBLEDASH.length());
309 // look for linked ID e.g. --arg[linkedID]
310 int idOpen = argName.indexOf('[');
311 int idClose = argName.indexOf(']');
312 if (idOpen > -1 && idClose == argName.length() - 1)
314 linkedId = argName.substring(idOpen + 1, idClose);
315 argName = argName.substring(0, idOpen);
318 // look for type modification e.g. --help-opening
319 int dashPos = argName.indexOf(SINGLEDASH);
322 String potentialArgName = argName.substring(0, dashPos);
323 Arg potentialArg = argMap.get(potentialArgName);
324 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
326 String typeName = argName.substring(dashPos + 1);
329 type = Type.valueOf(typeName);
330 } catch (IllegalArgumentException e)
334 argName = argName.substring(0, dashPos);
338 Arg a = argMap.get(argName);
339 // check for boolean prepended by "no" e.g. --nowrap
340 boolean negated = false;
343 if (argName.startsWith(NEGATESTRING) && argMap
344 .containsKey(argName.substring(NEGATESTRING.length())))
346 argName = argName.substring(NEGATESTRING.length());
347 a = argMap.get(argName);
352 // after all other args, look for Opt.PREFIXKEV args if still not
354 for (Arg potentialArg : EnumSet.allOf(Arg.class))
356 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
357 && argName.startsWith(potentialArg.getName())
360 val = argName.substring(potentialArg.getName().length())
362 argName = argName.substring(0,
363 potentialArg.getName().length());
371 // check for config errors
375 Console.error("Argument '" + arg + "' not recognised. Exiting.");
376 Jalview.exit("Invalid argument used." + System.lineSeparator()
377 + "Use" + System.lineSeparator() + "jalview "
378 + Arg.HELP.argString() + System.lineSeparator()
379 + "for a usage statement.", 13);
382 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
385 "Argument '" + a.argString() + "' is private. Ignoring.");
388 if (!a.hasOption(Opt.BOOLEAN) && negated)
390 // used "no" with a non-boolean option
391 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
392 + "' not a boolean option. Ignoring.");
395 if (!a.hasOption(Opt.STRING) && equalPos > -1)
397 // set --argname=value when arg does not accept values
398 Console.error("Argument '" + a.argString()
399 + "' does not expect a value (given as '" + arg
403 if (!a.hasOption(Opt.LINKED) && linkedId != null)
405 // set --argname[linkedId] when arg does not use linkedIds
406 Console.error("Argument '" + a.argString()
407 + "' does not expect a linked id (given as '" + arg
413 if (a.hasOption(Opt.STRING))
417 if (a.hasOption(Opt.GLOB))
419 // strip off and save the SubVals to be added individually later
420 globSubVals = new SubVals(val);
421 // make substitutions before looking for files
422 String fileGlob = makeSubstitutions(globSubVals.getContent(),
424 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
428 // val is already set -- will be saved in the ArgValue later in
434 // There is no "=" so value is next arg or args (possibly shell
436 if ((openEachInitialFilenames ? i : i + 1) >= args.size())
438 // no value to take for arg, which wants a value
439 Console.error("Argument '" + a.getName()
440 + "' requires a value, none given. Ignoring.");
443 // deal with bash globs here (--arg val* is expanded before reaching
444 // the JVM). Note that SubVals cannot be used in this case.
445 // If using the --arg=val then the glob is preserved and Java globs
446 // will be used later. SubVals can be used.
447 if (a.hasOption(Opt.GLOB))
449 // if this is the first argument with a file list at the start of
450 // the args we add filenames from index i instead of i+1
451 globVals = getShellGlobbedFilenameValues(a, args,
452 openEachInitialFilenames ? i : i + 1);
456 val = args.get(i + 1);
461 // make NOACTION adjustments
462 // default and auto counter increments
465 linkedIdAutoCounter++;
467 else if (a == Arg.SUBSTITUTIONS)
469 substitutions = !negated;
471 else if (a == Arg.SETARGFILE)
475 else if (a == Arg.UNSETARGFILE)
479 else if (a == Arg.ALL)
481 allLinkedIds = !negated;
482 openedLinkedIds = false;
484 else if (a == Arg.OPENED)
486 openedLinkedIds = !negated;
487 allLinkedIds = false;
489 else if (a == Arg.ALLSTRUCTURES)
491 allStructures = !negated;
494 if (a.hasOption(Opt.STORED))
496 // reset the lastOpenedLinkedIds list
497 this.storedLinkedIds = new ArrayList<>();
500 // this is probably only Arg.NEW and Arg.OPEN
501 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
503 // use the next default prefixed OPENLINKEDID
504 defaultLinkedId(true);
507 String autoCounterString = null;
508 String defaultLinkedId = defaultLinkedId(false);
509 boolean usingDefaultLinkedId = false;
510 if (a.hasOption(Opt.LINKED))
512 if (linkedId == null)
514 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWALL)
515 && val.startsWith(MATCHALLLINKEDIDS))
517 // --output=*.ext is shorthand for --all --output {basename}.ext
518 // (or --image=*.ext)
520 openedLinkedIds = false;
521 linkedId = MATCHALLLINKEDIDS;
522 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
523 + val.substring(MATCHALLLINKEDIDS.length());
525 else if (a.hasOption(Opt.OUTPUTFILE)
526 && a.hasOption(Opt.ALLOWALL)
527 && val.startsWith(MATCHOPENEDLINKEDIDS))
529 // --output=open*.ext is shorthand for --opened --output
531 // (or --image=open*.ext)
532 openedLinkedIds = true;
533 allLinkedIds = false;
534 linkedId = MATCHOPENEDLINKEDIDS;
535 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
536 + val.substring(MATCHOPENEDLINKEDIDS.length());
538 else if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
540 linkedId = MATCHALLLINKEDIDS;
542 else if (openedLinkedIds && a.hasOption(Opt.ALLOWALL))
544 linkedId = MATCHOPENEDLINKEDIDS;
548 // use default linkedId for linked arguments
549 linkedId = defaultLinkedId;
550 usingDefaultLinkedId = true;
551 Console.debug("Changing linkedId to '" + linkedId + "' from "
557 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
559 // turn {n} to the autoCounter
560 autoCounterString = Integer.toString(linkedIdAutoCounter);
561 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
563 Console.debug("Changing linkedId to '" + linkedId + "' from "
566 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
568 // turn {++n} to the incremented autoCounter
569 autoCounterString = Integer.toString(++linkedIdAutoCounter);
570 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
572 Console.debug("Changing linkedId to '" + linkedId + "' from "
578 // do not continue in this block for NOACTION args
579 if (a.hasOption(Opt.NOACTION))
582 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
584 // not dealing with both NODUPLICATEVALUES and GLOB
585 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
587 Console.error("Argument '" + a.argString()
588 + "' cannot contain a duplicate value ('" + val
589 + "'). Ignoring this and subsequent occurrences.");
593 // check for unique id
594 SubVals subvals = new SubVals(val);
595 boolean addNewSubVals = false;
596 String id = subvals.get(ArgValues.ID);
597 if (id != null && avm.hasId(a, id))
599 Console.error("Argument '" + a.argString()
600 + "' has a duplicate id ('" + id + "'). Ignoring.");
604 // set allstructures to all non-primary structure options in this linked
605 // id if --allstructures has been set
607 && (a.getType() == Type.STRUCTURE
608 || a.getType() == Type.STRUCTUREIMAGE)
609 && !a.hasOption(Opt.PRIMARY))
611 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
612 // && !subvals.has("structureid"))
614 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
615 addNewSubVals = true;
619 ArgValues avs = avm.getOrCreateArgValues(a);
621 // store appropriate String value(s)
622 if (a.hasOption(Opt.STRING))
624 if (a.hasOption(Opt.GLOB) && globVals != null
625 && globVals.size() > 0)
627 Enumeration<String> gve = Collections.enumeration(globVals);
628 while (gve.hasMoreElements())
630 String v = gve.nextElement();
631 SubVals vsv = new SubVals(globSubVals, v);
632 addValue(linkedId, type, avs, vsv, v, argIndex++, true);
633 // if we're using defaultLinkedId and the arg increments the
635 if (gve.hasMoreElements() && usingDefaultLinkedId
636 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
638 // increment the default linkedId
639 linkedId = defaultLinkedId(true);
640 // get new avm and avs
641 avm = linkedArgs.get(linkedId);
642 avs = avm.getOrCreateArgValues(a);
648 // addValue(linkedId, type, avs, val, argIndex, true);
649 addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
650 val, argIndex, true);
653 else if (a.hasOption(Opt.BOOLEAN))
655 setBoolean(linkedId, type, avs, !negated, argIndex);
656 setNegated(linkedId, avs, negated);
658 else if (a.hasOption(Opt.UNARY))
660 setBoolean(linkedId, type, avs, true, argIndex);
663 // remove the '*' or 'open*' linkedId that should be empty if it was
665 if ((MATCHALLLINKEDIDS.equals(linkedId)
666 && linkedArgs.containsKey(linkedId))
667 || (MATCHOPENEDLINKEDIDS.equals(linkedId)
668 && linkedArgs.containsKey(linkedId)))
670 linkedArgs.remove(linkedId);
676 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
679 incrementCount(linkedId, avs);
682 // store in appropriate place
683 if (a.hasOption(Opt.LINKED))
685 // store the order of linkedIds
686 if (!linkedOrder.contains(linkedId))
687 linkedOrder.add(linkedId);
690 // store arg in the list of args used
691 if (!argList.contains(a))
695 private String defaultLinkedId(boolean increment)
697 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
698 .append(Integer.toString(defaultLinkedIdCounter)).toString();
701 while (linkedArgs.containsKey(defaultLinkedId))
703 defaultLinkedIdCounter++;
704 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
705 .append(Integer.toString(defaultLinkedIdCounter))
709 getOrCreateLinkedArgValuesMap(defaultLinkedId);
710 return defaultLinkedId;
713 public String makeSubstitutions(String val, String linkedId)
715 if (!this.substitutions || val == null)
720 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
722 int closeBracket = val.indexOf(']');
723 if (val.length() == closeBracket)
725 subvals = val.substring(0, closeBracket + 1);
726 rest = val.substring(closeBracket + 1);
733 if (rest.contains(LINKEDIDAUTOCOUNTER))
734 rest = rest.replace(LINKEDIDAUTOCOUNTER,
735 String.valueOf(linkedIdAutoCounter));
736 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
737 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
738 String.valueOf(++linkedIdAutoCounter));
739 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
740 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
741 String.valueOf(defaultLinkedIdCounter));
742 ArgValuesMap avm = linkedArgs.get(linkedId);
745 if (rest.contains(LINKEDIDBASENAME))
747 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
749 if (rest.contains(LINKEDIDEXTENSION))
751 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
753 if (rest.contains(LINKEDIDDIRNAME))
755 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
760 if (rest.contains(ARGFILEBASENAME))
762 rest = rest.replace(ARGFILEBASENAME,
763 FileUtils.getBasename(new File(argFile)));
765 if (rest.contains(ARGFILEDIRNAME))
767 rest = rest.replace(ARGFILEDIRNAME,
768 FileUtils.getDirname(new File(argFile)));
772 return new StringBuilder(subvals).append(rest).toString();
776 * A helper method to take a list of String args where we're expecting
777 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
778 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
779 * "file2", "file3"} *and remove these from the original list object* so that
780 * processing can continue from where it has left off, e.g. args has become
781 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
782 * carries on from the next --arg if available.
784 protected static List<String> getShellGlobbedFilenameValues(Arg a,
785 List<String> args, int i)
787 List<String> vals = new ArrayList<>();
788 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
790 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
791 if (!a.hasOption(Opt.GLOB))
797 public BootstrapArgs getBootstrapArgs()
799 return bootstrapArgs;
802 public boolean isSet(Arg a)
804 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
807 public boolean isSetAtAll(Arg a)
809 for (String linkedId : linkedOrder)
811 if (isSet(linkedId, a))
817 public boolean isSet(String linkedId, Arg a)
819 ArgValuesMap avm = linkedArgs.get(linkedId);
820 return avm == null ? false : avm.containsArg(a);
823 public boolean getBoolean(Arg a)
825 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
827 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
830 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
833 public boolean getBool(String linkedId, Arg a)
835 ArgValuesMap avm = linkedArgs.get(linkedId);
837 return a.getDefaultBoolValue();
838 ArgValues avs = avm.getArgValues(a);
839 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
842 public List<String> getLinkedIds()
847 public ArgValuesMap getLinkedArgs(String id)
849 return linkedArgs.get(id);
853 public String toString()
855 StringBuilder sb = new StringBuilder();
856 sb.append("UNLINKED\n");
857 sb.append(argValuesMapToString(linkedArgs.get(null)));
858 if (getLinkedIds() != null)
860 sb.append("LINKED\n");
861 for (String id : getLinkedIds())
863 // already listed these as UNLINKED args
867 ArgValuesMap avm = getLinkedArgs(id);
868 sb.append("ID: '").append(id).append("'\n");
869 sb.append(argValuesMapToString(avm));
872 return sb.toString();
875 private static String argValuesMapToString(ArgValuesMap avm)
879 StringBuilder sb = new StringBuilder();
880 for (Arg a : avm.getArgKeys())
882 ArgValues v = avm.getArgValues(a);
883 sb.append(v.toString());
886 return sb.toString();
889 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
890 boolean initsubstitutions, BootstrapArgs bsa)
892 List<File> argFiles = new ArrayList<>();
894 for (String pattern : argFilenameGlobs)
896 // I don't think we want to dedup files, making life easier
897 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
900 return parseArgFileList(argFiles, initsubstitutions, bsa);
903 public static ArgParser parseArgFileList(List<File> argFiles,
904 boolean initsubstitutions, BootstrapArgs bsa)
906 List<String> argsList = new ArrayList<>();
907 for (File argFile : argFiles)
909 if (!argFile.exists())
911 String message = Arg.ARGFILE.argString() + EQUALS + "\""
912 + argFile.getPath() + "\": File does not exist.";
913 Jalview.exit(message, 2);
917 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
918 .append(EQUALS).append(argFile.getCanonicalPath())
920 argsList.add(setargfile);
921 argsList.addAll(readArgFile(argFile));
922 argsList.add(Arg.UNSETARGFILE.argString());
923 } catch (IOException e)
925 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
926 + "\": File could not be read.";
927 Jalview.exit(message, 3);
930 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
932 return new ArgParser(argsList, initsubstitutions, true, bsa);
935 protected static List<String> readArgFile(File argFile)
937 List<String> args = new ArrayList<>();
938 if (argFile != null && argFile.exists())
942 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
944 if (line != null && line.length() > 0
945 && line.charAt(0) != ARGFILECOMMENT)
948 } catch (IOException e)
950 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
951 + "\": File could not be read.";
952 Console.debug(message, e);
953 Jalview.exit(message, 3);
959 public static enum Position
965 * get from following Arg of type a or subval of same name (lowercase)
967 public static String getValueFromSubValOrArg(ArgValuesMap avm,
968 ArgValue av, Arg a, SubVals sv)
970 return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
974 * get from following Arg of type a or subval key or preference pref or
977 public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
978 Arg a, SubVals sv, String key, String pref, String def)
980 return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref,
985 * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
986 * Arg of type a or subval key or preference pref or default def
988 public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
989 Position pos, ArgValue av, SubVals sv, String key, String pref,
992 return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
996 public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
997 ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
998 String key, String pref, String def)
1002 String value = null;
1003 if (sv != null && sv.has(key) && sv.get(key) != null)
1005 value = ap == null ? sv.get(key)
1006 : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
1008 else if (avm != null && avm.containsArg(a))
1010 if (pos == Position.FIRST && avm.getValue(a) != null)
1011 value = avm.getValue(a);
1012 else if (pos == Position.BEFORE
1013 && avm.getClosestPreviousArgValueOfArg(av, a) != null)
1014 value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
1015 else if (pos == Position.AFTER
1016 && avm.getClosestNextArgValueOfArg(av, a) != null)
1017 value = avm.getClosestNextArgValueOfArg(av, a).getValue();
1019 // look for allstructures subval for Type.STRUCTURE*
1020 Arg arg = av.getArg();
1021 if (value == null && arg.hasOption(Opt.PRIMARY)
1022 && arg.getType() == Type.STRUCTURE
1023 && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
1024 || a.getType() == Type.STRUCTUREIMAGE))
1026 ArgValue av2 = avm.getArgValueOfArgWithSubValKey(a,
1027 Arg.ALLSTRUCTURES.getName());
1030 value = av2.getValue();
1036 value = pref != null ? Cache.getDefault(pref, def) : def;
1041 public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
1044 return getFromSubValArgOrPref(avm, a, sv, null, null, false);
1047 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1048 SubVals sv, String key, String pref, boolean def)
1050 return getFromSubValArgOrPref(avm, a, sv, key, pref, def, false);
1053 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1054 SubVals sv, String key, String pref, boolean def,
1057 if ((key == null && a == null) || (sv == null && a == null))
1060 boolean usingArgKey = false;
1067 String nokey = ArgParser.NEGATESTRING + key;
1069 // look for key or nokey in subvals first (if using Arg check options)
1072 // check for true boolean
1073 if (sv.has(key) && sv.get(key) != null)
1077 if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
1080 "Looking for boolean in subval from non-boolean/non-unary Arg "
1085 return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
1088 // check for negative boolean (subval "no..." will be "true")
1089 if (sv.has(nokey) && sv.get(nokey) != null)
1093 if (!(a.hasOption(Opt.BOOLEAN)))
1096 "Looking for negative boolean in subval from non-boolean Arg "
1101 return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
1106 if (avm != null && avm.containsArg(a))
1107 return avm.getBoolean(a);
1109 // return preference or default
1110 boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
1111 return pref != null ? (invertPref ? !prefVal : prefVal) : def;
1114 // the following methods look for the "*" linkedId and add the argvalue to all
1115 // linkedId ArgValues if it does.
1117 * This version inserts the subvals sv into all created values
1119 private void addValue(String linkedId, Type type, ArgValues avs,
1120 SubVals sv, String v, int argIndex, boolean doSubs)
1122 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
1126 private void addValue(String linkedId, Type type, ArgValues avs, String v,
1127 int argIndex, boolean doSubs)
1129 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, null, v, false,
1133 private void setBoolean(String linkedId, Type type, ArgValues avs,
1134 boolean b, int argIndex)
1136 this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
1137 b, argIndex, false);
1140 private void setNegated(String linkedId, ArgValues avs, boolean b)
1142 this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
1146 private void incrementCount(String linkedId, ArgValues avs)
1148 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
1149 null, false, 0, false);
1154 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1157 private void argValueOperation(Op op, String linkedId, Type type,
1158 ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
1161 // default to merge subvals if subvals are provided
1162 argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
1167 * The following operations look for the "*" and "open*" linkedIds and add the
1168 * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1169 * supplied, they are inserted into all new set values.
1172 * The ArgParser.Op operation
1174 * The String linkedId from the ArgValuesMap
1176 * The Arg.Type to attach to this ArgValue
1178 * The ArgValues for this linkedId
1180 * Use these SubVals on the ArgValue
1182 * Merge the SubVals with any existing on the value. False will
1183 * replace unless sv is null
1185 * The value of the ArgValue (may contain subvals).
1187 * The boolean value of the ArgValue.
1189 * The argIndex for the ArgValue.
1191 * Whether to perform substitutions on the subvals and value.
1193 private void argValueOperation(Op op, String linkedId, Type type,
1194 ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
1195 int argIndex, boolean doSubs)
1199 List<String> wildcardLinkedIds = null;
1200 if (a.hasOption(Opt.ALLOWALL))
1204 case MATCHALLLINKEDIDS:
1205 wildcardLinkedIds = getLinkedIds();
1207 case MATCHOPENEDLINKEDIDS:
1208 wildcardLinkedIds = this.storedLinkedIds;
1213 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1214 // to storedLinkedIds
1215 if (linkedId != null && wildcardLinkedIds == null
1216 && a.hasOption(Opt.STORED)
1217 && !storedLinkedIds.contains(linkedId))
1219 storedLinkedIds.add(linkedId);
1222 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1224 if (wildcardLinkedIds != null)
1226 for (String id : wildcardLinkedIds)
1228 // skip incorrectly stored wildcard ids!
1229 if (id == null || MATCHALLLINKEDIDS.equals(id)
1230 || MATCHOPENEDLINKEDIDS.equals(id))
1232 ArgValuesMap avm = linkedArgs.get(id);
1233 // don't set an output if there isn't an input
1234 if (a.hasOption(Opt.REQUIREINPUT)
1235 && !avm.hasArgWithOption(Opt.INPUT))
1238 ArgValues tavs = avm.getOrCreateArgValues(a);
1248 sv = new SubVals(sv, val, merge);
1249 val = makeSubstitutions(sv.getContent(), id);
1251 tavs.addValue(sv, type, val, argIndex, true);
1257 val = makeSubstitutions(v, id);
1259 tavs.addValue(type, val, argIndex, true);
1261 finaliseStoringArgValue(id, tavs);
1265 tavs.setBoolean(type, b, argIndex, true);
1266 finaliseStoringArgValue(id, tavs);
1270 tavs.setNegated(b, true);
1273 case INCREMENTCOUNT:
1274 tavs.incrementCount();
1284 else // no wildcard linkedId -- do it simpler
1294 val = makeSubstitutions(v, linkedId);
1295 sv = new SubVals(sv, val);
1297 avs.addValue(sv, type, val, argIndex, false);
1303 val = makeSubstitutions(v, linkedId);
1305 avs.addValue(type, val, argIndex, false);
1307 finaliseStoringArgValue(linkedId, avs);
1311 avs.setBoolean(type, b, argIndex, false);
1312 finaliseStoringArgValue(linkedId, avs);
1316 avs.setNegated(b, false);
1319 case INCREMENTCOUNT:
1320 avs.incrementCount();
1329 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1331 if (linkedArgs.containsKey(linkedId)
1332 && linkedArgs.get(linkedId) != null)
1333 return linkedArgs.get(linkedId);
1335 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1336 return linkedArgs.get(linkedId);