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";
56 * the default linked id prefix used for no id (ie when not even square braces
59 protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
62 * the linkedId string used to match all linkedIds seen so far
64 protected static final String MATCHALLLINKEDIDS = "*";
67 * the linkedId string used to match all of the last --open'ed linkedIds
69 protected static final String MATCHOPENEDLINKEDIDS = "open*";
72 * the counter added to the default linked id prefix
74 private int defaultLinkedIdCounter = 0;
77 * the substitution string used to use the defaultLinkedIdCounter
79 private static final String DEFAULTLINKEDIDCOUNTER = "{}";
82 * the linked id prefix used for --open files. NOW the same as DEFAULT
84 protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
87 * the counter used for {n} substitutions
89 private int linkedIdAutoCounter = 0;
92 * the linked id substitution string used to increment the idCounter (and use
93 * the incremented value)
95 private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
98 * the linked id substitution string used to use the idCounter
100 private static final String LINKEDIDAUTOCOUNTER = "{n}";
103 * the linked id substitution string used to use the filename extension of
106 private static final String LINKEDIDEXTENSION = "{extension}";
109 * the linked id substitution string used to use the base filename of --append
112 private static final String LINKEDIDBASENAME = "{basename}";
115 * the linked id substitution string used to use the dir path of --append or
118 private static final String LINKEDIDDIRNAME = "{dirname}";
121 * the current argfile
123 private String argFile = null;
126 * the linked id substitution string used to use the dir path of the latest
128 /** --argfile name */
129 private static final String ARGFILEBASENAME = "{argfilebasename}";
132 * the linked id substitution string used to use the dir path of the latest
135 private static final String ARGFILEDIRNAME = "{argfiledirname}";
138 * flag to say whether {n} subtitutions in output filenames should be made.
139 * Turn on and off with --substitutions and --nosubstitutions Start with it on
141 private boolean substitutions = true;
144 * flag to say whether the default linkedId is the current default linked id
148 private boolean allLinkedIds = false;
151 * flag to say whether the default linkedId is the current default linked id
152 * or OPENED linkedIds
154 private boolean openedLinkedIds = false;
157 * flag to say whether the structure arguments should be applied to all
158 * structures with this linked id
160 private boolean allStructures = false;
162 protected static final Map<String, Arg> argMap;
164 protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
166 protected List<String> linkedOrder = new ArrayList<>();
168 protected List<String> storedLinkedIds = new ArrayList<>();
170 protected List<Arg> argList = new ArrayList<>();
172 private static final char ARGFILECOMMENT = '#';
174 private int argIndex = 0;
176 private BootstrapArgs bootstrapArgs = null;
180 argMap = new HashMap<>();
181 for (Arg a : EnumSet.allOf(Arg.class))
183 for (String argName : a.getNames())
185 if (argMap.containsKey(argName))
187 Console.warn("Trying to add argument name multiple times: '"
189 if (argMap.get(argName) != a)
192 "Trying to add argument name multiple times for different Args: '"
193 + argMap.get(argName).getName() + ":" + argName
194 + "' and '" + a.getName() + ":" + argName
199 argMap.put(argName, a);
204 public ArgParser(String[] args)
206 this(args, false, null);
209 public ArgParser(String[] args, boolean initsubstitutions,
212 // Make a mutable new ArrayList so that shell globbing parser works.
213 // (When shell file globbing is used, there are a sequence of non-Arg
214 // arguments (which are the expanded globbed filenames) that need to be
215 // consumed by the --append/--argfile/etc Arg which is most easily done by
216 // removing these filenames from the list one at a time. This can't be done
217 // with an ArrayList made with only Arrays.asList(String[] args). )
218 this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
222 public ArgParser(List<String> args, boolean initsubstitutions)
224 this(args, initsubstitutions, false, null);
227 public ArgParser(List<String> args, boolean initsubstitutions,
228 boolean allowPrivate, BootstrapArgs bsa)
230 // do nothing if there are no "--" args and (some "-" args || >0 arg is
234 for (String arg : args)
236 if (arg.startsWith(DOUBLEDASH))
241 else if (arg.startsWith("-") || arg.equals("open"))
248 // leave it to the old style -- parse an empty list
249 parse(new ArrayList<String>(), false, false);
253 this.bootstrapArgs = bsa;
255 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
256 parse(args, initsubstitutions, allowPrivate);
259 private void parse(List<String> args, boolean initsubstitutions,
260 boolean allowPrivate)
262 this.substitutions = initsubstitutions;
263 boolean openEachInitialFilenames = true;
264 for (int i = 0; i < args.size(); i++)
266 String arg = args.get(i);
268 // If the first arguments do not start with "--" or "-" or is not "open"
269 // and` is a filename that exists it is probably a file/list of files to
270 // open so we fake an Arg.OPEN argument and when adding files only add the
271 // single arg[i] and increment the defaultLinkedIdCounter so that each of
272 // these files is opened separately.
273 if (openEachInitialFilenames && !arg.startsWith(DOUBLEDASH)
274 && !arg.startsWith("-") && !arg.equals("open")
275 && (new File(arg).exists()
276 || HttpUtils.startsWithHttpOrHttps(arg)))
278 arg = Arg.OPEN.argString();
282 openEachInitialFilenames = false;
285 // look for double-dash, e.g. --arg
286 if (arg.startsWith(DOUBLEDASH))
288 String argName = null;
290 List<String> globVals = null; // for Opt.GLOB only
291 SubVals globSubVals = null; // also for use by Opt.GLOB only
292 String linkedId = null;
295 // look for equals e.g. --arg=value
296 int equalPos = arg.indexOf(EQUALS);
299 argName = arg.substring(DOUBLEDASH.length(), equalPos);
300 val = arg.substring(equalPos + 1);
304 argName = arg.substring(DOUBLEDASH.length());
307 // look for linked ID e.g. --arg[linkedID]
308 int idOpen = argName.indexOf('[');
309 int idClose = argName.indexOf(']');
310 if (idOpen > -1 && idClose == argName.length() - 1)
312 linkedId = argName.substring(idOpen + 1, idClose);
313 argName = argName.substring(0, idOpen);
316 // look for type modification e.g. --help-opening
317 int dashPos = argName.indexOf(SINGLEDASH);
320 String potentialArgName = argName.substring(0, dashPos);
321 Arg potentialArg = argMap.get(potentialArgName);
322 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
324 String typeName = argName.substring(dashPos + 1);
327 type = Type.valueOf(typeName);
328 } catch (IllegalArgumentException e)
332 argName = argName.substring(0, dashPos);
336 Arg a = argMap.get(argName);
337 // check for boolean prepended by "no" e.g. --nowrap
338 boolean negated = false;
341 if (argName.startsWith(NEGATESTRING) && argMap
342 .containsKey(argName.substring(NEGATESTRING.length())))
344 argName = argName.substring(NEGATESTRING.length());
345 a = argMap.get(argName);
350 // after all other args, look for Opt.PREFIXKEV args if still not
352 for (Arg potentialArg : EnumSet.allOf(Arg.class))
354 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
355 && argName.startsWith(potentialArg.getName())
358 val = argName.substring(potentialArg.getName().length())
360 argName = argName.substring(0,
361 potentialArg.getName().length());
369 // check for config errors
373 Console.error("Argument '" + arg + "' not recognised. Exiting.");
374 Jalview.exit("Invalid argument used." + System.lineSeparator()
375 + "Use" + System.lineSeparator() + "jalview "
376 + Arg.HELP.argString() + System.lineSeparator()
377 + "for a usage statement.", 13);
380 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
383 "Argument '" + a.argString() + "' is private. Ignoring.");
386 if (!a.hasOption(Opt.BOOLEAN) && negated)
388 // used "no" with a non-boolean option
389 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
390 + "' not a boolean option. Ignoring.");
393 if (!a.hasOption(Opt.STRING) && equalPos > -1)
395 // set --argname=value when arg does not accept values
396 Console.error("Argument '" + a.argString()
397 + "' does not expect a value (given as '" + arg
401 if (!a.hasOption(Opt.LINKED) && linkedId != null)
403 // set --argname[linkedId] when arg does not use linkedIds
404 Console.error("Argument '" + a.argString()
405 + "' does not expect a linked id (given as '" + arg
411 if (a.hasOption(Opt.STRING))
415 if (a.hasOption(Opt.GLOB))
417 // strip off and save the SubVals to be added individually later
418 globSubVals = new SubVals(val);
419 // make substitutions before looking for files
420 String fileGlob = makeSubstitutions(globSubVals.getContent(),
422 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
426 // val is already set -- will be saved in the ArgValue later in
432 // There is no "=" so value is next arg or args (possibly shell
434 if ((openEachInitialFilenames ? i : i + 1) >= args.size())
436 // no value to take for arg, which wants a value
437 Console.error("Argument '" + a.getName()
438 + "' requires a value, none given. Ignoring.");
441 // deal with bash globs here (--arg val* is expanded before reaching
442 // the JVM). Note that SubVals cannot be used in this case.
443 // If using the --arg=val then the glob is preserved and Java globs
444 // will be used later. SubVals can be used.
445 if (a.hasOption(Opt.GLOB))
447 // if this is the first argument with a file list at the start of
448 // the args we add filenames from index i instead of i+1
449 globVals = getShellGlobbedFilenameValues(a, args,
450 openEachInitialFilenames ? i : i + 1);
454 val = args.get(i + 1);
459 // make NOACTION adjustments
460 // default and auto counter increments
463 linkedIdAutoCounter++;
465 else if (a == Arg.SUBSTITUTIONS)
467 substitutions = !negated;
469 else if (a == Arg.SETARGFILE)
473 else if (a == Arg.UNSETARGFILE)
477 else if (a == Arg.ALL)
479 allLinkedIds = !negated;
480 openedLinkedIds = false;
482 else if (a == Arg.OPENED)
484 openedLinkedIds = !negated;
485 allLinkedIds = false;
487 else if (a == Arg.ALLSTRUCTURES)
489 allStructures = !negated;
492 if (a.hasOption(Opt.STORED))
494 // reset the lastOpenedLinkedIds list
495 this.storedLinkedIds = new ArrayList<>();
498 // this is probably only Arg.NEW and Arg.OPEN
499 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
501 // use the next default prefixed OPENLINKEDID
502 defaultLinkedId(true);
505 String autoCounterString = null;
506 String defaultLinkedId = defaultLinkedId(false);
507 boolean usingDefaultLinkedId = false;
508 if (a.hasOption(Opt.LINKED))
510 if (linkedId == null)
512 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWALL)
513 && val.startsWith(MATCHALLLINKEDIDS))
515 // --output=*.ext is shorthand for --all --output {basename}.ext
516 // (or --image=*.ext)
518 openedLinkedIds = false;
519 linkedId = MATCHALLLINKEDIDS;
520 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
521 + val.substring(MATCHALLLINKEDIDS.length());
523 else if (a.hasOption(Opt.OUTPUTFILE)
524 && a.hasOption(Opt.ALLOWALL)
525 && val.startsWith(MATCHOPENEDLINKEDIDS))
527 // --output=open*.ext is shorthand for --opened --output
529 // (or --image=open*.ext)
530 openedLinkedIds = true;
531 allLinkedIds = false;
532 linkedId = MATCHOPENEDLINKEDIDS;
533 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
534 + val.substring(MATCHOPENEDLINKEDIDS.length());
536 else if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
538 linkedId = MATCHALLLINKEDIDS;
540 else if (openedLinkedIds && a.hasOption(Opt.ALLOWALL))
542 linkedId = MATCHOPENEDLINKEDIDS;
546 // use default linkedId for linked arguments
547 linkedId = defaultLinkedId;
548 usingDefaultLinkedId = true;
549 Console.debug("Changing linkedId to '" + linkedId + "' from "
555 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
557 // turn {n} to the autoCounter
558 autoCounterString = Integer.toString(linkedIdAutoCounter);
559 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
561 Console.debug("Changing linkedId to '" + linkedId + "' from "
564 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
566 // turn {++n} to the incremented autoCounter
567 autoCounterString = Integer.toString(++linkedIdAutoCounter);
568 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
570 Console.debug("Changing linkedId to '" + linkedId + "' from "
576 // do not continue in this block for NOACTION args
577 if (a.hasOption(Opt.NOACTION))
580 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
582 // not dealing with both NODUPLICATEVALUES and GLOB
583 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
585 Console.error("Argument '" + a.argString()
586 + "' cannot contain a duplicate value ('" + val
587 + "'). Ignoring this and subsequent occurrences.");
591 // check for unique id
592 SubVals subvals = new SubVals(val);
593 boolean addNewSubVals = false;
594 String id = subvals.get(ArgValues.ID);
595 if (id != null && avm.hasId(a, id))
597 Console.error("Argument '" + a.argString()
598 + "' has a duplicate id ('" + id + "'). Ignoring.");
602 // set allstructures to all non-primary structure options in this linked
603 // id if --allstructures has been set
605 && (a.getType() == Type.STRUCTURE
606 || a.getType() == Type.STRUCTUREIMAGE)
607 && !a.hasOption(Opt.PRIMARY))
609 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
610 // && !subvals.has("structureid"))
612 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
613 addNewSubVals = true;
617 ArgValues avs = avm.getOrCreateArgValues(a);
619 // store appropriate String value(s)
620 if (a.hasOption(Opt.STRING))
622 if (a.hasOption(Opt.GLOB) && globVals != null
623 && globVals.size() > 0)
625 Enumeration<String> gve = Collections.enumeration(globVals);
626 while (gve.hasMoreElements())
628 String v = gve.nextElement();
629 SubVals vsv = new SubVals(globSubVals, v);
630 addValue(linkedId, type, avs, vsv, v, argIndex++, true);
631 // if we're using defaultLinkedId and the arg increments the
633 if (gve.hasMoreElements() && usingDefaultLinkedId
634 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
636 // increment the default linkedId
637 linkedId = defaultLinkedId(true);
638 // get new avm and avs
639 avm = linkedArgs.get(linkedId);
640 avs = avm.getOrCreateArgValues(a);
646 // addValue(linkedId, type, avs, val, argIndex, true);
647 addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
648 val, argIndex, true);
651 else if (a.hasOption(Opt.BOOLEAN))
653 setBoolean(linkedId, type, avs, !negated, argIndex);
654 setNegated(linkedId, avs, negated);
656 else if (a.hasOption(Opt.UNARY))
658 setBoolean(linkedId, type, avs, true, argIndex);
661 // remove the '*' or 'open*' linkedId that should be empty if it was
663 if ((MATCHALLLINKEDIDS.equals(linkedId)
664 && linkedArgs.containsKey(linkedId))
665 || (MATCHOPENEDLINKEDIDS.equals(linkedId)
666 && linkedArgs.containsKey(linkedId)))
668 linkedArgs.remove(linkedId);
674 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
677 incrementCount(linkedId, avs);
680 // store in appropriate place
681 if (a.hasOption(Opt.LINKED))
683 // store the order of linkedIds
684 if (!linkedOrder.contains(linkedId))
685 linkedOrder.add(linkedId);
688 // store arg in the list of args used
689 if (!argList.contains(a))
693 private String defaultLinkedId(boolean increment)
695 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
696 .append(Integer.toString(defaultLinkedIdCounter)).toString();
699 while (linkedArgs.containsKey(defaultLinkedId))
701 defaultLinkedIdCounter++;
702 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
703 .append(Integer.toString(defaultLinkedIdCounter))
707 getOrCreateLinkedArgValuesMap(defaultLinkedId);
708 return defaultLinkedId;
711 public String makeSubstitutions(String val, String linkedId)
713 if (!this.substitutions || val == null)
718 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
720 int closeBracket = val.indexOf(']');
721 if (val.length() == closeBracket)
723 subvals = val.substring(0, closeBracket + 1);
724 rest = val.substring(closeBracket + 1);
731 if (rest.contains(LINKEDIDAUTOCOUNTER))
732 rest = rest.replace(LINKEDIDAUTOCOUNTER,
733 String.valueOf(linkedIdAutoCounter));
734 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
735 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
736 String.valueOf(++linkedIdAutoCounter));
737 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
738 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
739 String.valueOf(defaultLinkedIdCounter));
740 ArgValuesMap avm = linkedArgs.get(linkedId);
743 if (rest.contains(LINKEDIDBASENAME))
745 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
747 if (rest.contains(LINKEDIDEXTENSION))
749 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
751 if (rest.contains(LINKEDIDDIRNAME))
753 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
758 if (rest.contains(ARGFILEBASENAME))
760 rest = rest.replace(ARGFILEBASENAME,
761 FileUtils.getBasename(new File(argFile)));
763 if (rest.contains(ARGFILEDIRNAME))
765 rest = rest.replace(ARGFILEDIRNAME,
766 FileUtils.getDirname(new File(argFile)));
770 return new StringBuilder(subvals).append(rest).toString();
774 * A helper method to take a list of String args where we're expecting
775 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
776 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
777 * "file2", "file3"} *and remove these from the original list object* so that
778 * processing can continue from where it has left off, e.g. args has become
779 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
780 * carries on from the next --arg if available.
782 protected static List<String> getShellGlobbedFilenameValues(Arg a,
783 List<String> args, int i)
785 List<String> vals = new ArrayList<>();
786 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
788 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
789 if (!a.hasOption(Opt.GLOB))
795 public BootstrapArgs getBootstrapArgs()
797 return bootstrapArgs;
800 public boolean isSet(Arg a)
802 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
805 public boolean isSetAtAll(Arg a)
807 for (String linkedId : linkedOrder)
809 if (isSet(linkedId, a))
815 public boolean isSet(String linkedId, Arg a)
817 ArgValuesMap avm = linkedArgs.get(linkedId);
818 return avm == null ? false : avm.containsArg(a);
821 public boolean getBoolean(Arg a)
823 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
825 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
828 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
831 public boolean getBool(String linkedId, Arg a)
833 ArgValuesMap avm = linkedArgs.get(linkedId);
835 return a.getDefaultBoolValue();
836 ArgValues avs = avm.getArgValues(a);
837 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
840 public List<String> getLinkedIds()
845 public ArgValuesMap getLinkedArgs(String id)
847 return linkedArgs.get(id);
851 public String toString()
853 StringBuilder sb = new StringBuilder();
854 sb.append("UNLINKED\n");
855 sb.append(argValuesMapToString(linkedArgs.get(null)));
856 if (getLinkedIds() != null)
858 sb.append("LINKED\n");
859 for (String id : getLinkedIds())
861 // already listed these as UNLINKED args
865 ArgValuesMap avm = getLinkedArgs(id);
866 sb.append("ID: '").append(id).append("'\n");
867 sb.append(argValuesMapToString(avm));
870 return sb.toString();
873 private static String argValuesMapToString(ArgValuesMap avm)
877 StringBuilder sb = new StringBuilder();
878 for (Arg a : avm.getArgKeys())
880 ArgValues v = avm.getArgValues(a);
881 sb.append(v.toString());
884 return sb.toString();
887 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
888 boolean initsubstitutions, BootstrapArgs bsa)
890 List<File> argFiles = new ArrayList<>();
892 for (String pattern : argFilenameGlobs)
894 // I don't think we want to dedup files, making life easier
895 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
898 return parseArgFileList(argFiles, initsubstitutions, bsa);
901 public static ArgParser parseArgFileList(List<File> argFiles,
902 boolean initsubstitutions, BootstrapArgs bsa)
904 List<String> argsList = new ArrayList<>();
905 for (File argFile : argFiles)
907 if (!argFile.exists())
909 String message = Arg.ARGFILE.argString() + EQUALS + "\""
910 + argFile.getPath() + "\": File does not exist.";
911 Jalview.exit(message, 2);
915 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
916 .append(EQUALS).append(argFile.getCanonicalPath())
918 argsList.add(setargfile);
919 argsList.addAll(readArgFile(argFile));
920 argsList.add(Arg.UNSETARGFILE.argString());
921 } catch (IOException e)
923 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
924 + "\": File could not be read.";
925 Jalview.exit(message, 3);
928 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
930 return new ArgParser(argsList, initsubstitutions, true, bsa);
933 protected static List<String> readArgFile(File argFile)
935 List<String> args = new ArrayList<>();
936 if (argFile != null && argFile.exists())
940 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
942 if (line != null && line.length() > 0
943 && line.charAt(0) != ARGFILECOMMENT)
946 } catch (IOException e)
948 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
949 + "\": File could not be read.";
950 Console.debug(message, e);
951 Jalview.exit(message, 3);
957 public static enum Position
963 * get from following Arg of type a or subval of same name (lowercase)
965 public static String getValueFromSubValOrArg(ArgValuesMap avm,
966 ArgValue av, Arg a, SubVals sv)
968 return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
972 * get from following Arg of type a or subval key or preference pref or
975 public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
976 Arg a, SubVals sv, String key, String pref, String def)
978 return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref,
983 * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
984 * Arg of type a or subval key or preference pref or default def
986 public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
987 Position pos, ArgValue av, SubVals sv, String key, String pref,
990 return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
994 public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
995 ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
996 String key, String pref, String def)
1000 String value = null;
1001 if (sv != null && sv.has(key) && sv.get(key) != null)
1003 value = ap == null ? sv.get(key)
1004 : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
1006 else if (avm != null && avm.containsArg(a))
1008 if (pos == Position.FIRST && avm.getValue(a) != null)
1009 value = avm.getValue(a);
1010 else if (pos == Position.BEFORE
1011 && avm.getClosestPreviousArgValueOfArg(av, a) != null)
1012 value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
1013 else if (pos == Position.AFTER
1014 && avm.getClosestNextArgValueOfArg(av, a) != null)
1015 value = avm.getClosestNextArgValueOfArg(av, a).getValue();
1017 // look for allstructures subval for Type.STRUCTURE*
1018 Arg arg = av.getArg();
1019 if (value == null && arg.hasOption(Opt.PRIMARY)
1020 && arg.getType() == Type.STRUCTURE
1021 && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
1022 || a.getType() == Type.STRUCTUREIMAGE))
1024 ArgValue av2 = avm.getArgValueOfArgWithSubValKey(a,
1025 Arg.ALLSTRUCTURES.getName());
1028 value = av2.getValue();
1034 value = pref != null ? Cache.getDefault(pref, def) : def;
1039 public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
1042 return getFromSubValArgOrPref(avm, a, sv, null, null, false);
1045 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1046 SubVals sv, String key, String pref, boolean def)
1048 return getFromSubValArgOrPref(avm, a, sv, key, pref, def, false);
1051 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1052 SubVals sv, String key, String pref, boolean def,
1055 if ((key == null && a == null) || (sv == null && a == null))
1058 boolean usingArgKey = false;
1065 String nokey = ArgParser.NEGATESTRING + key;
1067 // look for key or nokey in subvals first (if using Arg check options)
1070 // check for true boolean
1071 if (sv.has(key) && sv.get(key) != null)
1075 if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
1078 "Looking for boolean in subval from non-boolean/non-unary Arg "
1083 return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
1086 // check for negative boolean (subval "no..." will be "true")
1087 if (sv.has(nokey) && sv.get(nokey) != null)
1091 if (!(a.hasOption(Opt.BOOLEAN)))
1094 "Looking for negative boolean in subval from non-boolean Arg "
1099 return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
1104 if (avm != null && avm.containsArg(a))
1105 return avm.getBoolean(a);
1107 // return preference or default
1108 boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
1109 return pref != null ? (invertPref ? !prefVal : prefVal) : def;
1112 // the following methods look for the "*" linkedId and add the argvalue to all
1113 // linkedId ArgValues if it does.
1115 * This version inserts the subvals sv into all created values
1117 private void addValue(String linkedId, Type type, ArgValues avs,
1118 SubVals sv, String v, int argIndex, boolean doSubs)
1120 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
1124 private void addValue(String linkedId, Type type, ArgValues avs, String v,
1125 int argIndex, boolean doSubs)
1127 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, null, v, false,
1131 private void setBoolean(String linkedId, Type type, ArgValues avs,
1132 boolean b, int argIndex)
1134 this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
1135 b, argIndex, false);
1138 private void setNegated(String linkedId, ArgValues avs, boolean b)
1140 this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
1144 private void incrementCount(String linkedId, ArgValues avs)
1146 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
1147 null, false, 0, false);
1152 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1155 private void argValueOperation(Op op, String linkedId, Type type,
1156 ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
1159 // default to merge subvals if subvals are provided
1160 argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
1165 * The following operations look for the "*" and "open*" linkedIds and add the
1166 * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1167 * supplied, they are inserted into all new set values.
1170 * The ArgParser.Op operation
1172 * The String linkedId from the ArgValuesMap
1174 * The Arg.Type to attach to this ArgValue
1176 * The ArgValues for this linkedId
1178 * Use these SubVals on the ArgValue
1180 * Merge the SubVals with any existing on the value. False will
1181 * replace unless sv is null
1183 * The value of the ArgValue (may contain subvals).
1185 * The boolean value of the ArgValue.
1187 * The argIndex for the ArgValue.
1189 * Whether to perform substitutions on the subvals and value.
1191 private void argValueOperation(Op op, String linkedId, Type type,
1192 ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
1193 int argIndex, boolean doSubs)
1197 List<String> wildcardLinkedIds = null;
1198 if (a.hasOption(Opt.ALLOWALL))
1202 case MATCHALLLINKEDIDS:
1203 wildcardLinkedIds = getLinkedIds();
1205 case MATCHOPENEDLINKEDIDS:
1206 wildcardLinkedIds = this.storedLinkedIds;
1211 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1212 // to storedLinkedIds
1213 if (linkedId != null && wildcardLinkedIds == null
1214 && a.hasOption(Opt.STORED)
1215 && !storedLinkedIds.contains(linkedId))
1217 storedLinkedIds.add(linkedId);
1220 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1222 if (wildcardLinkedIds != null)
1224 for (String id : wildcardLinkedIds)
1226 // skip incorrectly stored wildcard ids!
1227 if (id == null || MATCHALLLINKEDIDS.equals(id)
1228 || MATCHOPENEDLINKEDIDS.equals(id))
1230 ArgValuesMap avm = linkedArgs.get(id);
1231 // don't set an output if there isn't an input
1232 if (a.hasOption(Opt.REQUIREINPUT)
1233 && !avm.hasArgWithOption(Opt.INPUT))
1236 ArgValues tavs = avm.getOrCreateArgValues(a);
1246 sv = new SubVals(sv, val, merge);
1247 val = makeSubstitutions(sv.getContent(), id);
1249 tavs.addValue(sv, type, val, argIndex, true);
1255 val = makeSubstitutions(v, id);
1257 tavs.addValue(type, val, argIndex, true);
1259 finaliseStoringArgValue(id, tavs);
1263 tavs.setBoolean(type, b, argIndex, true);
1264 finaliseStoringArgValue(id, tavs);
1268 tavs.setNegated(b, true);
1271 case INCREMENTCOUNT:
1272 tavs.incrementCount();
1282 else // no wildcard linkedId -- do it simpler
1292 val = makeSubstitutions(v, linkedId);
1293 sv = new SubVals(sv, val);
1295 avs.addValue(sv, type, val, argIndex, false);
1301 val = makeSubstitutions(v, linkedId);
1303 avs.addValue(type, val, argIndex, false);
1305 finaliseStoringArgValue(linkedId, avs);
1309 avs.setBoolean(type, b, argIndex, false);
1310 finaliseStoringArgValue(linkedId, avs);
1314 avs.setNegated(b, false);
1317 case INCREMENTCOUNT:
1318 avs.incrementCount();
1327 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1329 if (linkedArgs.containsKey(linkedId)
1330 && linkedArgs.get(linkedId) != null)
1331 return linkedArgs.get(linkedId);
1333 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1334 return linkedArgs.get(linkedId);