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 protected static final String NEGATESTRING = "no";
55 // the default linked id prefix used for no id (not even square braces)
56 protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
58 // the linkedId string used to match all linkedIds seen so far
59 protected static final String MATCHALLLINKEDIDS = "*";
61 // the linkedId string used to match all of the last --open'ed linkedIds
62 protected static final String MATCHOPENEDLINKEDIDS = "open*";
64 // the counter added to the default linked id prefix
65 private int defaultLinkedIdCounter = 0;
67 // the substitution string used to use the defaultLinkedIdCounter
68 private static final String DEFAULTLINKEDIDCOUNTER = "{}";
70 // the counter added to the default linked id prefix. NOW using
71 // linkedIdAutoCounter
72 // private int openLinkedIdCounter = 0;
74 // the linked id prefix used for --open files. NOW the same as DEFAULT
75 protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
77 // the counter used for {n} substitutions
78 private int linkedIdAutoCounter = 0;
80 // the linked id substitution string used to increment the idCounter (and use
81 // the incremented value)
82 private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
84 // the linked id substitution string used to use the idCounter
85 private static final String LINKEDIDAUTOCOUNTER = "{n}";
87 // the linked id substitution string used to use the filename extension of
90 private static final String LINKEDIDEXTENSION = "{extension}";
92 // the linked id substitution string used to use the base filename of --append
94 private static final String LINKEDIDBASENAME = "{basename}";
96 // the linked id substitution string used to use the dir path of --append
98 private static final String LINKEDIDDIRNAME = "{dirname}";
100 // the current argfile
101 private String argFile = null;
103 // the linked id substitution string used to use the dir path of the latest
105 private static final String ARGFILEBASENAME = "{argfilebasename}";
107 // the linked id substitution string used to use the dir path of the latest
109 private static final String ARGFILEDIRNAME = "{argfiledirname}";
111 // flag to say whether {n} subtitutions in output filenames should be made.
112 // Turn on and off with --substitutions and --nosubstitutions
114 private boolean substitutions = true;
116 // flag to say whether the default linkedId is the current default linked id
118 private boolean allLinkedIds = false;
120 // flag to say whether the default linkedId is the current default linked id
121 // or OPENED linkedIds
122 private boolean openedLinkedIds = false;
124 // flag to say whether the structure arguments should be applied to all
125 // structures with this linked id
126 private boolean allStructures = false;
128 protected static final Map<String, Arg> argMap;
130 protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
132 protected List<String> linkedOrder = new ArrayList<>();
134 protected List<String> storedLinkedIds = new ArrayList<>();
136 protected List<Arg> argList = new ArrayList<>();
138 private static final char ARGFILECOMMENT = '#';
140 private int argIndex = 0;
142 private BootstrapArgs bootstrapArgs = null;
146 argMap = new HashMap<>();
147 for (Arg a : EnumSet.allOf(Arg.class))
149 for (String argName : a.getNames())
151 if (argMap.containsKey(argName))
153 Console.warn("Trying to add argument name multiple times: '"
155 if (argMap.get(argName) != a)
158 "Trying to add argument name multiple times for different Args: '"
159 + argMap.get(argName).getName() + ":" + argName
160 + "' and '" + a.getName() + ":" + argName
165 argMap.put(argName, a);
170 public ArgParser(String[] args)
172 this(args, false, null);
175 public ArgParser(String[] args, boolean initsubstitutions,
178 // Make a mutable new ArrayList so that shell globbing parser works.
179 // (When shell file globbing is used, there are a sequence of non-Arg
180 // arguments (which are the expanded globbed filenames) that need to be
181 // consumed by the --append/--argfile/etc Arg which is most easily done by
182 // removing these filenames from the list one at a time. This can't be done
183 // with an ArrayList made with only Arrays.asList(String[] args). )
184 this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
188 public ArgParser(List<String> args, boolean initsubstitutions)
190 this(args, initsubstitutions, false, null);
193 public ArgParser(List<String> args, boolean initsubstitutions,
194 boolean allowPrivate, BootstrapArgs bsa)
196 // do nothing if there are no "--" args and (some "-" args || >0 arg is
200 for (String arg : args)
202 if (arg.startsWith(DOUBLEDASH))
207 else if (arg.startsWith("-") || arg.equals("open"))
214 // leave it to the old style -- parse an empty list
215 parse(new ArrayList<String>(), false, false);
219 this.bootstrapArgs = bsa;
221 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
222 parse(args, initsubstitutions, allowPrivate);
225 private void parse(List<String> args, boolean initsubstitutions,
226 boolean allowPrivate)
228 this.substitutions = initsubstitutions;
229 boolean openEachInitialFilenames = true;
230 for (int i = 0; i < args.size(); i++)
232 String arg = args.get(i);
234 // If the first arguments do not start with "--" or "-" or is not "open"
235 // and` is a filename that exists it is probably a file/list of files to
236 // open so we fake an Arg.OPEN argument and when adding files only add the
237 // single arg[i] and increment the defaultLinkedIdCounter so that each of
238 // these files is opened separately.
239 if (openEachInitialFilenames && !arg.startsWith(DOUBLEDASH)
240 && !arg.startsWith("-") && !arg.equals("open")
241 && (new File(arg).exists()
242 || HttpUtils.startsWithHttpOrHttps(arg)))
244 arg = Arg.OPEN.argString();
248 openEachInitialFilenames = false;
251 // look for double-dash, e.g. --arg
252 if (arg.startsWith(DOUBLEDASH))
254 String argName = null;
256 List<String> globVals = null; // for Opt.GLOB only
257 SubVals globSubVals = null; // also for use by Opt.GLOB only
258 String linkedId = null;
261 // look for equals e.g. --arg=value
262 int equalPos = arg.indexOf(EQUALS);
265 argName = arg.substring(DOUBLEDASH.length(), equalPos);
266 val = arg.substring(equalPos + 1);
270 argName = arg.substring(DOUBLEDASH.length());
273 // look for linked ID e.g. --arg[linkedID]
274 int idOpen = argName.indexOf('[');
275 int idClose = argName.indexOf(']');
276 if (idOpen > -1 && idClose == argName.length() - 1)
278 linkedId = argName.substring(idOpen + 1, idClose);
279 argName = argName.substring(0, idOpen);
282 // look for type modification e.g. --help-opening
283 int dashPos = argName.indexOf(SINGLEDASH);
286 String potentialArgName = argName.substring(0, dashPos);
287 Arg potentialArg = argMap.get(potentialArgName);
288 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
290 String typeName = argName.substring(dashPos + 1);
293 type = Type.valueOf(typeName);
294 } catch (IllegalArgumentException e)
298 argName = argName.substring(0, dashPos);
302 Arg a = argMap.get(argName);
303 // check for boolean prepended by "no" e.g. --nowrap
304 boolean negated = false;
307 if (argName.startsWith(NEGATESTRING) && argMap
308 .containsKey(argName.substring(NEGATESTRING.length())))
310 argName = argName.substring(NEGATESTRING.length());
311 a = argMap.get(argName);
316 // after all other args, look for Opt.PREFIXKEV args if still not
318 for (Arg potentialArg : EnumSet.allOf(Arg.class))
320 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
321 && argName.startsWith(potentialArg.getName())
324 val = argName.substring(potentialArg.getName().length())
326 argName = argName.substring(0,
327 potentialArg.getName().length());
335 // check for config errors
339 Console.error("Argument '" + arg + "' not recognised. Exiting.");
340 Jalview.exit("Invalid argument used." + System.lineSeparator()
341 + "Use" + System.lineSeparator() + "jalview "
342 + Arg.HELP.argString() + System.lineSeparator()
343 + "for a usage statement.", 13);
346 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
349 "Argument '" + a.argString() + "' is private. Ignoring.");
352 if (!a.hasOption(Opt.BOOLEAN) && negated)
354 // used "no" with a non-boolean option
355 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
356 + "' not a boolean option. Ignoring.");
359 if (!a.hasOption(Opt.STRING) && equalPos > -1)
361 // set --argname=value when arg does not accept values
362 Console.error("Argument '" + a.argString()
363 + "' does not expect a value (given as '" + arg
367 if (!a.hasOption(Opt.LINKED) && linkedId != null)
369 // set --argname[linkedId] when arg does not use linkedIds
370 Console.error("Argument '" + a.argString()
371 + "' does not expect a linked id (given as '" + arg
377 if (a.hasOption(Opt.STRING))
381 if (a.hasOption(Opt.GLOB))
383 // strip off and save the SubVals to be added individually later
384 globSubVals = new SubVals(val);
385 // make substitutions before looking for files
386 String fileGlob = makeSubstitutions(globSubVals.getContent(),
388 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
392 // val is already set -- will be saved in the ArgValue later in
398 // There is no "=" so value is next arg or args (possibly shell
400 if ((openEachInitialFilenames ? i : i + 1) >= args.size())
402 // no value to take for arg, which wants a value
403 Console.error("Argument '" + a.getName()
404 + "' requires a value, none given. Ignoring.");
407 // deal with bash globs here (--arg val* is expanded before reaching
408 // the JVM). Note that SubVals cannot be used in this case.
409 // If using the --arg=val then the glob is preserved and Java globs
410 // will be used later. SubVals can be used.
411 if (a.hasOption(Opt.GLOB))
413 // if this is the first argument with a file list at the start of
414 // the args we add filenames from index i instead of i+1
415 globVals = getShellGlobbedFilenameValues(a, args,
416 openEachInitialFilenames ? i : i + 1);
420 val = args.get(i + 1);
425 // make NOACTION adjustments
426 // default and auto counter increments
429 linkedIdAutoCounter++;
431 else if (a == Arg.SUBSTITUTIONS)
433 substitutions = !negated;
435 else if (a == Arg.SETARGFILE)
439 else if (a == Arg.UNSETARGFILE)
443 else if (a == Arg.ALL)
445 allLinkedIds = !negated;
446 openedLinkedIds = false;
448 else if (a == Arg.OPENED)
450 openedLinkedIds = !negated;
451 allLinkedIds = false;
453 else if (a == Arg.ALLSTRUCTURES)
455 allStructures = !negated;
458 if (a.hasOption(Opt.STORED))
460 // reset the lastOpenedLinkedIds list
461 this.storedLinkedIds = new ArrayList<>();
464 // this is probably only Arg.NEW and Arg.OPEN
465 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
467 // use the next default prefixed OPENLINKEDID
468 defaultLinkedId(true);
471 String autoCounterString = null;
472 String defaultLinkedId = defaultLinkedId(false);
473 boolean usingDefaultLinkedId = false;
474 if (a.hasOption(Opt.LINKED))
476 if (linkedId == null)
478 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWALL)
479 && val.startsWith(MATCHALLLINKEDIDS))
481 // --output=*.ext is shorthand for --all --output {basename}.ext
482 // (or --image=*.ext)
484 openedLinkedIds = false;
485 linkedId = MATCHALLLINKEDIDS;
486 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
487 + val.substring(MATCHALLLINKEDIDS.length());
489 else if (a.hasOption(Opt.OUTPUTFILE)
490 && a.hasOption(Opt.ALLOWALL)
491 && val.startsWith(MATCHOPENEDLINKEDIDS))
493 // --output=open*.ext is shorthand for --opened --output
495 // (or --image=open*.ext)
496 openedLinkedIds = true;
497 allLinkedIds = false;
498 linkedId = MATCHOPENEDLINKEDIDS;
499 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
500 + val.substring(MATCHOPENEDLINKEDIDS.length());
502 else if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
504 linkedId = MATCHALLLINKEDIDS;
506 else if (openedLinkedIds && a.hasOption(Opt.ALLOWALL))
508 linkedId = MATCHOPENEDLINKEDIDS;
512 // use default linkedId for linked arguments
513 linkedId = defaultLinkedId;
514 usingDefaultLinkedId = true;
515 Console.debug("Changing linkedId to '" + linkedId + "' from "
521 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
523 // turn {n} to the autoCounter
524 autoCounterString = Integer.toString(linkedIdAutoCounter);
525 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
527 Console.debug("Changing linkedId to '" + linkedId + "' from "
530 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
532 // turn {++n} to the incremented autoCounter
533 autoCounterString = Integer.toString(++linkedIdAutoCounter);
534 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
536 Console.debug("Changing linkedId to '" + linkedId + "' from "
542 // do not continue in this block for NOACTION args
543 if (a.hasOption(Opt.NOACTION))
546 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
548 // not dealing with both NODUPLICATEVALUES and GLOB
549 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
551 Console.error("Argument '" + a.argString()
552 + "' cannot contain a duplicate value ('" + val
553 + "'). Ignoring this and subsequent occurrences.");
557 // check for unique id
558 SubVals subvals = new SubVals(val);
559 boolean addNewSubVals = false;
560 String id = subvals.get(ArgValues.ID);
561 if (id != null && avm.hasId(a, id))
563 Console.error("Argument '" + a.argString()
564 + "' has a duplicate id ('" + id + "'). Ignoring.");
568 // set allstructures to all non-primary structure options in this linked
569 // id if --allstructures has been set
571 && (a.getType() == Type.STRUCTURE
572 || a.getType() == Type.STRUCTUREIMAGE)
573 && !a.hasOption(Opt.PRIMARY))
575 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
576 // && !subvals.has("structureid"))
578 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
579 addNewSubVals = true;
583 ArgValues avs = avm.getOrCreateArgValues(a);
585 // store appropriate String value(s)
586 if (a.hasOption(Opt.STRING))
588 if (a.hasOption(Opt.GLOB) && globVals != null
589 && globVals.size() > 0)
591 Enumeration<String> gve = Collections.enumeration(globVals);
592 while (gve.hasMoreElements())
594 String v = gve.nextElement();
595 SubVals vsv = new SubVals(globSubVals, v);
596 addValue(linkedId, type, avs, vsv, v, argIndex++, true);
597 // if we're using defaultLinkedId and the arg increments the
599 if (gve.hasMoreElements() && usingDefaultLinkedId
600 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
602 // increment the default linkedId
603 linkedId = defaultLinkedId(true);
604 // get new avm and avs
605 avm = linkedArgs.get(linkedId);
606 avs = avm.getOrCreateArgValues(a);
612 // addValue(linkedId, type, avs, val, argIndex, true);
613 addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
614 val, argIndex, true);
617 else if (a.hasOption(Opt.BOOLEAN))
619 setBoolean(linkedId, type, avs, !negated, argIndex);
620 setNegated(linkedId, avs, negated);
622 else if (a.hasOption(Opt.UNARY))
624 setBoolean(linkedId, type, avs, true, argIndex);
627 // remove the '*' or 'open*' linkedId that should be empty if it was
629 if ((MATCHALLLINKEDIDS.equals(linkedId)
630 && linkedArgs.containsKey(linkedId))
631 || (MATCHOPENEDLINKEDIDS.equals(linkedId)
632 && linkedArgs.containsKey(linkedId)))
634 linkedArgs.remove(linkedId);
640 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
643 incrementCount(linkedId, avs);
646 // store in appropriate place
647 if (a.hasOption(Opt.LINKED))
649 // store the order of linkedIds
650 if (!linkedOrder.contains(linkedId))
651 linkedOrder.add(linkedId);
654 // store arg in the list of args used
655 if (!argList.contains(a))
659 private String defaultLinkedId(boolean increment)
661 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
662 .append(Integer.toString(defaultLinkedIdCounter)).toString();
665 while (linkedArgs.containsKey(defaultLinkedId))
667 defaultLinkedIdCounter++;
668 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
669 .append(Integer.toString(defaultLinkedIdCounter))
673 getOrCreateLinkedArgValuesMap(defaultLinkedId);
674 return defaultLinkedId;
677 public String makeSubstitutions(String val, String linkedId)
679 if (!this.substitutions || val == null)
684 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
686 int closeBracket = val.indexOf(']');
687 if (val.length() == closeBracket)
689 subvals = val.substring(0, closeBracket + 1);
690 rest = val.substring(closeBracket + 1);
697 if (rest.contains(LINKEDIDAUTOCOUNTER))
698 rest = rest.replace(LINKEDIDAUTOCOUNTER,
699 String.valueOf(linkedIdAutoCounter));
700 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
701 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
702 String.valueOf(++linkedIdAutoCounter));
703 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
704 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
705 String.valueOf(defaultLinkedIdCounter));
706 ArgValuesMap avm = linkedArgs.get(linkedId);
709 if (rest.contains(LINKEDIDBASENAME))
711 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
713 if (rest.contains(LINKEDIDEXTENSION))
715 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
717 if (rest.contains(LINKEDIDDIRNAME))
719 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
724 if (rest.contains(ARGFILEBASENAME))
726 rest = rest.replace(ARGFILEBASENAME,
727 FileUtils.getBasename(new File(argFile)));
729 if (rest.contains(ARGFILEDIRNAME))
731 rest = rest.replace(ARGFILEDIRNAME,
732 FileUtils.getDirname(new File(argFile)));
736 return new StringBuilder(subvals).append(rest).toString();
740 * A helper method to take a list of String args where we're expecting
741 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
742 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
743 * "file2", "file3"} *and remove these from the original list object* so that
744 * processing can continue from where it has left off, e.g. args has become
745 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
746 * carries on from the next --arg if available.
748 protected static List<String> getShellGlobbedFilenameValues(Arg a,
749 List<String> args, int i)
751 List<String> vals = new ArrayList<>();
752 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
754 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
755 if (!a.hasOption(Opt.GLOB))
761 public BootstrapArgs getBootstrapArgs()
763 return bootstrapArgs;
766 public boolean isSet(Arg a)
768 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
771 public boolean isSetAtAll(Arg a)
773 for (String linkedId : linkedOrder)
775 if (isSet(linkedId, a))
781 public boolean isSet(String linkedId, Arg a)
783 ArgValuesMap avm = linkedArgs.get(linkedId);
784 return avm == null ? false : avm.containsArg(a);
787 public boolean getBoolean(Arg a)
789 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
791 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
794 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
797 public boolean getBool(String linkedId, Arg a)
799 ArgValuesMap avm = linkedArgs.get(linkedId);
801 return a.getDefaultBoolValue();
802 ArgValues avs = avm.getArgValues(a);
803 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
806 public List<String> getLinkedIds()
811 public ArgValuesMap getLinkedArgs(String id)
813 return linkedArgs.get(id);
817 public String toString()
819 StringBuilder sb = new StringBuilder();
820 sb.append("UNLINKED\n");
821 sb.append(argValuesMapToString(linkedArgs.get(null)));
822 if (getLinkedIds() != null)
824 sb.append("LINKED\n");
825 for (String id : getLinkedIds())
827 // already listed these as UNLINKED args
831 ArgValuesMap avm = getLinkedArgs(id);
832 sb.append("ID: '").append(id).append("'\n");
833 sb.append(argValuesMapToString(avm));
836 return sb.toString();
839 private static String argValuesMapToString(ArgValuesMap avm)
843 StringBuilder sb = new StringBuilder();
844 for (Arg a : avm.getArgKeys())
846 ArgValues v = avm.getArgValues(a);
847 sb.append(v.toString());
850 return sb.toString();
853 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
854 boolean initsubstitutions, BootstrapArgs bsa)
856 List<File> argFiles = new ArrayList<>();
858 for (String pattern : argFilenameGlobs)
860 // I don't think we want to dedup files, making life easier
861 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
864 return parseArgFileList(argFiles, initsubstitutions, bsa);
867 public static ArgParser parseArgFileList(List<File> argFiles,
868 boolean initsubstitutions, BootstrapArgs bsa)
870 List<String> argsList = new ArrayList<>();
871 for (File argFile : argFiles)
873 if (!argFile.exists())
875 String message = Arg.ARGFILE.argString() + EQUALS + "\""
876 + argFile.getPath() + "\": File does not exist.";
877 Jalview.exit(message, 2);
881 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
882 .append(EQUALS).append(argFile.getCanonicalPath())
884 argsList.add(setargfile);
885 argsList.addAll(readArgFile(argFile));
886 argsList.add(Arg.UNSETARGFILE.argString());
887 } catch (IOException e)
889 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
890 + "\": File could not be read.";
891 Jalview.exit(message, 3);
894 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
896 return new ArgParser(argsList, initsubstitutions, true, bsa);
899 protected static List<String> readArgFile(File argFile)
901 List<String> args = new ArrayList<>();
902 if (argFile != null && argFile.exists())
906 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
908 if (line != null && line.length() > 0
909 && line.charAt(0) != ARGFILECOMMENT)
912 } catch (IOException e)
914 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
915 + "\": File could not be read.";
916 Console.debug(message, e);
917 Jalview.exit(message, 3);
923 public static enum Position
928 // get from following Arg of type a or subval of same name (lowercase)
929 public static String getValueFromSubValOrArg(ArgValuesMap avm,
930 ArgValue av, Arg a, SubVals sv)
932 return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
935 // get from following Arg of type a or subval key or preference pref or
937 public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
938 Arg a, SubVals sv, String key, String pref, String def)
940 return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref,
944 // get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
945 // Arg of type a or subval key or preference pref or default def
946 public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
947 Position pos, ArgValue av, SubVals sv, String key, String pref,
950 return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
954 public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
955 ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
956 String key, String pref, String def)
961 if (sv != null && sv.has(key) && sv.get(key) != null)
963 value = ap == null ? sv.get(key)
964 : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
966 else if (avm != null && avm.containsArg(a))
968 if (pos == Position.FIRST && avm.getValue(a) != null)
969 value = avm.getValue(a);
970 else if (pos == Position.BEFORE
971 && avm.getClosestPreviousArgValueOfArg(av, a) != null)
972 value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
973 else if (pos == Position.AFTER
974 && avm.getClosestNextArgValueOfArg(av, a) != null)
975 value = avm.getClosestNextArgValueOfArg(av, a).getValue();
977 // look for allstructures subval for Type.STRUCTURE*
978 Arg arg = av.getArg();
979 if (value == null && arg.hasOption(Opt.PRIMARY)
980 && arg.getType() == Type.STRUCTURE
981 && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
982 || a.getType() == Type.STRUCTUREIMAGE))
984 ArgValue av2 = avm.getArgValueOfArgWithSubValKey(a,
985 Arg.ALLSTRUCTURES.getName());
988 value = av2.getValue();
994 value = pref != null ? Cache.getDefault(pref, def) : def;
999 public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
1002 return getFromSubValArgOrPref(avm, a, sv, null, null, false);
1005 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1006 SubVals sv, String key, String pref, boolean def)
1008 return getFromSubValArgOrPref(avm, a, sv, key, pref, def, false);
1011 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1012 SubVals sv, String key, String pref, boolean def,
1015 if ((key == null && a == null) || (sv == null && a == null))
1018 boolean usingArgKey = false;
1025 String nokey = ArgParser.NEGATESTRING + key;
1027 // look for key or nokey in subvals first (if using Arg check options)
1030 // check for true boolean
1031 if (sv.has(key) && sv.get(key) != null)
1035 if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
1038 "Looking for boolean in subval from non-boolean/non-unary Arg "
1043 return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
1046 // check for negative boolean (subval "no..." will be "true")
1047 if (sv.has(nokey) && sv.get(nokey) != null)
1051 if (!(a.hasOption(Opt.BOOLEAN)))
1054 "Looking for negative boolean in subval from non-boolean Arg "
1059 return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
1064 if (avm != null && avm.containsArg(a))
1065 return avm.getBoolean(a);
1067 // return preference or default
1068 boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
1069 return pref != null ? (invertPref ? !prefVal : prefVal) : def;
1072 // the following methods look for the "*" linkedId and add the argvalue to all
1073 // linkedId ArgValues if it does.
1074 // This version inserts the subvals sv into all created values
1075 private void addValue(String linkedId, Type type, ArgValues avs,
1076 SubVals sv, String v, int argIndex, boolean doSubs)
1078 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
1082 private void addValue(String linkedId, Type type, ArgValues avs, String v,
1083 int argIndex, boolean doSubs)
1085 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, null, v, false,
1089 private void setBoolean(String linkedId, Type type, ArgValues avs,
1090 boolean b, int argIndex)
1092 this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
1093 b, argIndex, false);
1096 private void setNegated(String linkedId, ArgValues avs, boolean b)
1098 this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
1102 private void incrementCount(String linkedId, ArgValues avs)
1104 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
1105 null, false, 0, false);
1110 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1113 private void argValueOperation(Op op, String linkedId, Type type,
1114 ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
1117 // default to merge subvals if subvals are provided
1118 argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
1123 * The following operations look for the "*" and "open*" linkedIds and add the
1124 * argvalue to all appropriate linkedId ArgValues if it does.
1125 * If subvals are supplied, they are inserted into all new set values.
1127 * @param op The ArgParser.Op operation
1128 * @param linkedId The String linkedId from the ArgValuesMap
1129 * @param type The Arg.Type to attach to this ArgValue
1130 * @param avs The ArgValues for this linkedId
1131 * @param sv Use these SubVals on the ArgValue
1132 * @param merge Merge the SubVals with any existing on the value. False will replace unless sv is null
1133 * @param v The value of the ArgValue (may contain subvals).
1134 * @param b The boolean value of the ArgValue.
1135 * @param argIndex The argIndex for the ArgValue.
1136 * @param doSubs Whether to perform substitutions on the subvals and value.
1138 private void argValueOperation(Op op, String linkedId, Type type,
1139 ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
1140 int argIndex, boolean doSubs)
1144 List<String> wildcardLinkedIds = null;
1145 if (a.hasOption(Opt.ALLOWALL))
1149 case MATCHALLLINKEDIDS:
1150 wildcardLinkedIds = getLinkedIds();
1152 case MATCHOPENEDLINKEDIDS:
1153 wildcardLinkedIds = this.storedLinkedIds;
1158 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1159 // to storedLinkedIds
1160 if (linkedId != null && wildcardLinkedIds == null
1161 && a.hasOption(Opt.STORED)
1162 && !storedLinkedIds.contains(linkedId))
1164 storedLinkedIds.add(linkedId);
1167 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1169 if (wildcardLinkedIds != null)
1171 for (String id : wildcardLinkedIds)
1173 // skip incorrectly stored wildcard ids!
1174 if (id == null || MATCHALLLINKEDIDS.equals(id)
1175 || MATCHOPENEDLINKEDIDS.equals(id))
1177 ArgValuesMap avm = linkedArgs.get(id);
1178 // don't set an output if there isn't an input
1179 if (a.hasOption(Opt.REQUIREINPUT)
1180 && !avm.hasArgWithOption(Opt.INPUT))
1183 ArgValues tavs = avm.getOrCreateArgValues(a);
1193 sv = new SubVals(sv, val, merge);
1194 val = makeSubstitutions(sv.getContent(), id);
1196 tavs.addValue(sv, type, val, argIndex, true);
1202 val = makeSubstitutions(v, id);
1204 tavs.addValue(type, val, argIndex, true);
1206 finaliseStoringArgValue(id, tavs);
1210 tavs.setBoolean(type, b, argIndex, true);
1211 finaliseStoringArgValue(id, tavs);
1215 tavs.setNegated(b, true);
1218 case INCREMENTCOUNT:
1219 tavs.incrementCount();
1229 else // no wildcard linkedId -- do it simpler
1239 val = makeSubstitutions(v, linkedId);
1240 sv = new SubVals(sv, val);
1242 avs.addValue(sv, type, val, argIndex, false);
1248 val = makeSubstitutions(v, linkedId);
1250 avs.addValue(type, val, argIndex, false);
1252 finaliseStoringArgValue(linkedId, avs);
1256 avs.setBoolean(type, b, argIndex, false);
1257 finaliseStoringArgValue(linkedId, avs);
1261 avs.setNegated(b, false);
1264 case INCREMENTCOUNT:
1265 avs.incrementCount();
1274 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1276 if (linkedArgs.containsKey(linkedId)
1277 && linkedArgs.get(linkedId) != null)
1278 return linkedArgs.get(linkedId);
1280 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1281 return linkedArgs.get(linkedId);