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 protected 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;
305 if (a == null && argName.startsWith(NEGATESTRING) && argMap
306 .containsKey(argName.substring(NEGATESTRING.length())))
308 argName = argName.substring(NEGATESTRING.length());
309 a = argMap.get(argName);
313 // check for config errors
317 Console.error("Argument '" + arg + "' not recognised. Exiting.");
318 Jalview.exit("Invalid argument used." + System.lineSeparator()
319 + "Use" + System.lineSeparator() + "jalview "
320 + Arg.HELP.argString() + System.lineSeparator()
321 + "for a usage statement.", 13);
324 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
327 "Argument '" + a.argString() + "' is private. Ignoring.");
330 if (!a.hasOption(Opt.BOOLEAN) && negated)
332 // used "no" with a non-boolean option
333 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
334 + "' not a boolean option. Ignoring.");
337 if (!a.hasOption(Opt.STRING) && equalPos > -1)
339 // set --argname=value when arg does not accept values
340 Console.error("Argument '" + a.argString()
341 + "' does not expect a value (given as '" + arg
345 if (!a.hasOption(Opt.LINKED) && linkedId != null)
347 // set --argname[linkedId] when arg does not use linkedIds
348 Console.error("Argument '" + a.argString()
349 + "' does not expect a linked id (given as '" + arg
355 if (a.hasOption(Opt.STRING))
359 if (a.hasOption(Opt.GLOB))
361 // strip off and save the SubVals to be added individually later
362 globSubVals = new SubVals(val);
363 // make substitutions before looking for files
364 String fileGlob = makeSubstitutions(globSubVals.getContent(),
366 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
370 // val is already set -- will be saved in the ArgValue later in
376 // There is no "=" so value is next arg or args (possibly shell
378 if ((openEachInitialFilenames ? i : i + 1) >= args.size())
380 // no value to take for arg, which wants a value
381 Console.error("Argument '" + a.getName()
382 + "' requires a value, none given. Ignoring.");
385 // deal with bash globs here (--arg val* is expanded before reaching
386 // the JVM). Note that SubVals cannot be used in this case.
387 // If using the --arg=val then the glob is preserved and Java globs
388 // will be used later. SubVals can be used.
389 if (a.hasOption(Opt.GLOB))
391 // if this is the first argument with a file list at the start of
392 // the args we add filenames from index i instead of i+1
393 globVals = getShellGlobbedFilenameValues(a, args,
394 openEachInitialFilenames ? i : i + 1);
398 val = args.get(i + 1);
403 // make NOACTION adjustments
404 // default and auto counter increments
407 linkedIdAutoCounter++;
409 else if (a == Arg.SUBSTITUTIONS)
411 substitutions = !negated;
413 else if (a == Arg.SETARGFILE)
417 else if (a == Arg.UNSETARGFILE)
421 else if (a == Arg.ALL)
423 allLinkedIds = !negated;
424 openedLinkedIds = false;
426 else if (a == Arg.OPENED)
428 openedLinkedIds = !negated;
429 allLinkedIds = false;
431 else if (a == Arg.ALLSTRUCTURES)
433 allStructures = !negated;
436 if (a.hasOption(Opt.STORED))
438 // reset the lastOpenedLinkedIds list
439 this.storedLinkedIds = new ArrayList<>();
442 // this is probably only Arg.NEW and Arg.OPEN
443 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
445 // use the next default prefixed OPENLINKEDID
446 defaultLinkedId(true);
449 String autoCounterString = null;
450 String defaultLinkedId = defaultLinkedId(false);
451 boolean usingDefaultLinkedId = false;
452 if (a.hasOption(Opt.LINKED))
454 if (linkedId == null)
456 if (a.hasOption(Opt.OUTPUT) && a.hasOption(Opt.ALLOWALL)
457 && val.startsWith(MATCHALLLINKEDIDS))
459 // --output=*.ext is shorthand for --all --output {basename}.ext
460 // (or --image=*.ext)
462 openedLinkedIds = false;
463 linkedId = MATCHALLLINKEDIDS;
464 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
465 + val.substring(MATCHALLLINKEDIDS.length());
467 else if (a.hasOption(Opt.OUTPUT) && a.hasOption(Opt.ALLOWALL)
468 && val.startsWith(MATCHOPENEDLINKEDIDS))
470 // --output=open*.ext is shorthand for --opened --output
472 // (or --image=open*.ext)
473 openedLinkedIds = true;
474 allLinkedIds = false;
475 linkedId = MATCHOPENEDLINKEDIDS;
476 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
477 + val.substring(MATCHOPENEDLINKEDIDS.length());
479 else if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
481 linkedId = MATCHALLLINKEDIDS;
483 else if (openedLinkedIds && a.hasOption(Opt.ALLOWALL))
485 linkedId = MATCHOPENEDLINKEDIDS;
489 // use default linkedId for linked arguments
490 linkedId = defaultLinkedId;
491 usingDefaultLinkedId = true;
492 Console.debug("Changing linkedId to '" + linkedId + "' from "
498 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
500 // turn {n} to the autoCounter
501 autoCounterString = Integer.toString(linkedIdAutoCounter);
502 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
504 Console.debug("Changing linkedId to '" + linkedId + "' from "
507 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
509 // turn {++n} to the incremented autoCounter
510 autoCounterString = Integer.toString(++linkedIdAutoCounter);
511 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
513 Console.debug("Changing linkedId to '" + linkedId + "' from "
519 // do not continue in this block for NOACTION args
520 if (a.hasOption(Opt.NOACTION))
523 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
525 // not dealing with both NODUPLICATEVALUES and GLOB
526 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
528 Console.error("Argument '" + a.argString()
529 + "' cannot contain a duplicate value ('" + val
530 + "'). Ignoring this and subsequent occurrences.");
534 // check for unique id
535 SubVals subvals = new SubVals(val);
536 boolean addNewSubVals = false;
537 String id = subvals.get(ArgValues.ID);
538 if (id != null && avm.hasId(a, id))
540 Console.error("Argument '" + a.argString()
541 + "' has a duplicate id ('" + id + "'). Ignoring.");
545 // set allstructures to all non-primary structure options in this linked
546 // id if --allstructures has been set
548 && (a.getType() == Type.STRUCTURE
549 || a.getType() == Type.STRUCTUREIMAGE)
550 && !a.hasOption(Opt.PRIMARY))
552 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
553 // && !subvals.has("structureid"))
555 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
556 addNewSubVals = true;
560 ArgValues avs = avm.getOrCreateArgValues(a);
562 // store appropriate String value(s)
563 if (a.hasOption(Opt.STRING))
565 if (a.hasOption(Opt.GLOB) && globVals != null
566 && globVals.size() > 0)
568 Enumeration<String> gve = Collections.enumeration(globVals);
569 while (gve.hasMoreElements())
571 String v = gve.nextElement();
572 SubVals vsv = new SubVals(globSubVals, v);
573 addValue(linkedId, type, avs, vsv, v, argIndex++, true);
574 // if we're using defaultLinkedId and the arg increments the
576 if (gve.hasMoreElements() && usingDefaultLinkedId
577 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
579 // increment the default linkedId
580 linkedId = defaultLinkedId(true);
581 // get new avm and avs
582 avm = linkedArgs.get(linkedId);
583 avs = avm.getOrCreateArgValues(a);
589 // addValue(linkedId, type, avs, val, argIndex, true);
590 addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
591 val, argIndex, true);
594 else if (a.hasOption(Opt.BOOLEAN))
596 setBoolean(linkedId, type, avs, !negated, argIndex);
597 setNegated(linkedId, avs, negated);
599 else if (a.hasOption(Opt.UNARY))
601 setBoolean(linkedId, type, avs, true, argIndex);
604 // remove the '*' or 'open*' linkedId that should be empty if it was
606 if ((MATCHALLLINKEDIDS.equals(linkedId)
607 && linkedArgs.containsKey(linkedId))
608 || (MATCHOPENEDLINKEDIDS.equals(linkedId)
609 && linkedArgs.containsKey(linkedId)))
611 linkedArgs.remove(linkedId);
617 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
620 incrementCount(linkedId, avs);
623 // store in appropriate place
624 if (a.hasOption(Opt.LINKED))
626 // store the order of linkedIds
627 if (!linkedOrder.contains(linkedId))
628 linkedOrder.add(linkedId);
631 // store arg in the list of args used
632 if (!argList.contains(a))
636 private String defaultLinkedId(boolean increment)
638 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
639 .append(Integer.toString(defaultLinkedIdCounter)).toString();
642 while (linkedArgs.containsKey(defaultLinkedId))
644 defaultLinkedIdCounter++;
645 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
646 .append(Integer.toString(defaultLinkedIdCounter))
650 getOrCreateLinkedArgValuesMap(defaultLinkedId);
651 return defaultLinkedId;
654 public String makeSubstitutions(String val, String linkedId)
656 if (!this.substitutions || val == null)
661 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
663 int closeBracket = val.indexOf(']');
664 if (val.length() == closeBracket)
666 subvals = val.substring(0, closeBracket + 1);
667 rest = val.substring(closeBracket + 1);
674 if (rest.contains(LINKEDIDAUTOCOUNTER))
675 rest = rest.replace(LINKEDIDAUTOCOUNTER,
676 String.valueOf(linkedIdAutoCounter));
677 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
678 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
679 String.valueOf(++linkedIdAutoCounter));
680 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
681 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
682 String.valueOf(defaultLinkedIdCounter));
683 ArgValuesMap avm = linkedArgs.get(linkedId);
686 if (rest.contains(LINKEDIDBASENAME))
688 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
690 if (rest.contains(LINKEDIDEXTENSION))
692 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
694 if (rest.contains(LINKEDIDDIRNAME))
696 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
701 if (rest.contains(ARGFILEBASENAME))
703 rest = rest.replace(ARGFILEBASENAME,
704 FileUtils.getBasename(new File(argFile)));
706 if (rest.contains(ARGFILEDIRNAME))
708 rest = rest.replace(ARGFILEDIRNAME,
709 FileUtils.getDirname(new File(argFile)));
713 return new StringBuilder(subvals).append(rest).toString();
717 * A helper method to take a list of String args where we're expecting
718 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
719 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
720 * "file2", "file3"} *and remove these from the original list object* so that
721 * processing can continue from where it has left off, e.g. args has become
722 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
723 * carries on from the next --arg if available.
725 protected static List<String> getShellGlobbedFilenameValues(Arg a,
726 List<String> args, int i)
728 List<String> vals = new ArrayList<>();
729 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
731 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
732 if (!a.hasOption(Opt.GLOB))
738 public BootstrapArgs getBootstrapArgs()
740 return bootstrapArgs;
743 public boolean isSet(Arg a)
745 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
748 public boolean isSetAtAll(Arg a)
750 for (String linkedId : linkedOrder)
752 if (isSet(linkedId, a))
758 public boolean isSet(String linkedId, Arg a)
760 ArgValuesMap avm = linkedArgs.get(linkedId);
761 return avm == null ? false : avm.containsArg(a);
764 public boolean getBoolean(Arg a)
766 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
768 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
771 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
774 public boolean getBool(String linkedId, Arg a)
776 ArgValuesMap avm = linkedArgs.get(linkedId);
778 return a.getDefaultBoolValue();
779 ArgValues avs = avm.getArgValues(a);
780 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
783 public List<String> getLinkedIds()
788 public ArgValuesMap getLinkedArgs(String id)
790 return linkedArgs.get(id);
794 public String toString()
796 StringBuilder sb = new StringBuilder();
797 sb.append("UNLINKED\n");
798 sb.append(argValuesMapToString(linkedArgs.get(null)));
799 if (getLinkedIds() != null)
801 sb.append("LINKED\n");
802 for (String id : getLinkedIds())
804 // already listed these as UNLINKED args
808 ArgValuesMap avm = getLinkedArgs(id);
809 sb.append("ID: '").append(id).append("'\n");
810 sb.append(argValuesMapToString(avm));
813 return sb.toString();
816 private static String argValuesMapToString(ArgValuesMap avm)
820 StringBuilder sb = new StringBuilder();
821 for (Arg a : avm.getArgKeys())
823 ArgValues v = avm.getArgValues(a);
824 sb.append(v.toString());
827 return sb.toString();
830 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
831 boolean initsubstitutions, BootstrapArgs bsa)
833 List<File> argFiles = new ArrayList<>();
835 for (String pattern : argFilenameGlobs)
837 // I don't think we want to dedup files, making life easier
838 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
841 return parseArgFileList(argFiles, initsubstitutions, bsa);
844 public static ArgParser parseArgFileList(List<File> argFiles,
845 boolean initsubstitutions, BootstrapArgs bsa)
847 List<String> argsList = new ArrayList<>();
848 for (File argFile : argFiles)
850 if (!argFile.exists())
852 String message = Arg.ARGFILE.argString() + EQUALS + "\""
853 + argFile.getPath() + "\": File does not exist.";
854 Jalview.exit(message, 2);
858 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
859 .append(EQUALS).append(argFile.getCanonicalPath())
861 argsList.add(setargfile);
862 argsList.addAll(readArgFile(argFile));
863 argsList.add(Arg.UNSETARGFILE.argString());
864 } catch (IOException e)
866 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
867 + "\": File could not be read.";
868 Jalview.exit(message, 3);
871 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
873 return new ArgParser(argsList, initsubstitutions, true, bsa);
876 protected static List<String> readArgFile(File argFile)
878 List<String> args = new ArrayList<>();
879 if (argFile != null && argFile.exists())
883 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
885 if (line != null && line.length() > 0
886 && line.charAt(0) != ARGFILECOMMENT)
889 } catch (IOException e)
891 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
892 + "\": File could not be read.";
893 Console.debug(message, e);
894 Jalview.exit(message, 3);
900 public static enum Position
905 // get from following Arg of type a or subval of same name (lowercase)
906 public static String getValueFromSubValOrArg(ArgValuesMap avm,
907 ArgValue av, Arg a, SubVals sv)
909 return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
912 // get from following Arg of type a or subval key or preference pref or
914 public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
915 Arg a, SubVals sv, String key, String pref, String def)
917 return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref,
921 // get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
922 // Arg of type a or subval key or preference pref or default def
923 public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
924 Position pos, ArgValue av, SubVals sv, String key, String pref,
927 return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
931 public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
932 ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
933 String key, String pref, String def)
938 if (sv != null && sv.has(key) && sv.get(key) != null)
940 value = ap == null ? sv.get(key)
941 : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
943 else if (avm != null && avm.containsArg(a))
945 if (pos == Position.FIRST && avm.getValue(a) != null)
946 value = avm.getValue(a);
947 else if (pos == Position.BEFORE
948 && avm.getClosestPreviousArgValueOfArg(av, a) != null)
949 value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
950 else if (pos == Position.AFTER
951 && avm.getClosestNextArgValueOfArg(av, a) != null)
952 value = avm.getClosestNextArgValueOfArg(av, a).getValue();
954 // look for allstructures subval for Type.STRUCTURE*
955 Arg arg = av.getArg();
956 if (value == null && arg.hasOption(Opt.PRIMARY)
957 && arg.getType() == Type.STRUCTURE
958 && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
959 || a.getType() == Type.STRUCTUREIMAGE))
961 ArgValue av2 = avm.getArgValueOfArgWithSubValKey(a,
962 Arg.ALLSTRUCTURES.getName());
965 value = av2.getValue();
971 value = pref != null ? Cache.getDefault(pref, def) : def;
976 public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
979 return getFromSubValArgOrPref(avm, a, sv, null, null, false);
982 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
983 SubVals sv, String key, String pref, boolean def)
985 return getFromSubValArgOrPref(avm, a, sv, key, pref, def, false);
988 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
989 SubVals sv, String key, String pref, boolean def,
992 if ((key == null && a == null) || (sv == null && a == null))
995 boolean usingArgKey = false;
1002 String nokey = ArgParser.NEGATESTRING + key;
1004 // look for key or nokey in subvals first (if using Arg check options)
1007 // check for true boolean
1008 if (sv.has(key) && sv.get(key) != null)
1012 if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
1015 "Looking for boolean in subval from non-boolean/non-unary Arg "
1020 return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
1023 // check for negative boolean (subval "no..." will be "true")
1024 if (sv.has(nokey) && sv.get(nokey) != null)
1028 if (!(a.hasOption(Opt.BOOLEAN)))
1031 "Looking for negative boolean in subval from non-boolean Arg "
1036 return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
1041 if (avm != null && avm.containsArg(a))
1042 return avm.getBoolean(a);
1044 // return preference or default
1045 boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
1046 return pref != null ? (invertPref ? !prefVal : prefVal) : def;
1049 // the following methods look for the "*" linkedId and add the argvalue to all
1050 // linkedId ArgValues if it does.
1051 // This version inserts the subvals sv into all created values
1052 private void addValue(String linkedId, Type type, ArgValues avs,
1053 SubVals sv, String v, int argIndex, boolean doSubs)
1055 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
1059 private void addValue(String linkedId, Type type, ArgValues avs, String v,
1060 int argIndex, boolean doSubs)
1062 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, null, v, false,
1066 private void setBoolean(String linkedId, Type type, ArgValues avs,
1067 boolean b, int argIndex)
1069 this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
1070 b, argIndex, false);
1073 private void setNegated(String linkedId, ArgValues avs, boolean b)
1075 this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
1079 private void incrementCount(String linkedId, ArgValues avs)
1081 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
1082 null, false, 0, false);
1087 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1090 private void argValueOperation(Op op, String linkedId, Type type,
1091 ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
1094 // default to merge subvals if subvals are provided
1095 argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
1100 * The following operations look for the "*" and "open*" linkedIds and add the
1101 * argvalue to all appropriate linkedId ArgValues if it does.
1102 * If subvals are supplied, they are inserted into all new set values.
1104 * @param op The ArgParser.Op operation
1105 * @param linkedId The String linkedId from the ArgValuesMap
1106 * @param type The Arg.Type to attach to this ArgValue
1107 * @param avs The ArgValues for this linkedId
1108 * @param sv Use these SubVals on the ArgValue
1109 * @param merge Merge the SubVals with any existing on the value. False will replace unless sv is null
1110 * @param v The value of the ArgValue (may contain subvals).
1111 * @param b The boolean value of the ArgValue.
1112 * @param argIndex The argIndex for the ArgValue.
1113 * @param doSubs Whether to perform substitutions on the subvals and value.
1115 private void argValueOperation(Op op, String linkedId, Type type,
1116 ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
1117 int argIndex, boolean doSubs)
1121 List<String> wildcardLinkedIds = null;
1122 if (a.hasOption(Opt.ALLOWALL))
1126 case MATCHALLLINKEDIDS:
1127 wildcardLinkedIds = getLinkedIds();
1129 case MATCHOPENEDLINKEDIDS:
1130 wildcardLinkedIds = this.storedLinkedIds;
1135 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1136 // to storedLinkedIds
1137 if (linkedId != null && wildcardLinkedIds == null
1138 && a.hasOption(Opt.STORED)
1139 && !storedLinkedIds.contains(linkedId))
1141 storedLinkedIds.add(linkedId);
1144 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1146 if (wildcardLinkedIds != null)
1148 for (String id : wildcardLinkedIds)
1150 // skip incorrectly stored wildcard ids!
1151 if (id == null || MATCHALLLINKEDIDS.equals(id)
1152 || MATCHOPENEDLINKEDIDS.equals(id))
1154 ArgValuesMap avm = linkedArgs.get(id);
1155 // don't set an output if there isn't an input
1156 if (a.hasOption(Opt.REQUIREINPUT)
1157 && !avm.hasArgWithOption(Opt.INPUT))
1160 ArgValues tavs = avm.getOrCreateArgValues(a);
1170 sv = new SubVals(sv, val, merge);
1171 val = makeSubstitutions(sv.getContent(), id);
1173 tavs.addValue(sv, type, val, argIndex, true);
1179 val = makeSubstitutions(v, id);
1181 tavs.addValue(type, val, argIndex, true);
1183 finaliseStoringArgValue(id, tavs);
1187 tavs.setBoolean(type, b, argIndex, true);
1188 finaliseStoringArgValue(id, tavs);
1192 tavs.setNegated(b, true);
1195 case INCREMENTCOUNT:
1196 tavs.incrementCount();
1206 else // no wildcard linkedId -- do it simpler
1216 val = makeSubstitutions(v, linkedId);
1217 sv = new SubVals(sv, val);
1219 avs.addValue(sv, type, val, argIndex, false);
1225 val = makeSubstitutions(v, linkedId);
1227 avs.addValue(type, val, argIndex, false);
1229 finaliseStoringArgValue(linkedId, avs);
1233 avs.setBoolean(type, b, argIndex, false);
1234 finaliseStoringArgValue(linkedId, avs);
1238 avs.setNegated(b, false);
1241 case INCREMENTCOUNT:
1242 avs.incrementCount();
1251 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1253 if (linkedArgs.containsKey(linkedId)
1254 && linkedArgs.get(linkedId) != null)
1255 return linkedArgs.get(linkedId);
1257 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1258 return linkedArgs.get(linkedId);