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;
36 import jalview.bin.Console;
37 import jalview.bin.Jalview;
38 import jalview.bin.Jalview.ExitCode;
39 import jalview.bin.argparser.Arg.Opt;
40 import jalview.bin.argparser.Arg.Type;
41 import jalview.util.FileUtils;
42 import jalview.util.HttpUtils;
44 public class ArgParser
46 protected static final String SINGLEDASH = "-";
48 protected static final String DOUBLEDASH = "--";
50 public static final char EQUALS = '=';
52 public static final String STDOUTFILENAME = "-";
54 protected static final String NEGATESTRING = "no";
57 * the default linked id prefix used for no id (ie when not even square braces
60 protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
63 * the linkedId string used to match all linkedIds seen so far
65 protected static final String MATCHALLLINKEDIDS = "*";
68 * the linkedId string used to match all of the last --open'ed linkedIds
70 protected static final String MATCHOPENEDLINKEDIDS = "open*";
73 * the counter added to the default linked id prefix
75 private int defaultLinkedIdCounter = 0;
78 * the substitution string used to use the defaultLinkedIdCounter
80 private static final String DEFAULTLINKEDIDCOUNTER = "{}";
83 * the linked id prefix used for --open files. NOW the same as DEFAULT
85 protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
88 * the counter used for {n} substitutions
90 private int linkedIdAutoCounter = 0;
93 * the linked id substitution string used to increment the idCounter (and use
94 * the incremented value)
96 private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
99 * the linked id substitution string used to use the idCounter
101 private static final String LINKEDIDAUTOCOUNTER = "{n}";
104 * the linked id substitution string used to use the filename extension of
107 private static final String LINKEDIDEXTENSION = "{extension}";
110 * the linked id substitution string used to use the base filename of --append
113 private static final String LINKEDIDBASENAME = "{basename}";
116 * the linked id substitution string used to use the dir path of --append or
119 private static final String LINKEDIDDIRNAME = "{dirname}";
122 * On-the-fly substitution (not made at argument parsing time)! the current
123 * structure filename extension
125 private static final String STRUCTUREEXTENSION = "{structureextension}";
128 * On-the-fly substitution (not made at argument parsing time)! the current
129 * structure filename base
131 private static final String STRUCTUREBASENAME = "{structurebasename}";
134 * On-the-fly substitution (not made at argument parsing time)! the current
135 * structure filename dir path
137 private static final String STRUCTUREDIRNAME = "{structuredirname}";
140 * On-the-fly substitution (not made at argument parsing time)! increment the
141 * on-the-fly counter and substitute the incremented value
143 private static final String INCREMENTONTHEFLYCOUNTER = "{++m}";
146 * On-the-fly substitution (not made at argument parsing time)! the current
147 * substitute with the on-the-fly counter
149 private static final String ONTHEFLYCOUNTER = "{m}";
152 * the string used for on-the-fly structure filename substitutions
154 private String currentStructureFilename = null;
157 * the counter used for on-the-fly {m} substitutions
159 private int ontheflyCounter = 0;
162 * the current argfile
164 private String argFile = null;
167 * the linked id substitution string used to use the dir path of the latest
169 /** --argfile name */
170 private static final String ARGFILEBASENAME = "{argfilebasename}";
173 * the linked id substitution string used to use the dir path of the latest
176 private static final String ARGFILEDIRNAME = "{argfiledirname}";
179 * flag to say whether {n} subtitutions in output filenames should be made.
180 * Turn on and off with --substitutions and --nosubstitutions Start with it on
182 private boolean substitutions = true;
185 * flag to say whether the default linkedId is the current default linked id
189 private boolean allLinkedIds = false;
192 * flag to say whether the structure arguments should be applied to all
193 * structures with this linked id
195 private boolean allStructures = false;
198 * flag to say whether to ignore or reject non-string values args with a value
201 * Default is false (i.e. reject non-string args that have a value. It is set
202 * to true for JalviewJS in Platform.getURLCommandArguments().
204 private static boolean ignoreNonStringValues = false;
206 protected static final Map<String, Arg> argMap;
208 protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
210 protected List<String> linkedOrder = new ArrayList<>();
212 protected List<String> storedLinkedIds = new ArrayList<>();
214 protected List<Arg> argList = new ArrayList<>();
216 private static final char ARGFILECOMMENT = '#';
218 private int argIndex = 0;
220 private BootstrapArgs bootstrapArgs = null;
222 private boolean oldArguments = false;
224 private boolean mixedArguments = false;
227 * saved examples of mixed arguments
229 private String[] mixedExamples = new String[] { null, null };
233 argMap = new HashMap<>();
234 for (Arg a : EnumSet.allOf(Arg.class))
236 for (String argName : a.getNames())
238 if (argMap.containsKey(argName))
240 Console.warn("Trying to add argument name multiple times: '"
242 if (argMap.get(argName) != a)
245 "Trying to add argument name multiple times for different Args: '"
246 + argMap.get(argName).getName() + ":" + argName
247 + "' and '" + a.getName() + ":" + argName
252 argMap.put(argName, a);
257 public ArgParser(String[] args)
259 this(args, false, null);
262 public ArgParser(String[] args, boolean initsubstitutions,
266 * Make a mutable new ArrayList so that shell globbing parser works.
267 * (When shell file globbing is used, there are a sequence of non-Arg
268 * arguments (which are the expanded globbed filenames) that need to be
269 * consumed by the --append/--argfile/etc Arg which is most easily done
270 * by removing these filenames from the list one at a time. This can't be
271 * done with an ArrayList made with only Arrays.asList(String[] args) as
272 * that is not mutable. )
274 this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
278 public ArgParser(List<String> args, boolean initsubstitutions)
280 this(args, initsubstitutions, false, null);
283 public ArgParser(List<String> args, boolean initsubstitutions,
284 boolean allowPrivate, BootstrapArgs bsa)
286 // do nothing if there are no "--" args and (some "-" args || >0 arg is
290 for (String arg : args)
292 if (arg.startsWith(DOUBLEDASH))
295 if (mixedExamples[1] == null)
297 mixedExamples[1] = arg;
300 else if ((arg.startsWith("-") && !arg.equals(STDOUTFILENAME))
301 || arg.equals("open"))
304 if (mixedExamples[0] == null)
306 mixedExamples[0] = arg;
314 mixedArguments = true;
322 if (oldArguments || mixedArguments)
324 // leave it to the old style -- parse an empty list
325 parse(new ArrayList<String>(), false, false);
331 this.bootstrapArgs = bsa;
335 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
337 parse(args, initsubstitutions, allowPrivate);
340 private void parse(List<String> args, boolean initsubstitutions,
341 boolean allowPrivate)
343 this.substitutions = initsubstitutions;
346 * If the first argument does not start with "--" or "-" or is not "open",
347 * and is a filename that exists or a URL, it is probably a file/list of
348 * files to open so we insert an Arg.OPEN argument before it. This will
349 * mean the list of files at the start of the arguments are all opened
354 String arg0 = args.get(0);
356 && (!arg0.startsWith(DOUBLEDASH) && !arg0.startsWith("-")
357 && !arg0.equals("open") && (new File(arg0).exists()
358 || HttpUtils.startsWithHttpOrHttps(arg0))))
360 // insert "--open" at the start
361 args.add(0, Arg.OPEN.argString());
365 for (int i = 0; i < args.size(); i++)
367 String arg = args.get(i);
369 // look for double-dash, e.g. --arg
370 if (arg.startsWith(DOUBLEDASH))
372 String argName = null;
374 List<String> globVals = null; // for Opt.GLOB only
375 SubVals globSubVals = null; // also for use by Opt.GLOB only
376 String linkedId = null;
379 // look for equals e.g. --arg=value
380 int equalPos = arg.indexOf(EQUALS);
383 argName = arg.substring(DOUBLEDASH.length(), equalPos);
384 val = arg.substring(equalPos + 1);
388 argName = arg.substring(DOUBLEDASH.length());
391 // look for linked ID e.g. --arg[linkedID]
392 int idOpen = argName.indexOf('[');
393 int idClose = argName.indexOf(']');
394 if (idOpen > -1 && idClose == argName.length() - 1)
396 linkedId = argName.substring(idOpen + 1, idClose);
397 argName = argName.substring(0, idOpen);
400 // look for type modification e.g. --help-opening
401 int dashPos = argName.indexOf(SINGLEDASH);
404 String potentialArgName = argName.substring(0, dashPos);
405 Arg potentialArg = argMap.get(potentialArgName);
406 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
408 String typeName = argName.substring(dashPos + 1);
411 type = Type.valueOf(typeName);
412 } catch (IllegalArgumentException e)
416 argName = argName.substring(0, dashPos);
420 Arg a = argMap.get(argName);
421 // check for boolean prepended by "no" e.g. --nowrap
422 boolean negated = false;
425 if (argName.startsWith(NEGATESTRING) && argMap
426 .containsKey(argName.substring(NEGATESTRING.length())))
428 argName = argName.substring(NEGATESTRING.length());
429 a = argMap.get(argName);
434 // after all other args, look for Opt.PREFIXKEV args if still not
436 for (Arg potentialArg : EnumSet.allOf(Arg.class))
438 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
439 && argName.startsWith(potentialArg.getName())
442 val = argName.substring(potentialArg.getName().length())
444 argName = argName.substring(0,
445 potentialArg.getName().length());
453 // check for config errors
457 Console.error("Argument '" + arg + "' not recognised. Exiting.");
459 "Invalid argument used." + System.lineSeparator() + "Use"
460 + System.lineSeparator() + "jalview "
461 + Arg.HELP.argString() + System.lineSeparator()
462 + "for a usage statement.",
463 ExitCode.INVALID_ARGUMENT);
466 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
469 "Argument '" + a.argString() + "' is private. Ignoring.");
472 if (!a.hasOption(Opt.BOOLEAN) && negated)
474 // used "no" with a non-boolean option
475 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
476 + "' not a boolean option. Ignoring.");
479 if (!a.hasOption(Opt.STRING) && equalPos > -1)
481 if (getIgnoreNonStringValues())
483 // delete equals sign and value
485 arg = arg.substring(0, equalPos);
489 // set --argname=value when arg does not accept values
490 Console.error("Argument '" + a.argString()
491 + "' does not expect a value (given as '" + arg
496 if (!a.hasOption(Opt.LINKED) && linkedId != null)
498 // set --argname[linkedId] when arg does not use linkedIds
499 Console.error("Argument '" + a.argString()
500 + "' does not expect a linked id (given as '" + arg
506 if (a.hasOption(Opt.STRING))
510 if (a.hasOption(Opt.GLOB))
512 // strip off and save the SubVals to be added individually later
513 globSubVals = new SubVals(val);
514 // make substitutions before looking for files
515 String fileGlob = makeSubstitutions(globSubVals.getContent(),
517 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
521 // val is already set -- will be saved in the ArgValue later in
527 // There is no "=" so value is next arg or args (possibly shell
529 if (i + 1 >= args.size())
531 // no value to take for arg, which wants a value
532 Console.error("Argument '" + a.getName()
533 + "' requires a value, none given. Ignoring.");
536 // deal with bash globs here (--arg val* is expanded before reaching
537 // the JVM). Note that SubVals cannot be used in this case.
538 // If using the --arg=val then the glob is preserved and Java globs
539 // will be used later. SubVals can be used.
540 if (a.hasOption(Opt.GLOB))
542 // if this is the first argument with a file list at the start of
543 // the args we add filenames from index i instead of i+1
544 globVals = getShellGlobbedFilenameValues(a, args, i + 1);
548 val = args.get(i + 1);
553 // make NOACTION adjustments
554 // default and auto counter increments
557 linkedIdAutoCounter++;
559 else if (a == Arg.SUBSTITUTIONS)
561 substitutions = !negated;
563 else if (a == Arg.SETARGFILE)
567 else if (a == Arg.UNSETARGFILE)
571 else if (a == Arg.ALL)
573 allLinkedIds = !negated;
575 else if (a == Arg.ALLSTRUCTURES)
577 allStructures = !negated;
580 if (a.hasOption(Opt.STORED))
582 // reset the lastOpenedLinkedIds list
583 this.storedLinkedIds = new ArrayList<>();
586 // this is probably only Arg.NEW and Arg.OPEN
587 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
589 // use the next default prefixed OPENLINKEDID
590 defaultLinkedId(true);
593 String autoCounterString = null;
594 String defaultLinkedId = defaultLinkedId(false);
595 boolean usingDefaultLinkedId = false;
596 if (a.hasOption(Opt.LINKED))
598 if (linkedId == null)
600 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWMULTIID)
601 && val.contains(MATCHALLLINKEDIDS))
603 // --output=*.ext is shorthand for --output {basename}.ext
604 // --output=*/*.ext is shorthand for
605 // --output {dirname}/{basename}.ext
606 // (or --image=*.ext)
607 linkedId = allLinkedIds ? MATCHALLLINKEDIDS
608 : MATCHOPENEDLINKEDIDS;
609 val = FileUtils.convertWildcardsToPath(val, MATCHALLLINKEDIDS,
610 LINKEDIDDIRNAME, LINKEDIDBASENAME);
612 else if (allLinkedIds && a.hasOption(Opt.ALLOWMULTIID))
614 linkedId = MATCHALLLINKEDIDS;
616 else if (a.hasOption(Opt.ALLOWMULTIID)
617 && this.storedLinkedIds != null
618 && this.storedLinkedIds.size() > 0)
620 linkedId = MATCHOPENEDLINKEDIDS;
624 // use default linkedId for linked arguments
625 linkedId = defaultLinkedId;
626 usingDefaultLinkedId = true;
627 Console.debug("Changing linkedId to '" + linkedId + "' from "
633 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
635 // turn {n} to the autoCounter
636 autoCounterString = Integer.toString(linkedIdAutoCounter);
637 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
639 Console.debug("Changing linkedId to '" + linkedId + "' from "
642 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
644 // turn {++n} to the incremented autoCounter
645 autoCounterString = Integer.toString(++linkedIdAutoCounter);
646 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
648 Console.debug("Changing linkedId to '" + linkedId + "' from "
654 // do not continue in this block for NOACTION args
655 if (a.hasOption(Opt.NOACTION))
658 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
660 // not dealing with both NODUPLICATEVALUES and GLOB
661 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
663 Console.error("Argument '" + a.argString()
664 + "' cannot contain a duplicate value ('" + val
665 + "'). Ignoring this and subsequent occurrences.");
669 // check for unique id
670 SubVals subvals = new SubVals(val);
671 boolean addNewSubVals = false;
672 String id = subvals.get(ArgValues.ID);
673 if (id != null && avm.hasId(a, id))
675 Console.error("Argument '" + a.argString()
676 + "' has a duplicate id ('" + id + "'). Ignoring.");
680 // set allstructures to all non-primary structure options in this linked
681 // id if --allstructures has been set
682 if (allStructures && (a.getType() == Type.STRUCTURE
683 // || a.getType() == Type.STRUCTUREIMAGE)
684 ) && !a.hasOption(Opt.PRIMARY))
686 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
687 // && !subvals.has("structureid"))
689 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
690 addNewSubVals = true;
694 ArgValues avs = avm.getOrCreateArgValues(a);
696 // store appropriate String value(s)
697 if (a.hasOption(Opt.STRING))
699 if (a.hasOption(Opt.GLOB) && globVals != null
700 && globVals.size() > 0)
702 Enumeration<String> gve = Collections.enumeration(globVals);
703 while (gve.hasMoreElements())
705 String v = gve.nextElement();
706 SubVals vsv = new SubVals(globSubVals, v);
707 addValue(linkedId, type, avs, vsv, v, argIndex++, true);
708 // if we're using defaultLinkedId and the arg increments the
710 if (gve.hasMoreElements() && usingDefaultLinkedId
711 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
713 // increment the default linkedId
714 linkedId = defaultLinkedId(true);
715 // get new avm and avs
716 avm = linkedArgs.get(linkedId);
717 avs = avm.getOrCreateArgValues(a);
723 // addValue(linkedId, type, avs, val, argIndex, true);
724 addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
725 val, argIndex, true);
728 else if (a.hasOption(Opt.BOOLEAN))
730 setBoolean(linkedId, type, avs, !negated, argIndex);
731 setNegated(linkedId, avs, negated);
733 else if (a.hasOption(Opt.UNARY))
735 setBoolean(linkedId, type, avs, true, argIndex);
738 // remove the '*' or 'open*' linkedId that should be empty if it was
740 if ((MATCHALLLINKEDIDS.equals(linkedId)
741 || MATCHOPENEDLINKEDIDS.equals(linkedId))
742 && linkedArgs.containsKey(linkedId))
744 linkedArgs.remove(linkedId);
750 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
753 incrementCount(linkedId, avs);
756 // store in appropriate place
757 if (a.hasOption(Opt.LINKED))
759 // store the order of linkedIds
760 if (!linkedOrder.contains(linkedId))
761 linkedOrder.add(linkedId);
764 // store arg in the list of args used
765 if (!argList.contains(a))
769 private String defaultLinkedId(boolean increment)
771 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
772 .append(Integer.toString(defaultLinkedIdCounter)).toString();
775 while (linkedArgs.containsKey(defaultLinkedId))
777 defaultLinkedIdCounter++;
778 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
779 .append(Integer.toString(defaultLinkedIdCounter))
783 getOrCreateLinkedArgValuesMap(defaultLinkedId);
784 return defaultLinkedId;
787 public String makeSubstitutions(String val, String linkedId)
789 return makeSubstitutions(val, linkedId, false);
792 public String makeSubstitutions(String val, String linkedId,
795 if (!this.substitutions || val == null)
800 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
802 int closeBracket = val.indexOf(']');
803 if (val.length() == closeBracket)
805 subvals = val.substring(0, closeBracket + 1);
806 rest = val.substring(closeBracket + 1);
813 if (rest.contains(LINKEDIDAUTOCOUNTER))
815 rest = rest.replace(LINKEDIDAUTOCOUNTER,
816 String.valueOf(linkedIdAutoCounter));
818 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
820 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
821 String.valueOf(++linkedIdAutoCounter));
823 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
825 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
826 String.valueOf(defaultLinkedIdCounter));
828 ArgValuesMap avm = linkedArgs.get(linkedId);
831 if (rest.contains(LINKEDIDBASENAME))
833 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
835 if (rest.contains(LINKEDIDEXTENSION))
837 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
839 if (rest.contains(LINKEDIDDIRNAME))
841 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
846 if (rest.contains(ARGFILEBASENAME))
848 rest = rest.replace(ARGFILEBASENAME,
849 FileUtils.getBasename(new File(argFile)));
851 if (rest.contains(ARGFILEDIRNAME))
853 rest = rest.replace(ARGFILEDIRNAME,
854 FileUtils.getDirname(new File(argFile)));
859 if (rest.contains(ONTHEFLYCOUNTER))
861 rest = rest.replace(ONTHEFLYCOUNTER,
862 String.valueOf(ontheflyCounter));
864 if (rest.contains(INCREMENTONTHEFLYCOUNTER))
866 rest = rest.replace(INCREMENTONTHEFLYCOUNTER,
867 String.valueOf(++ontheflyCounter));
869 if (currentStructureFilename != null)
871 if (rest.contains(STRUCTUREBASENAME))
873 rest = rest.replace(STRUCTUREBASENAME, FileUtils
874 .getBasename(new File(currentStructureFilename)));
876 if (rest.contains(STRUCTUREDIRNAME))
878 rest = rest.replace(STRUCTUREDIRNAME,
879 FileUtils.getDirname(new File(currentStructureFilename)));
884 return new StringBuilder(subvals).append(rest).toString();
888 * A helper method to take a list of String args where we're expecting
889 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
890 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
891 * "file2", "file3"} *and remove these from the original list object* so that
892 * processing can continue from where it has left off, e.g. args has become
893 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
894 * carries on from the next --arg if available.
896 protected static List<String> getShellGlobbedFilenameValues(Arg a,
897 List<String> args, int i)
899 List<String> vals = new ArrayList<>();
900 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
902 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
903 if (!a.hasOption(Opt.GLOB))
909 public BootstrapArgs getBootstrapArgs()
911 return bootstrapArgs;
914 public boolean isSet(Arg a)
916 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
919 public boolean isSetAtAll(Arg a)
921 for (String linkedId : linkedOrder)
923 if (isSet(linkedId, a))
929 public boolean isSet(String linkedId, Arg a)
931 ArgValuesMap avm = linkedArgs.get(linkedId);
932 return avm == null ? false : avm.containsArg(a);
935 public boolean getBoolean(Arg a)
937 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
939 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
942 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
945 public boolean getBool(String linkedId, Arg a)
947 ArgValuesMap avm = linkedArgs.get(linkedId);
949 return a.getDefaultBoolValue();
950 ArgValues avs = avm.getArgValues(a);
951 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
954 public List<String> getLinkedIds()
959 public ArgValuesMap getLinkedArgs(String id)
961 return linkedArgs.get(id);
965 public String toString()
967 StringBuilder sb = new StringBuilder();
968 sb.append("UNLINKED\n");
969 sb.append(argValuesMapToString(linkedArgs.get(null)));
970 if (getLinkedIds() != null)
972 sb.append("LINKED\n");
973 for (String id : getLinkedIds())
975 // already listed these as UNLINKED args
979 ArgValuesMap avm = getLinkedArgs(id);
980 sb.append("ID: '").append(id).append("'\n");
981 sb.append(argValuesMapToString(avm));
984 return sb.toString();
987 private static String argValuesMapToString(ArgValuesMap avm)
991 StringBuilder sb = new StringBuilder();
992 for (Arg a : avm.getArgKeys())
994 ArgValues v = avm.getArgValues(a);
995 sb.append(v.toString());
998 return sb.toString();
1001 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
1002 boolean initsubstitutions, BootstrapArgs bsa)
1004 List<File> argFiles = new ArrayList<>();
1006 for (String pattern : argFilenameGlobs)
1008 // I don't think we want to dedup files, making life easier
1009 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
1012 return parseArgFileList(argFiles, initsubstitutions, bsa);
1015 public static ArgParser parseArgFileList(List<File> argFiles,
1016 boolean initsubstitutions, BootstrapArgs bsa)
1018 List<String> argsList = new ArrayList<>();
1019 for (File argFile : argFiles)
1021 if (!argFile.exists())
1023 String message = Arg.ARGFILE.argString() + EQUALS + "\""
1024 + argFile.getPath() + "\": File does not exist.";
1025 Jalview.exit(message, ExitCode.FILE_NOT_FOUND);
1029 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
1030 .append(EQUALS).append(argFile.getCanonicalPath())
1032 argsList.add(setargfile);
1033 argsList.addAll(readArgFile(argFile));
1034 argsList.add(Arg.UNSETARGFILE.argString());
1035 } catch (IOException e)
1037 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
1038 + "\": File could not be read.";
1039 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
1042 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
1044 return new ArgParser(argsList, initsubstitutions, true, bsa);
1047 protected static List<String> readArgFile(File argFile)
1049 List<String> args = new ArrayList<>();
1050 if (argFile != null && argFile.exists())
1054 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
1056 if (line != null && line.length() > 0
1057 && line.charAt(0) != ARGFILECOMMENT)
1060 } catch (IOException e)
1062 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
1063 + "\": File could not be read.";
1064 Console.debug(message, e);
1065 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
1071 // the following methods look for the "*" linkedId and add the argvalue to all
1072 // linkedId ArgValues if it does.
1074 * This version inserts the subvals sv into all created values
1076 private void addValue(String linkedId, Type type, ArgValues avs,
1077 SubVals sv, String v, int argIndex, boolean doSubs)
1079 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
1083 private void setBoolean(String linkedId, Type type, ArgValues avs,
1084 boolean b, int argIndex)
1086 this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
1087 b, argIndex, false);
1090 private void setNegated(String linkedId, ArgValues avs, boolean b)
1092 this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
1096 private void incrementCount(String linkedId, ArgValues avs)
1098 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
1099 null, false, 0, false);
1104 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1107 private void argValueOperation(Op op, String linkedId, Type type,
1108 ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
1111 // default to merge subvals if subvals are provided
1112 argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
1117 * The following operations look for the "*" and "open*" linkedIds and add the
1118 * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1119 * supplied, they are inserted into all new set values.
1122 * The ArgParser.Op operation
1124 * The String linkedId from the ArgValuesMap
1126 * The Arg.Type to attach to this ArgValue
1128 * The ArgValues for this linkedId
1130 * Use these SubVals on the ArgValue
1132 * Merge the SubVals with any existing on the value. False will
1133 * replace unless sv is null
1135 * The value of the ArgValue (may contain subvals).
1137 * The boolean value of the ArgValue.
1139 * The argIndex for the ArgValue.
1141 * Whether to perform substitutions on the subvals and value.
1143 private void argValueOperation(Op op, String linkedId, Type type,
1144 ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
1145 int argIndex, boolean doSubs)
1149 List<String> wildcardLinkedIds = null;
1150 if (a.hasOption(Opt.ALLOWMULTIID))
1154 case MATCHALLLINKEDIDS:
1155 wildcardLinkedIds = getLinkedIds();
1157 case MATCHOPENEDLINKEDIDS:
1158 wildcardLinkedIds = this.storedLinkedIds;
1163 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1164 // to storedLinkedIds
1165 if (linkedId != null && wildcardLinkedIds == null
1166 && a.hasOption(Opt.STORED)
1167 && !storedLinkedIds.contains(linkedId))
1169 storedLinkedIds.add(linkedId);
1172 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1174 if (wildcardLinkedIds != null)
1176 for (String id : wildcardLinkedIds)
1178 // skip incorrectly stored wildcard ids!
1179 if (id == null || MATCHALLLINKEDIDS.equals(id)
1180 || MATCHOPENEDLINKEDIDS.equals(id))
1184 ArgValuesMap avm = linkedArgs.get(id);
1185 // don't set an output if there isn't an input
1186 if (a.hasOption(Opt.REQUIREINPUT)
1187 && !avm.hasArgWithOption(Opt.INPUT))
1190 ArgValues tavs = avm.getOrCreateArgValues(a);
1200 sv = new SubVals(sv, val, merge);
1201 val = makeSubstitutions(sv.getContent(), id);
1203 tavs.addValue(sv, type, val, argIndex, true);
1209 val = makeSubstitutions(v, id);
1211 tavs.addValue(type, val, argIndex, true);
1213 finaliseStoringArgValue(id, tavs);
1217 tavs.setBoolean(type, b, argIndex, true);
1218 finaliseStoringArgValue(id, tavs);
1222 tavs.setNegated(b, true);
1225 case INCREMENTCOUNT:
1226 tavs.incrementCount();
1236 else // no wildcard linkedId -- do it simpler
1246 val = makeSubstitutions(v, linkedId);
1247 sv = new SubVals(sv, val);
1249 avs.addValue(sv, type, val, argIndex, false);
1255 val = makeSubstitutions(v, linkedId);
1257 avs.addValue(type, val, argIndex, false);
1259 finaliseStoringArgValue(linkedId, avs);
1263 avs.setBoolean(type, b, argIndex, false);
1264 finaliseStoringArgValue(linkedId, avs);
1268 avs.setNegated(b, false);
1271 case INCREMENTCOUNT:
1272 avs.incrementCount();
1281 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1283 if (linkedArgs.containsKey(linkedId)
1284 && linkedArgs.get(linkedId) != null)
1285 return linkedArgs.get(linkedId);
1287 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1288 return linkedArgs.get(linkedId);
1291 public boolean isOldStyle()
1293 return oldArguments;
1296 public boolean isMixedStyle()
1298 return mixedArguments;
1301 public String[] getMixedExamples()
1303 return mixedExamples;
1306 public void setStructureFilename(String s)
1308 this.currentStructureFilename = s;
1311 public static boolean getIgnoreNonStringValues()
1313 return ignoreNonStringValues;
1316 public static void setIgnoreNonStringValues(boolean b)
1318 ignoreNonStringValues = b;