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";
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;
339 if (a == null && argName.startsWith(NEGATESTRING) && argMap
340 .containsKey(argName.substring(NEGATESTRING.length())))
342 argName = argName.substring(NEGATESTRING.length());
343 a = argMap.get(argName);
347 // check for config errors
351 Console.error("Argument '" + arg + "' not recognised. Exiting.");
352 Jalview.exit("Invalid argument used." + System.lineSeparator()
353 + "Use" + System.lineSeparator() + "jalview "
354 + Arg.HELP.argString() + System.lineSeparator()
355 + "for a usage statement.", 13);
358 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
361 "Argument '" + a.argString() + "' is private. Ignoring.");
364 if (!a.hasOption(Opt.BOOLEAN) && negated)
366 // used "no" with a non-boolean option
367 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
368 + "' not a boolean option. Ignoring.");
371 if (!a.hasOption(Opt.STRING) && equalPos > -1)
373 // set --argname=value when arg does not accept values
374 Console.error("Argument '" + a.argString()
375 + "' does not expect a value (given as '" + arg
379 if (!a.hasOption(Opt.LINKED) && linkedId != null)
381 // set --argname[linkedId] when arg does not use linkedIds
382 Console.error("Argument '" + a.argString()
383 + "' does not expect a linked id (given as '" + arg
389 if (a.hasOption(Opt.STRING))
393 if (a.hasOption(Opt.GLOB))
395 // strip off and save the SubVals to be added individually later
396 globSubVals = new SubVals(val);
397 // make substitutions before looking for files
398 String fileGlob = makeSubstitutions(globSubVals.getContent(),
400 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
404 // val is already set -- will be saved in the ArgValue later in
410 // There is no "=" so value is next arg or args (possibly shell
412 if ((openEachInitialFilenames ? i : i + 1) >= args.size())
414 // no value to take for arg, which wants a value
415 Console.error("Argument '" + a.getName()
416 + "' requires a value, none given. Ignoring.");
419 // deal with bash globs here (--arg val* is expanded before reaching
420 // the JVM). Note that SubVals cannot be used in this case.
421 // If using the --arg=val then the glob is preserved and Java globs
422 // will be used later. SubVals can be used.
423 if (a.hasOption(Opt.GLOB))
425 // if this is the first argument with a file list at the start of
426 // the args we add filenames from index i instead of i+1
427 globVals = getShellGlobbedFilenameValues(a, args,
428 openEachInitialFilenames ? i : i + 1);
432 val = args.get(i + 1);
437 // make NOACTION adjustments
438 // default and auto counter increments
441 linkedIdAutoCounter++;
443 else if (a == Arg.SUBSTITUTIONS)
445 substitutions = !negated;
447 else if (a == Arg.SETARGFILE)
451 else if (a == Arg.UNSETARGFILE)
455 else if (a == Arg.ALL)
457 allLinkedIds = !negated;
458 openedLinkedIds = false;
460 else if (a == Arg.OPENED)
462 openedLinkedIds = !negated;
463 allLinkedIds = false;
465 else if (a == Arg.ALLSTRUCTURES)
467 allStructures = !negated;
470 if (a.hasOption(Opt.STORED))
472 // reset the lastOpenedLinkedIds list
473 this.storedLinkedIds = new ArrayList<>();
476 // this is probably only Arg.NEW and Arg.OPEN
477 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
479 // use the next default prefixed OPENLINKEDID
480 defaultLinkedId(true);
483 String autoCounterString = null;
484 String defaultLinkedId = defaultLinkedId(false);
485 boolean usingDefaultLinkedId = false;
486 if (a.hasOption(Opt.LINKED))
488 if (linkedId == null)
490 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWALL)
491 && val.startsWith(MATCHALLLINKEDIDS))
493 // --output=*.ext is shorthand for --all --output {basename}.ext
494 // (or --image=*.ext)
496 openedLinkedIds = false;
497 linkedId = MATCHALLLINKEDIDS;
498 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
499 + val.substring(MATCHALLLINKEDIDS.length());
501 else if (a.hasOption(Opt.OUTPUTFILE)
502 && a.hasOption(Opt.ALLOWALL)
503 && val.startsWith(MATCHOPENEDLINKEDIDS))
505 // --output=open*.ext is shorthand for --opened --output
507 // (or --image=open*.ext)
508 openedLinkedIds = true;
509 allLinkedIds = false;
510 linkedId = MATCHOPENEDLINKEDIDS;
511 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
512 + val.substring(MATCHOPENEDLINKEDIDS.length());
514 else if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
516 linkedId = MATCHALLLINKEDIDS;
518 else if (openedLinkedIds && a.hasOption(Opt.ALLOWALL))
520 linkedId = MATCHOPENEDLINKEDIDS;
524 // use default linkedId for linked arguments
525 linkedId = defaultLinkedId;
526 usingDefaultLinkedId = true;
527 Console.debug("Changing linkedId to '" + linkedId + "' from "
533 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
535 // turn {n} to the autoCounter
536 autoCounterString = Integer.toString(linkedIdAutoCounter);
537 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
539 Console.debug("Changing linkedId to '" + linkedId + "' from "
542 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
544 // turn {++n} to the incremented autoCounter
545 autoCounterString = Integer.toString(++linkedIdAutoCounter);
546 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
548 Console.debug("Changing linkedId to '" + linkedId + "' from "
554 // do not continue in this block for NOACTION args
555 if (a.hasOption(Opt.NOACTION))
558 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
560 // not dealing with both NODUPLICATEVALUES and GLOB
561 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
563 Console.error("Argument '" + a.argString()
564 + "' cannot contain a duplicate value ('" + val
565 + "'). Ignoring this and subsequent occurrences.");
569 // check for unique id
570 SubVals subvals = new SubVals(val);
571 boolean addNewSubVals = false;
572 String id = subvals.get(ArgValues.ID);
573 if (id != null && avm.hasId(a, id))
575 Console.error("Argument '" + a.argString()
576 + "' has a duplicate id ('" + id + "'). Ignoring.");
580 // set allstructures to all non-primary structure options in this linked
581 // id if --allstructures has been set
583 && (a.getType() == Type.STRUCTURE
584 || a.getType() == Type.STRUCTUREIMAGE)
585 && !a.hasOption(Opt.PRIMARY))
587 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
588 // && !subvals.has("structureid"))
590 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
591 addNewSubVals = true;
595 ArgValues avs = avm.getOrCreateArgValues(a);
597 // store appropriate String value(s)
598 if (a.hasOption(Opt.STRING))
600 if (a.hasOption(Opt.GLOB) && globVals != null
601 && globVals.size() > 0)
603 Enumeration<String> gve = Collections.enumeration(globVals);
604 while (gve.hasMoreElements())
606 String v = gve.nextElement();
607 SubVals vsv = new SubVals(globSubVals, v);
608 addValue(linkedId, type, avs, vsv, v, argIndex++, true);
609 // if we're using defaultLinkedId and the arg increments the
611 if (gve.hasMoreElements() && usingDefaultLinkedId
612 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
614 // increment the default linkedId
615 linkedId = defaultLinkedId(true);
616 // get new avm and avs
617 avm = linkedArgs.get(linkedId);
618 avs = avm.getOrCreateArgValues(a);
624 // addValue(linkedId, type, avs, val, argIndex, true);
625 addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
626 val, argIndex, true);
629 else if (a.hasOption(Opt.BOOLEAN))
631 setBoolean(linkedId, type, avs, !negated, argIndex);
632 setNegated(linkedId, avs, negated);
634 else if (a.hasOption(Opt.UNARY))
636 setBoolean(linkedId, type, avs, true, argIndex);
639 // remove the '*' or 'open*' linkedId that should be empty if it was
641 if ((MATCHALLLINKEDIDS.equals(linkedId)
642 && linkedArgs.containsKey(linkedId))
643 || (MATCHOPENEDLINKEDIDS.equals(linkedId)
644 && linkedArgs.containsKey(linkedId)))
646 linkedArgs.remove(linkedId);
652 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
655 incrementCount(linkedId, avs);
658 // store in appropriate place
659 if (a.hasOption(Opt.LINKED))
661 // store the order of linkedIds
662 if (!linkedOrder.contains(linkedId))
663 linkedOrder.add(linkedId);
666 // store arg in the list of args used
667 if (!argList.contains(a))
671 private String defaultLinkedId(boolean increment)
673 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
674 .append(Integer.toString(defaultLinkedIdCounter)).toString();
677 while (linkedArgs.containsKey(defaultLinkedId))
679 defaultLinkedIdCounter++;
680 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
681 .append(Integer.toString(defaultLinkedIdCounter))
685 getOrCreateLinkedArgValuesMap(defaultLinkedId);
686 return defaultLinkedId;
689 public String makeSubstitutions(String val, String linkedId)
691 if (!this.substitutions || val == null)
696 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
698 int closeBracket = val.indexOf(']');
699 if (val.length() == closeBracket)
701 subvals = val.substring(0, closeBracket + 1);
702 rest = val.substring(closeBracket + 1);
709 if (rest.contains(LINKEDIDAUTOCOUNTER))
710 rest = rest.replace(LINKEDIDAUTOCOUNTER,
711 String.valueOf(linkedIdAutoCounter));
712 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
713 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
714 String.valueOf(++linkedIdAutoCounter));
715 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
716 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
717 String.valueOf(defaultLinkedIdCounter));
718 ArgValuesMap avm = linkedArgs.get(linkedId);
721 if (rest.contains(LINKEDIDBASENAME))
723 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
725 if (rest.contains(LINKEDIDEXTENSION))
727 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
729 if (rest.contains(LINKEDIDDIRNAME))
731 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
736 if (rest.contains(ARGFILEBASENAME))
738 rest = rest.replace(ARGFILEBASENAME,
739 FileUtils.getBasename(new File(argFile)));
741 if (rest.contains(ARGFILEDIRNAME))
743 rest = rest.replace(ARGFILEDIRNAME,
744 FileUtils.getDirname(new File(argFile)));
748 return new StringBuilder(subvals).append(rest).toString();
752 * A helper method to take a list of String args where we're expecting
753 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
754 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
755 * "file2", "file3"} *and remove these from the original list object* so that
756 * processing can continue from where it has left off, e.g. args has become
757 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
758 * carries on from the next --arg if available.
760 protected static List<String> getShellGlobbedFilenameValues(Arg a,
761 List<String> args, int i)
763 List<String> vals = new ArrayList<>();
764 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
766 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
767 if (!a.hasOption(Opt.GLOB))
773 public BootstrapArgs getBootstrapArgs()
775 return bootstrapArgs;
778 public boolean isSet(Arg a)
780 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
783 public boolean isSetAtAll(Arg a)
785 for (String linkedId : linkedOrder)
787 if (isSet(linkedId, a))
793 public boolean isSet(String linkedId, Arg a)
795 ArgValuesMap avm = linkedArgs.get(linkedId);
796 return avm == null ? false : avm.containsArg(a);
799 public boolean getBoolean(Arg a)
801 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
803 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
806 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
809 public boolean getBool(String linkedId, Arg a)
811 ArgValuesMap avm = linkedArgs.get(linkedId);
813 return a.getDefaultBoolValue();
814 ArgValues avs = avm.getArgValues(a);
815 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
818 public List<String> getLinkedIds()
823 public ArgValuesMap getLinkedArgs(String id)
825 return linkedArgs.get(id);
829 public String toString()
831 StringBuilder sb = new StringBuilder();
832 sb.append("UNLINKED\n");
833 sb.append(argValuesMapToString(linkedArgs.get(null)));
834 if (getLinkedIds() != null)
836 sb.append("LINKED\n");
837 for (String id : getLinkedIds())
839 // already listed these as UNLINKED args
843 ArgValuesMap avm = getLinkedArgs(id);
844 sb.append("ID: '").append(id).append("'\n");
845 sb.append(argValuesMapToString(avm));
848 return sb.toString();
851 private static String argValuesMapToString(ArgValuesMap avm)
855 StringBuilder sb = new StringBuilder();
856 for (Arg a : avm.getArgKeys())
858 ArgValues v = avm.getArgValues(a);
859 sb.append(v.toString());
862 return sb.toString();
865 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
866 boolean initsubstitutions, BootstrapArgs bsa)
868 List<File> argFiles = new ArrayList<>();
870 for (String pattern : argFilenameGlobs)
872 // I don't think we want to dedup files, making life easier
873 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
876 return parseArgFileList(argFiles, initsubstitutions, bsa);
879 public static ArgParser parseArgFileList(List<File> argFiles,
880 boolean initsubstitutions, BootstrapArgs bsa)
882 List<String> argsList = new ArrayList<>();
883 for (File argFile : argFiles)
885 if (!argFile.exists())
887 String message = Arg.ARGFILE.argString() + EQUALS + "\""
888 + argFile.getPath() + "\": File does not exist.";
889 Jalview.exit(message, 2);
893 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
894 .append(EQUALS).append(argFile.getCanonicalPath())
896 argsList.add(setargfile);
897 argsList.addAll(readArgFile(argFile));
898 argsList.add(Arg.UNSETARGFILE.argString());
899 } catch (IOException e)
901 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
902 + "\": File could not be read.";
903 Jalview.exit(message, 3);
906 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
908 return new ArgParser(argsList, initsubstitutions, true, bsa);
911 protected static List<String> readArgFile(File argFile)
913 List<String> args = new ArrayList<>();
914 if (argFile != null && argFile.exists())
918 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
920 if (line != null && line.length() > 0
921 && line.charAt(0) != ARGFILECOMMENT)
924 } catch (IOException e)
926 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
927 + "\": File could not be read.";
928 Console.debug(message, e);
929 Jalview.exit(message, 3);
935 public static enum Position
941 * get from following Arg of type a or subval of same name (lowercase)
943 public static String getValueFromSubValOrArg(ArgValuesMap avm,
944 ArgValue av, Arg a, SubVals sv)
946 return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
950 * get from following Arg of type a or subval key or preference pref or
953 public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
954 Arg a, SubVals sv, String key, String pref, String def)
956 return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref,
961 * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
962 * Arg of type a or subval key or preference pref or default def
964 public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
965 Position pos, ArgValue av, SubVals sv, String key, String pref,
968 return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
972 public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
973 ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
974 String key, String pref, String def)
979 if (sv != null && sv.has(key) && sv.get(key) != null)
981 value = ap == null ? sv.get(key)
982 : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
984 else if (avm != null && avm.containsArg(a))
986 if (pos == Position.FIRST && avm.getValue(a) != null)
987 value = avm.getValue(a);
988 else if (pos == Position.BEFORE
989 && avm.getClosestPreviousArgValueOfArg(av, a) != null)
990 value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
991 else if (pos == Position.AFTER
992 && avm.getClosestNextArgValueOfArg(av, a) != null)
993 value = avm.getClosestNextArgValueOfArg(av, a).getValue();
995 // look for allstructures subval for Type.STRUCTURE*
996 Arg arg = av.getArg();
997 if (value == null && arg.hasOption(Opt.PRIMARY)
998 && arg.getType() == Type.STRUCTURE
999 && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
1000 || a.getType() == Type.STRUCTUREIMAGE))
1002 ArgValue av2 = avm.getArgValueOfArgWithSubValKey(a,
1003 Arg.ALLSTRUCTURES.getName());
1006 value = av2.getValue();
1012 value = pref != null ? Cache.getDefault(pref, def) : def;
1017 public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
1020 return getFromSubValArgOrPref(avm, a, sv, null, null, false);
1023 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1024 SubVals sv, String key, String pref, boolean def)
1026 return getFromSubValArgOrPref(avm, a, sv, key, pref, def, false);
1029 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1030 SubVals sv, String key, String pref, boolean def,
1033 if ((key == null && a == null) || (sv == null && a == null))
1036 boolean usingArgKey = false;
1043 String nokey = ArgParser.NEGATESTRING + key;
1045 // look for key or nokey in subvals first (if using Arg check options)
1048 // check for true boolean
1049 if (sv.has(key) && sv.get(key) != null)
1053 if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
1056 "Looking for boolean in subval from non-boolean/non-unary Arg "
1061 return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
1064 // check for negative boolean (subval "no..." will be "true")
1065 if (sv.has(nokey) && sv.get(nokey) != null)
1069 if (!(a.hasOption(Opt.BOOLEAN)))
1072 "Looking for negative boolean in subval from non-boolean Arg "
1077 return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
1082 if (avm != null && avm.containsArg(a))
1083 return avm.getBoolean(a);
1085 // return preference or default
1086 boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
1087 return pref != null ? (invertPref ? !prefVal : prefVal) : def;
1090 // the following methods look for the "*" linkedId and add the argvalue to all
1091 // linkedId ArgValues if it does.
1093 * This version inserts the subvals sv into all created values
1095 private void addValue(String linkedId, Type type, ArgValues avs,
1096 SubVals sv, String v, int argIndex, boolean doSubs)
1098 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
1102 private void addValue(String linkedId, Type type, ArgValues avs, String v,
1103 int argIndex, boolean doSubs)
1105 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, null, v, false,
1109 private void setBoolean(String linkedId, Type type, ArgValues avs,
1110 boolean b, int argIndex)
1112 this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
1113 b, argIndex, false);
1116 private void setNegated(String linkedId, ArgValues avs, boolean b)
1118 this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
1122 private void incrementCount(String linkedId, ArgValues avs)
1124 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
1125 null, false, 0, false);
1130 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1133 private void argValueOperation(Op op, String linkedId, Type type,
1134 ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
1137 // default to merge subvals if subvals are provided
1138 argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
1143 * The following operations look for the "*" and "open*" linkedIds and add the
1144 * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1145 * supplied, they are inserted into all new set values.
1148 * The ArgParser.Op operation
1150 * The String linkedId from the ArgValuesMap
1152 * The Arg.Type to attach to this ArgValue
1154 * The ArgValues for this linkedId
1156 * Use these SubVals on the ArgValue
1158 * Merge the SubVals with any existing on the value. False will
1159 * replace unless sv is null
1161 * The value of the ArgValue (may contain subvals).
1163 * The boolean value of the ArgValue.
1165 * The argIndex for the ArgValue.
1167 * Whether to perform substitutions on the subvals and value.
1169 private void argValueOperation(Op op, String linkedId, Type type,
1170 ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
1171 int argIndex, boolean doSubs)
1175 List<String> wildcardLinkedIds = null;
1176 if (a.hasOption(Opt.ALLOWALL))
1180 case MATCHALLLINKEDIDS:
1181 wildcardLinkedIds = getLinkedIds();
1183 case MATCHOPENEDLINKEDIDS:
1184 wildcardLinkedIds = this.storedLinkedIds;
1189 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1190 // to storedLinkedIds
1191 if (linkedId != null && wildcardLinkedIds == null
1192 && a.hasOption(Opt.STORED)
1193 && !storedLinkedIds.contains(linkedId))
1195 storedLinkedIds.add(linkedId);
1198 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1200 if (wildcardLinkedIds != null)
1202 for (String id : wildcardLinkedIds)
1204 // skip incorrectly stored wildcard ids!
1205 if (id == null || MATCHALLLINKEDIDS.equals(id)
1206 || MATCHOPENEDLINKEDIDS.equals(id))
1208 ArgValuesMap avm = linkedArgs.get(id);
1209 // don't set an output if there isn't an input
1210 if (a.hasOption(Opt.REQUIREINPUT)
1211 && !avm.hasArgWithOption(Opt.INPUT))
1214 ArgValues tavs = avm.getOrCreateArgValues(a);
1224 sv = new SubVals(sv, val, merge);
1225 val = makeSubstitutions(sv.getContent(), id);
1227 tavs.addValue(sv, type, val, argIndex, true);
1233 val = makeSubstitutions(v, id);
1235 tavs.addValue(type, val, argIndex, true);
1237 finaliseStoringArgValue(id, tavs);
1241 tavs.setBoolean(type, b, argIndex, true);
1242 finaliseStoringArgValue(id, tavs);
1246 tavs.setNegated(b, true);
1249 case INCREMENTCOUNT:
1250 tavs.incrementCount();
1260 else // no wildcard linkedId -- do it simpler
1270 val = makeSubstitutions(v, linkedId);
1271 sv = new SubVals(sv, val);
1273 avs.addValue(sv, type, val, argIndex, false);
1279 val = makeSubstitutions(v, linkedId);
1281 avs.addValue(type, val, argIndex, false);
1283 finaliseStoringArgValue(linkedId, avs);
1287 avs.setBoolean(type, b, argIndex, false);
1288 finaliseStoringArgValue(linkedId, avs);
1292 avs.setNegated(b, false);
1295 case INCREMENTCOUNT:
1296 avs.incrementCount();
1305 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1307 if (linkedArgs.containsKey(linkedId)
1308 && linkedArgs.get(linkedId) != null)
1309 return linkedArgs.get(linkedId);
1311 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1312 return linkedArgs.get(linkedId);