2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.bin.argparser;
24 import java.io.IOException;
25 import java.nio.file.Files;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.EnumSet;
31 import java.util.Enumeration;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Locale;
37 import jalview.bin.Cache;
38 import jalview.bin.Console;
39 import jalview.bin.Jalview;
40 import jalview.bin.argparser.Arg.Opt;
41 import jalview.bin.argparser.Arg.Type;
42 import jalview.util.FileUtils;
43 import jalview.util.HttpUtils;
45 public class ArgParser
47 protected static final String SINGLEDASH = "-";
49 protected static final String DOUBLEDASH = "--";
51 public static final char EQUALS = '=';
53 public static final String STDOUTFILENAME = "-";
55 protected static final String NEGATESTRING = "no";
58 * the default linked id prefix used for no id (ie when not even square braces
61 protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
64 * the linkedId string used to match all linkedIds seen so far
66 protected static final String MATCHALLLINKEDIDS = "*";
69 * the linkedId string used to match all of the last --open'ed linkedIds
71 protected static final String MATCHOPENEDLINKEDIDS = "open*";
74 * the counter added to the default linked id prefix
76 private int defaultLinkedIdCounter = 0;
79 * the substitution string used to use the defaultLinkedIdCounter
81 private static final String DEFAULTLINKEDIDCOUNTER = "{}";
84 * the linked id prefix used for --open files. NOW the same as DEFAULT
86 protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
89 * the counter used for {n} substitutions
91 private int linkedIdAutoCounter = 0;
94 * the linked id substitution string used to increment the idCounter (and use
95 * the incremented value)
97 private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
100 * the linked id substitution string used to use the idCounter
102 private static final String LINKEDIDAUTOCOUNTER = "{n}";
105 * the linked id substitution string used to use the filename extension of
108 private static final String LINKEDIDEXTENSION = "{extension}";
111 * the linked id substitution string used to use the base filename of --append
114 private static final String LINKEDIDBASENAME = "{basename}";
117 * the linked id substitution string used to use the dir path of --append or
120 private static final String LINKEDIDDIRNAME = "{dirname}";
123 * the current argfile
125 private String argFile = null;
128 * the linked id substitution string used to use the dir path of the latest
130 /** --argfile name */
131 private static final String ARGFILEBASENAME = "{argfilebasename}";
134 * the linked id substitution string used to use the dir path of the latest
137 private static final String ARGFILEDIRNAME = "{argfiledirname}";
140 * flag to say whether {n} subtitutions in output filenames should be made.
141 * Turn on and off with --substitutions and --nosubstitutions Start with it on
143 private boolean substitutions = true;
146 * flag to say whether the default linkedId is the current default linked id
150 private boolean allLinkedIds = false;
153 * flag to say whether the default linkedId is the current default linked id
154 * or OPENED linkedIds
156 private boolean openedLinkedIds = false;
159 * flag to say whether the structure arguments should be applied to all
160 * structures with this linked id
162 private boolean allStructures = false;
164 protected static final Map<String, Arg> argMap;
166 protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
168 protected List<String> linkedOrder = new ArrayList<>();
170 protected List<String> storedLinkedIds = new ArrayList<>();
172 protected List<Arg> argList = new ArrayList<>();
174 private static final char ARGFILECOMMENT = '#';
176 private int argIndex = 0;
178 private BootstrapArgs bootstrapArgs = null;
182 argMap = new HashMap<>();
183 for (Arg a : EnumSet.allOf(Arg.class))
185 for (String argName : a.getNames())
187 if (argMap.containsKey(argName))
189 Console.warn("Trying to add argument name multiple times: '"
191 if (argMap.get(argName) != a)
194 "Trying to add argument name multiple times for different Args: '"
195 + argMap.get(argName).getName() + ":" + argName
196 + "' and '" + a.getName() + ":" + argName
201 argMap.put(argName, a);
206 public ArgParser(String[] args)
208 this(args, false, null);
211 public ArgParser(String[] args, boolean initsubstitutions,
215 * Make a mutable new ArrayList so that shell globbing parser works.
216 * (When shell file globbing is used, there are a sequence of non-Arg
217 * arguments (which are the expanded globbed filenames) that need to be
218 * consumed by the --append/--argfile/etc Arg which is most easily done
219 * by removing these filenames from the list one at a time. This can't be
220 * done with an ArrayList made with only Arrays.asList(String[] args) as
221 * that is not mutable. )
223 this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
227 public ArgParser(List<String> args, boolean initsubstitutions)
229 this(args, initsubstitutions, false, null);
232 public ArgParser(List<String> args, boolean initsubstitutions,
233 boolean allowPrivate, BootstrapArgs bsa)
235 // do nothing if there are no "--" args and (some "-" args || >0 arg is
239 for (String arg : args)
241 if (arg.startsWith(DOUBLEDASH))
246 else if (arg.startsWith("-") || arg.equals("open"))
253 // leave it to the old style -- parse an empty list
254 parse(new ArrayList<String>(), false, false);
258 this.bootstrapArgs = bsa;
260 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
261 parse(args, initsubstitutions, allowPrivate);
264 private void parse(List<String> args, boolean initsubstitutions,
265 boolean allowPrivate)
267 this.substitutions = initsubstitutions;
270 * If the first argument does not start with "--" or "-" or is not "open",
271 * and is a filename that exists or a URL, it is probably a file/list of
272 * files to open so we insert an Arg.OPEN argument before it. This will
273 * mean the list of files at the start of the arguments are all opened
278 String arg0 = args.get(0);
280 && (!arg0.startsWith(DOUBLEDASH) && !arg0.startsWith("-")
281 && !arg0.equals("open") && (new File(arg0).exists()
282 || HttpUtils.startsWithHttpOrHttps(arg0))))
284 // insert "--open" at the start
285 args.add(0, Arg.OPEN.argString());
289 for (int i = 0; i < args.size(); i++)
291 String arg = args.get(i);
293 // look for double-dash, e.g. --arg
294 if (arg.startsWith(DOUBLEDASH))
296 String argName = null;
298 List<String> globVals = null; // for Opt.GLOB only
299 SubVals globSubVals = null; // also for use by Opt.GLOB only
300 String linkedId = null;
303 // look for equals e.g. --arg=value
304 int equalPos = arg.indexOf(EQUALS);
307 argName = arg.substring(DOUBLEDASH.length(), equalPos);
308 val = arg.substring(equalPos + 1);
312 argName = arg.substring(DOUBLEDASH.length());
315 // look for linked ID e.g. --arg[linkedID]
316 int idOpen = argName.indexOf('[');
317 int idClose = argName.indexOf(']');
318 if (idOpen > -1 && idClose == argName.length() - 1)
320 linkedId = argName.substring(idOpen + 1, idClose);
321 argName = argName.substring(0, idOpen);
324 // look for type modification e.g. --help-opening
325 int dashPos = argName.indexOf(SINGLEDASH);
328 String potentialArgName = argName.substring(0, dashPos);
329 Arg potentialArg = argMap.get(potentialArgName);
330 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
332 String typeName = argName.substring(dashPos + 1);
335 type = Type.valueOf(typeName);
336 } catch (IllegalArgumentException e)
340 argName = argName.substring(0, dashPos);
344 Arg a = argMap.get(argName);
345 // check for boolean prepended by "no" e.g. --nowrap
346 boolean negated = false;
349 if (argName.startsWith(NEGATESTRING) && argMap
350 .containsKey(argName.substring(NEGATESTRING.length())))
352 argName = argName.substring(NEGATESTRING.length());
353 a = argMap.get(argName);
358 // after all other args, look for Opt.PREFIXKEV args if still not
360 for (Arg potentialArg : EnumSet.allOf(Arg.class))
362 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
363 && argName.startsWith(potentialArg.getName())
366 val = argName.substring(potentialArg.getName().length())
368 argName = argName.substring(0,
369 potentialArg.getName().length());
377 // check for config errors
381 Console.error("Argument '" + arg + "' not recognised. Exiting.");
382 Jalview.exit("Invalid argument used." + System.lineSeparator()
383 + "Use" + System.lineSeparator() + "jalview "
384 + Arg.HELP.argString() + System.lineSeparator()
385 + "for a usage statement.", 13);
388 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
391 "Argument '" + a.argString() + "' is private. Ignoring.");
394 if (!a.hasOption(Opt.BOOLEAN) && negated)
396 // used "no" with a non-boolean option
397 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
398 + "' not a boolean option. Ignoring.");
401 if (!a.hasOption(Opt.STRING) && equalPos > -1)
403 // set --argname=value when arg does not accept values
404 Console.error("Argument '" + a.argString()
405 + "' does not expect a value (given as '" + arg
409 if (!a.hasOption(Opt.LINKED) && linkedId != null)
411 // set --argname[linkedId] when arg does not use linkedIds
412 Console.error("Argument '" + a.argString()
413 + "' does not expect a linked id (given as '" + arg
419 if (a.hasOption(Opt.STRING))
423 if (a.hasOption(Opt.GLOB))
425 // strip off and save the SubVals to be added individually later
426 globSubVals = new SubVals(val);
427 // make substitutions before looking for files
428 String fileGlob = makeSubstitutions(globSubVals.getContent(),
430 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
434 // val is already set -- will be saved in the ArgValue later in
440 // There is no "=" so value is next arg or args (possibly shell
442 if (i + 1 >= args.size())
444 // no value to take for arg, which wants a value
445 Console.error("Argument '" + a.getName()
446 + "' requires a value, none given. Ignoring.");
449 // deal with bash globs here (--arg val* is expanded before reaching
450 // the JVM). Note that SubVals cannot be used in this case.
451 // If using the --arg=val then the glob is preserved and Java globs
452 // will be used later. SubVals can be used.
453 if (a.hasOption(Opt.GLOB))
455 // if this is the first argument with a file list at the start of
456 // the args we add filenames from index i instead of i+1
457 globVals = getShellGlobbedFilenameValues(a, args, i + 1);
461 val = args.get(i + 1);
466 // make NOACTION adjustments
467 // default and auto counter increments
470 linkedIdAutoCounter++;
472 else if (a == Arg.SUBSTITUTIONS)
474 substitutions = !negated;
476 else if (a == Arg.SETARGFILE)
480 else if (a == Arg.UNSETARGFILE)
484 else if (a == Arg.ALL)
486 allLinkedIds = !negated;
487 openedLinkedIds = false;
489 else if (a == Arg.OPENED)
491 openedLinkedIds = !negated;
492 allLinkedIds = false;
494 else if (a == Arg.ALLSTRUCTURES)
496 allStructures = !negated;
499 if (a.hasOption(Opt.STORED))
501 // reset the lastOpenedLinkedIds list
502 this.storedLinkedIds = new ArrayList<>();
505 // this is probably only Arg.NEW and Arg.OPEN
506 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
508 // use the next default prefixed OPENLINKEDID
509 defaultLinkedId(true);
512 String autoCounterString = null;
513 String defaultLinkedId = defaultLinkedId(false);
514 boolean usingDefaultLinkedId = false;
515 if (a.hasOption(Opt.LINKED))
517 if (linkedId == null)
519 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWALL)
520 && val.startsWith(MATCHALLLINKEDIDS))
522 // --output=*.ext is shorthand for --all --output {basename}.ext
523 // (or --image=*.ext)
525 openedLinkedIds = false;
526 linkedId = MATCHALLLINKEDIDS;
527 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
528 + val.substring(MATCHALLLINKEDIDS.length());
530 else if (a.hasOption(Opt.OUTPUTFILE)
531 && a.hasOption(Opt.ALLOWALL)
532 && val.startsWith(MATCHOPENEDLINKEDIDS))
534 // --output=open*.ext is shorthand for --opened --output
536 // (or --image=open*.ext)
537 openedLinkedIds = true;
538 allLinkedIds = false;
539 linkedId = MATCHOPENEDLINKEDIDS;
540 val = LINKEDIDDIRNAME + File.separator + LINKEDIDBASENAME
541 + val.substring(MATCHOPENEDLINKEDIDS.length());
543 else if (allLinkedIds && a.hasOption(Opt.ALLOWALL))
545 linkedId = MATCHALLLINKEDIDS;
547 else if (openedLinkedIds && a.hasOption(Opt.ALLOWALL))
549 linkedId = MATCHOPENEDLINKEDIDS;
553 // use default linkedId for linked arguments
554 linkedId = defaultLinkedId;
555 usingDefaultLinkedId = true;
556 Console.debug("Changing linkedId to '" + linkedId + "' from "
562 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
564 // turn {n} to the autoCounter
565 autoCounterString = Integer.toString(linkedIdAutoCounter);
566 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
568 Console.debug("Changing linkedId to '" + linkedId + "' from "
571 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
573 // turn {++n} to the incremented autoCounter
574 autoCounterString = Integer.toString(++linkedIdAutoCounter);
575 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
577 Console.debug("Changing linkedId to '" + linkedId + "' from "
583 // do not continue in this block for NOACTION args
584 if (a.hasOption(Opt.NOACTION))
587 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
589 // not dealing with both NODUPLICATEVALUES and GLOB
590 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
592 Console.error("Argument '" + a.argString()
593 + "' cannot contain a duplicate value ('" + val
594 + "'). Ignoring this and subsequent occurrences.");
598 // check for unique id
599 SubVals subvals = new SubVals(val);
600 boolean addNewSubVals = false;
601 String id = subvals.get(ArgValues.ID);
602 if (id != null && avm.hasId(a, id))
604 Console.error("Argument '" + a.argString()
605 + "' has a duplicate id ('" + id + "'). Ignoring.");
609 // set allstructures to all non-primary structure options in this linked
610 // id if --allstructures has been set
612 && (a.getType() == Type.STRUCTURE
613 || a.getType() == Type.STRUCTUREIMAGE)
614 && !a.hasOption(Opt.PRIMARY))
616 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
617 // && !subvals.has("structureid"))
619 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
620 addNewSubVals = true;
624 ArgValues avs = avm.getOrCreateArgValues(a);
626 // store appropriate String value(s)
627 if (a.hasOption(Opt.STRING))
629 if (a.hasOption(Opt.GLOB) && globVals != null
630 && globVals.size() > 0)
632 Enumeration<String> gve = Collections.enumeration(globVals);
633 while (gve.hasMoreElements())
635 String v = gve.nextElement();
636 SubVals vsv = new SubVals(globSubVals, v);
637 addValue(linkedId, type, avs, vsv, v, argIndex++, true);
638 // if we're using defaultLinkedId and the arg increments the
640 if (gve.hasMoreElements() && usingDefaultLinkedId
641 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
643 // increment the default linkedId
644 linkedId = defaultLinkedId(true);
645 // get new avm and avs
646 avm = linkedArgs.get(linkedId);
647 avs = avm.getOrCreateArgValues(a);
653 // addValue(linkedId, type, avs, val, argIndex, true);
654 addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
655 val, argIndex, true);
658 else if (a.hasOption(Opt.BOOLEAN))
660 setBoolean(linkedId, type, avs, !negated, argIndex);
661 setNegated(linkedId, avs, negated);
663 else if (a.hasOption(Opt.UNARY))
665 setBoolean(linkedId, type, avs, true, argIndex);
668 // remove the '*' or 'open*' linkedId that should be empty if it was
670 if ((MATCHALLLINKEDIDS.equals(linkedId)
671 && linkedArgs.containsKey(linkedId))
672 || (MATCHOPENEDLINKEDIDS.equals(linkedId)
673 && linkedArgs.containsKey(linkedId)))
675 linkedArgs.remove(linkedId);
681 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
684 incrementCount(linkedId, avs);
687 // store in appropriate place
688 if (a.hasOption(Opt.LINKED))
690 // store the order of linkedIds
691 if (!linkedOrder.contains(linkedId))
692 linkedOrder.add(linkedId);
695 // store arg in the list of args used
696 if (!argList.contains(a))
700 private String defaultLinkedId(boolean increment)
702 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
703 .append(Integer.toString(defaultLinkedIdCounter)).toString();
706 while (linkedArgs.containsKey(defaultLinkedId))
708 defaultLinkedIdCounter++;
709 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
710 .append(Integer.toString(defaultLinkedIdCounter))
714 getOrCreateLinkedArgValuesMap(defaultLinkedId);
715 return defaultLinkedId;
718 public String makeSubstitutions(String val, String linkedId)
720 if (!this.substitutions || val == null)
725 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
727 int closeBracket = val.indexOf(']');
728 if (val.length() == closeBracket)
730 subvals = val.substring(0, closeBracket + 1);
731 rest = val.substring(closeBracket + 1);
738 if (rest.contains(LINKEDIDAUTOCOUNTER))
739 rest = rest.replace(LINKEDIDAUTOCOUNTER,
740 String.valueOf(linkedIdAutoCounter));
741 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
742 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
743 String.valueOf(++linkedIdAutoCounter));
744 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
745 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
746 String.valueOf(defaultLinkedIdCounter));
747 ArgValuesMap avm = linkedArgs.get(linkedId);
750 if (rest.contains(LINKEDIDBASENAME))
752 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
754 if (rest.contains(LINKEDIDEXTENSION))
756 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
758 if (rest.contains(LINKEDIDDIRNAME))
760 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
765 if (rest.contains(ARGFILEBASENAME))
767 rest = rest.replace(ARGFILEBASENAME,
768 FileUtils.getBasename(new File(argFile)));
770 if (rest.contains(ARGFILEDIRNAME))
772 rest = rest.replace(ARGFILEDIRNAME,
773 FileUtils.getDirname(new File(argFile)));
777 return new StringBuilder(subvals).append(rest).toString();
781 * A helper method to take a list of String args where we're expecting
782 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
783 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
784 * "file2", "file3"} *and remove these from the original list object* so that
785 * processing can continue from where it has left off, e.g. args has become
786 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
787 * carries on from the next --arg if available.
789 protected static List<String> getShellGlobbedFilenameValues(Arg a,
790 List<String> args, int i)
792 List<String> vals = new ArrayList<>();
793 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
795 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
796 if (!a.hasOption(Opt.GLOB))
802 public BootstrapArgs getBootstrapArgs()
804 return bootstrapArgs;
807 public boolean isSet(Arg a)
809 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
812 public boolean isSetAtAll(Arg a)
814 for (String linkedId : linkedOrder)
816 if (isSet(linkedId, a))
822 public boolean isSet(String linkedId, Arg a)
824 ArgValuesMap avm = linkedArgs.get(linkedId);
825 return avm == null ? false : avm.containsArg(a);
828 public boolean getBoolean(Arg a)
830 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
832 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
835 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
838 public boolean getBool(String linkedId, Arg a)
840 ArgValuesMap avm = linkedArgs.get(linkedId);
842 return a.getDefaultBoolValue();
843 ArgValues avs = avm.getArgValues(a);
844 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
847 public List<String> getLinkedIds()
852 public ArgValuesMap getLinkedArgs(String id)
854 return linkedArgs.get(id);
858 public String toString()
860 StringBuilder sb = new StringBuilder();
861 sb.append("UNLINKED\n");
862 sb.append(argValuesMapToString(linkedArgs.get(null)));
863 if (getLinkedIds() != null)
865 sb.append("LINKED\n");
866 for (String id : getLinkedIds())
868 // already listed these as UNLINKED args
872 ArgValuesMap avm = getLinkedArgs(id);
873 sb.append("ID: '").append(id).append("'\n");
874 sb.append(argValuesMapToString(avm));
877 return sb.toString();
880 private static String argValuesMapToString(ArgValuesMap avm)
884 StringBuilder sb = new StringBuilder();
885 for (Arg a : avm.getArgKeys())
887 ArgValues v = avm.getArgValues(a);
888 sb.append(v.toString());
891 return sb.toString();
894 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
895 boolean initsubstitutions, BootstrapArgs bsa)
897 List<File> argFiles = new ArrayList<>();
899 for (String pattern : argFilenameGlobs)
901 // I don't think we want to dedup files, making life easier
902 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
905 return parseArgFileList(argFiles, initsubstitutions, bsa);
908 public static ArgParser parseArgFileList(List<File> argFiles,
909 boolean initsubstitutions, BootstrapArgs bsa)
911 List<String> argsList = new ArrayList<>();
912 for (File argFile : argFiles)
914 if (!argFile.exists())
916 String message = Arg.ARGFILE.argString() + EQUALS + "\""
917 + argFile.getPath() + "\": File does not exist.";
918 Jalview.exit(message, 2);
922 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
923 .append(EQUALS).append(argFile.getCanonicalPath())
925 argsList.add(setargfile);
926 argsList.addAll(readArgFile(argFile));
927 argsList.add(Arg.UNSETARGFILE.argString());
928 } catch (IOException e)
930 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
931 + "\": File could not be read.";
932 Jalview.exit(message, 3);
935 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
937 return new ArgParser(argsList, initsubstitutions, true, bsa);
940 protected static List<String> readArgFile(File argFile)
942 List<String> args = new ArrayList<>();
943 if (argFile != null && argFile.exists())
947 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
949 if (line != null && line.length() > 0
950 && line.charAt(0) != ARGFILECOMMENT)
953 } catch (IOException e)
955 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
956 + "\": File could not be read.";
957 Console.debug(message, e);
958 Jalview.exit(message, 3);
964 public static enum Position
970 * get from following Arg of type a or subval of same name (lowercase)
972 public static String getValueFromSubValOrArg(ArgValuesMap avm,
973 ArgValue av, Arg a, SubVals sv)
975 return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
979 * get from following Arg of type a or subval key or preference pref or
982 public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
983 Arg a, SubVals sv, String key, String pref, String def)
985 return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref,
990 * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
991 * Arg of type a or subval key or preference pref or default def
993 public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
994 Position pos, ArgValue av, SubVals sv, String key, String pref,
997 return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
1001 public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
1002 ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
1003 String key, String pref, String def)
1007 String value = null;
1008 if (sv != null && sv.has(key) && sv.get(key) != null)
1010 value = ap == null ? sv.get(key)
1011 : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
1013 else if (avm != null && avm.containsArg(a))
1015 if (pos == Position.FIRST && avm.getValue(a) != null)
1016 value = avm.getValue(a);
1017 else if (pos == Position.BEFORE
1018 && avm.getClosestPreviousArgValueOfArg(av, a) != null)
1019 value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
1020 else if (pos == Position.AFTER
1021 && avm.getClosestNextArgValueOfArg(av, a) != null)
1022 value = avm.getClosestNextArgValueOfArg(av, a).getValue();
1024 // look for allstructures subval for Type.STRUCTURE*
1025 Arg arg = av.getArg();
1026 if (value == null && arg.hasOption(Opt.PRIMARY)
1027 && arg.getType() == Type.STRUCTURE
1028 && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
1029 || a.getType() == Type.STRUCTUREIMAGE))
1031 ArgValue av2 = avm.getArgValueOfArgWithSubValKey(a,
1032 Arg.ALLSTRUCTURES.getName());
1035 value = av2.getValue();
1041 value = pref != null ? Cache.getDefault(pref, def) : def;
1046 public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
1049 return getFromSubValArgOrPref(avm, a, sv, null, null, false);
1052 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1053 SubVals sv, String key, String pref, boolean def)
1055 return getFromSubValArgOrPref(avm, a, sv, key, pref, def, false);
1058 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1059 SubVals sv, String key, String pref, boolean def,
1062 if ((key == null && a == null) || (sv == null && a == null))
1065 boolean usingArgKey = false;
1072 String nokey = ArgParser.NEGATESTRING + key;
1074 // look for key or nokey in subvals first (if using Arg check options)
1077 // check for true boolean
1078 if (sv.has(key) && sv.get(key) != null)
1082 if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
1085 "Looking for boolean in subval from non-boolean/non-unary Arg "
1090 return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
1093 // check for negative boolean (subval "no..." will be "true")
1094 if (sv.has(nokey) && sv.get(nokey) != null)
1098 if (!(a.hasOption(Opt.BOOLEAN)))
1101 "Looking for negative boolean in subval from non-boolean Arg "
1106 return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
1111 if (avm != null && avm.containsArg(a))
1112 return avm.getBoolean(a);
1114 // return preference or default
1115 boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
1116 return pref != null ? (invertPref ? !prefVal : prefVal) : def;
1119 // the following methods look for the "*" linkedId and add the argvalue to all
1120 // linkedId ArgValues if it does.
1122 * This version inserts the subvals sv into all created values
1124 private void addValue(String linkedId, Type type, ArgValues avs,
1125 SubVals sv, String v, int argIndex, boolean doSubs)
1127 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
1131 private void addValue(String linkedId, Type type, ArgValues avs, String v,
1132 int argIndex, boolean doSubs)
1134 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, null, v, false,
1138 private void setBoolean(String linkedId, Type type, ArgValues avs,
1139 boolean b, int argIndex)
1141 this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
1142 b, argIndex, false);
1145 private void setNegated(String linkedId, ArgValues avs, boolean b)
1147 this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
1151 private void incrementCount(String linkedId, ArgValues avs)
1153 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
1154 null, false, 0, false);
1159 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1162 private void argValueOperation(Op op, String linkedId, Type type,
1163 ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
1166 // default to merge subvals if subvals are provided
1167 argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
1172 * The following operations look for the "*" and "open*" linkedIds and add the
1173 * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1174 * supplied, they are inserted into all new set values.
1177 * The ArgParser.Op operation
1179 * The String linkedId from the ArgValuesMap
1181 * The Arg.Type to attach to this ArgValue
1183 * The ArgValues for this linkedId
1185 * Use these SubVals on the ArgValue
1187 * Merge the SubVals with any existing on the value. False will
1188 * replace unless sv is null
1190 * The value of the ArgValue (may contain subvals).
1192 * The boolean value of the ArgValue.
1194 * The argIndex for the ArgValue.
1196 * Whether to perform substitutions on the subvals and value.
1198 private void argValueOperation(Op op, String linkedId, Type type,
1199 ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
1200 int argIndex, boolean doSubs)
1204 List<String> wildcardLinkedIds = null;
1205 if (a.hasOption(Opt.ALLOWALL))
1209 case MATCHALLLINKEDIDS:
1210 wildcardLinkedIds = getLinkedIds();
1212 case MATCHOPENEDLINKEDIDS:
1213 wildcardLinkedIds = this.storedLinkedIds;
1218 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1219 // to storedLinkedIds
1220 if (linkedId != null && wildcardLinkedIds == null
1221 && a.hasOption(Opt.STORED)
1222 && !storedLinkedIds.contains(linkedId))
1224 storedLinkedIds.add(linkedId);
1227 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1229 if (wildcardLinkedIds != null)
1231 for (String id : wildcardLinkedIds)
1233 // skip incorrectly stored wildcard ids!
1234 if (id == null || MATCHALLLINKEDIDS.equals(id)
1235 || MATCHOPENEDLINKEDIDS.equals(id))
1237 ArgValuesMap avm = linkedArgs.get(id);
1238 // don't set an output if there isn't an input
1239 if (a.hasOption(Opt.REQUIREINPUT)
1240 && !avm.hasArgWithOption(Opt.INPUT))
1243 ArgValues tavs = avm.getOrCreateArgValues(a);
1253 sv = new SubVals(sv, val, merge);
1254 val = makeSubstitutions(sv.getContent(), id);
1256 tavs.addValue(sv, type, val, argIndex, true);
1262 val = makeSubstitutions(v, id);
1264 tavs.addValue(type, val, argIndex, true);
1266 finaliseStoringArgValue(id, tavs);
1270 tavs.setBoolean(type, b, argIndex, true);
1271 finaliseStoringArgValue(id, tavs);
1275 tavs.setNegated(b, true);
1278 case INCREMENTCOUNT:
1279 tavs.incrementCount();
1289 else // no wildcard linkedId -- do it simpler
1299 val = makeSubstitutions(v, linkedId);
1300 sv = new SubVals(sv, val);
1302 avs.addValue(sv, type, val, argIndex, false);
1308 val = makeSubstitutions(v, linkedId);
1310 avs.addValue(type, val, argIndex, false);
1312 finaliseStoringArgValue(linkedId, avs);
1316 avs.setBoolean(type, b, argIndex, false);
1317 finaliseStoringArgValue(linkedId, avs);
1321 avs.setNegated(b, false);
1324 case INCREMENTCOUNT:
1325 avs.incrementCount();
1334 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1336 if (linkedArgs.containsKey(linkedId)
1337 && linkedArgs.get(linkedId) != null)
1338 return linkedArgs.get(linkedId);
1340 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1341 return linkedArgs.get(linkedId);