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;
197 protected static final Map<String, Arg> argMap;
199 protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
201 protected List<String> linkedOrder = new ArrayList<>();
203 protected List<String> storedLinkedIds = new ArrayList<>();
205 protected List<Arg> argList = new ArrayList<>();
207 private static final char ARGFILECOMMENT = '#';
209 private int argIndex = 0;
211 private BootstrapArgs bootstrapArgs = null;
213 private boolean oldArguments = false;
215 private boolean mixedArguments = false;
218 * saved examples of mixed arguments
220 private String[] mixedExamples = new String[] { null, null };
224 argMap = new HashMap<>();
225 for (Arg a : EnumSet.allOf(Arg.class))
227 for (String argName : a.getNames())
229 if (argMap.containsKey(argName))
231 Console.warn("Trying to add argument name multiple times: '"
233 if (argMap.get(argName) != a)
236 "Trying to add argument name multiple times for different Args: '"
237 + argMap.get(argName).getName() + ":" + argName
238 + "' and '" + a.getName() + ":" + argName
243 argMap.put(argName, a);
248 public ArgParser(String[] args)
250 this(args, false, null);
253 public ArgParser(String[] args, boolean initsubstitutions,
257 * Make a mutable new ArrayList so that shell globbing parser works.
258 * (When shell file globbing is used, there are a sequence of non-Arg
259 * arguments (which are the expanded globbed filenames) that need to be
260 * consumed by the --append/--argfile/etc Arg which is most easily done
261 * by removing these filenames from the list one at a time. This can't be
262 * done with an ArrayList made with only Arrays.asList(String[] args) as
263 * that is not mutable. )
265 this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
269 public ArgParser(List<String> args, boolean initsubstitutions)
271 this(args, initsubstitutions, false, null);
274 public ArgParser(List<String> args, boolean initsubstitutions,
275 boolean allowPrivate, BootstrapArgs bsa)
277 // do nothing if there are no "--" args and (some "-" args || >0 arg is
281 for (String arg : args)
283 if (arg.startsWith(DOUBLEDASH))
286 if (mixedExamples[1] == null)
288 mixedExamples[1] = arg;
291 else if ((arg.startsWith("-") && !arg.equals(STDOUTFILENAME))
292 || arg.equals("open"))
295 if (mixedExamples[0] == null)
297 mixedExamples[0] = arg;
305 mixedArguments = true;
313 if (oldArguments || mixedArguments)
315 // leave it to the old style -- parse an empty list
316 parse(new ArrayList<String>(), false, false);
322 this.bootstrapArgs = bsa;
326 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
328 parse(args, initsubstitutions, allowPrivate);
331 private void parse(List<String> args, boolean initsubstitutions,
332 boolean allowPrivate)
334 this.substitutions = initsubstitutions;
337 * If the first argument does not start with "--" or "-" or is not "open",
338 * and is a filename that exists or a URL, it is probably a file/list of
339 * files to open so we insert an Arg.OPEN argument before it. This will
340 * mean the list of files at the start of the arguments are all opened
345 String arg0 = args.get(0);
347 && (!arg0.startsWith(DOUBLEDASH) && !arg0.startsWith("-")
348 && !arg0.equals("open") && (new File(arg0).exists()
349 || HttpUtils.startsWithHttpOrHttps(arg0))))
351 // insert "--open" at the start
352 args.add(0, Arg.OPEN.argString());
356 for (int i = 0; i < args.size(); i++)
358 String arg = args.get(i);
360 // look for double-dash, e.g. --arg
361 if (arg.startsWith(DOUBLEDASH))
363 String argName = null;
365 List<String> globVals = null; // for Opt.GLOB only
366 SubVals globSubVals = null; // also for use by Opt.GLOB only
367 String linkedId = null;
368 String givenLinkedId = null; // this is preserved to add to each
372 // look for equals e.g. --arg=value
373 int equalPos = arg.indexOf(EQUALS);
376 argName = arg.substring(DOUBLEDASH.length(), equalPos);
377 val = arg.substring(equalPos + 1);
381 argName = arg.substring(DOUBLEDASH.length());
384 // look for linked ID e.g. --arg[linkedID]
385 int idOpen = argName.indexOf('[');
386 int idClose = argName.indexOf(']');
387 if (idOpen > -1 && idClose == argName.length() - 1)
389 linkedId = argName.substring(idOpen + 1, idClose);
390 givenLinkedId = linkedId;
391 argName = argName.substring(0, idOpen);
394 // look for type modification e.g. --help-opening
395 int dashPos = argName.indexOf(SINGLEDASH);
398 String potentialArgName = argName.substring(0, dashPos);
399 Arg potentialArg = argMap.get(potentialArgName);
400 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
402 String typeName = argName.substring(dashPos + 1);
405 type = Type.valueOf(typeName);
406 } catch (IllegalArgumentException e)
410 argName = argName.substring(0, dashPos);
414 Arg a = argMap.get(argName);
415 // check for boolean prepended by "no" e.g. --nowrap
416 boolean negated = false;
419 if (argName.startsWith(NEGATESTRING) && argMap
420 .containsKey(argName.substring(NEGATESTRING.length())))
422 argName = argName.substring(NEGATESTRING.length());
423 a = argMap.get(argName);
428 // after all other args, look for Opt.PREFIXKEV args if still not
430 for (Arg potentialArg : EnumSet.allOf(Arg.class))
432 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
433 && argName.startsWith(potentialArg.getName())
436 val = argName.substring(potentialArg.getName().length())
438 argName = argName.substring(0,
439 potentialArg.getName().length());
447 // check for config errors
451 Console.error("Argument '" + arg + "' not recognised. Exiting.");
453 "Invalid argument used." + System.lineSeparator() + "Use"
454 + System.lineSeparator() + "jalview "
455 + Arg.HELP.argString() + System.lineSeparator()
456 + "for a usage statement.",
457 ExitCode.INVALID_ARGUMENT);
460 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
463 "Argument '" + a.argString() + "' is private. Ignoring.");
466 if (!a.hasOption(Opt.BOOLEAN) && negated)
468 // used "no" with a non-boolean option
469 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
470 + "' not a boolean option. Ignoring.");
473 if (!a.hasOption(Opt.STRING) && equalPos > -1)
475 // set --argname=value when arg does not accept values
476 Console.error("Argument '" + a.argString()
477 + "' does not expect a value (given as '" + arg
481 if (!a.hasOption(Opt.LINKED) && linkedId != null)
483 // set --argname[linkedId] when arg does not use linkedIds
484 Console.error("Argument '" + a.argString()
485 + "' does not expect a linked id (given as '" + arg
491 if (a.hasOption(Opt.STRING))
495 if (a.hasOption(Opt.GLOB))
497 // strip off and save the SubVals to be added individually later
498 globSubVals = new SubVals(val);
499 // make substitutions before looking for files
500 String fileGlob = makeSubstitutions(globSubVals.getContent(),
502 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
506 // val is already set -- will be saved in the ArgValue later in
512 // There is no "=" so value is next arg or args (possibly shell
514 if (i + 1 >= args.size())
516 // no value to take for arg, which wants a value
517 Console.error("Argument '" + a.getName()
518 + "' requires a value, none given. Ignoring.");
521 // deal with bash globs here (--arg val* is expanded before reaching
522 // the JVM). Note that SubVals cannot be used in this case.
523 // If using the --arg=val then the glob is preserved and Java globs
524 // will be used later. SubVals can be used.
525 if (a.hasOption(Opt.GLOB))
527 // if this is the first argument with a file list at the start of
528 // the args we add filenames from index i instead of i+1
529 globVals = getShellGlobbedFilenameValues(a, args, i + 1);
533 val = args.get(i + 1);
538 // make NOACTION adjustments
539 // default and auto counter increments
542 linkedIdAutoCounter++;
544 else if (a == Arg.SUBSTITUTIONS)
546 substitutions = !negated;
548 else if (a == Arg.SETARGFILE)
552 else if (a == Arg.UNSETARGFILE)
556 else if (a == Arg.ALL)
558 allLinkedIds = !negated;
560 else if (a == Arg.ALLSTRUCTURES)
562 allStructures = !negated;
565 if (a.hasOption(Opt.STORED))
567 // reset the lastOpenedLinkedIds list
568 this.storedLinkedIds = new ArrayList<>();
571 // this is probably only Arg.NEW and Arg.OPEN
572 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
574 // use the next default prefixed OPENLINKEDID
575 defaultLinkedId(true);
578 String autoCounterString = null;
579 String defaultLinkedId = defaultLinkedId(false);
580 boolean usingDefaultLinkedId = false;
581 if (a.hasOption(Opt.LINKED))
583 if (linkedId == null)
585 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWMULTIID)
586 && val.contains(MATCHALLLINKEDIDS))
588 // --output=*.ext is shorthand for --output {basename}.ext
589 // --output=*/*.ext is shorthand for
590 // --output {dirname}/{basename}.ext
591 // (or --image=*.ext)
592 linkedId = allLinkedIds ? MATCHALLLINKEDIDS
593 : MATCHOPENEDLINKEDIDS;
594 val = FileUtils.convertWildcardsToPath(val, MATCHALLLINKEDIDS,
595 LINKEDIDDIRNAME, LINKEDIDBASENAME);
597 else if (allLinkedIds && a.hasOption(Opt.ALLOWMULTIID))
599 linkedId = MATCHALLLINKEDIDS;
603 // user has made conscious decision for these args to apply to
604 // all, so set givenLinkedId too
605 givenLinkedId = linkedId;
607 else if (a.hasOption(Opt.ALLOWMULTIID)
608 && this.storedLinkedIds != null
609 && this.storedLinkedIds.size() > 0)
611 linkedId = MATCHOPENEDLINKEDIDS;
615 // use default linkedId for linked arguments
616 linkedId = defaultLinkedId;
617 usingDefaultLinkedId = true;
618 Console.debug("Changing linkedId to '" + linkedId + "' from "
624 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
626 // turn {n} to the autoCounter
627 autoCounterString = Integer.toString(linkedIdAutoCounter);
628 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
630 Console.debug("Changing linkedId to '" + linkedId + "' from "
633 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
635 // turn {++n} to the incremented autoCounter
636 autoCounterString = Integer.toString(++linkedIdAutoCounter);
637 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
639 Console.debug("Changing linkedId to '" + linkedId + "' from "
645 // do not continue in this block for NOACTION args
646 if (a.hasOption(Opt.NOACTION))
649 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
651 // not dealing with both NODUPLICATEVALUES and GLOB
652 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
654 Console.error("Argument '" + a.argString()
655 + "' cannot contain a duplicate value ('" + val
656 + "'). Ignoring this and subsequent occurrences.");
660 // check for unique id
661 SubVals subvals = new SubVals(val);
662 boolean addNewSubVals = false;
663 String id = subvals.get(ArgValues.ID);
664 if (id != null && avm.hasId(a, id))
666 Console.error("Argument '" + a.argString()
667 + "' has a duplicate id ('" + id + "'). Ignoring.");
671 // set allstructures to all non-primary structure options in this linked
672 // id if --allstructures has been set
673 if (allStructures && (a.hasType(Type.STRUCTURE)
674 // || a.getType() == Type.STRUCTUREIMAGE)
675 ) && !a.hasOption(Opt.PRIMARY))
677 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
678 // && !subvals.has("structureid"))
680 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
681 addNewSubVals = true;
685 ArgValues avs = avm.getOrCreateArgValues(a);
687 // store appropriate String value(s)
688 if (a.hasOption(Opt.STRING))
690 if (a.hasOption(Opt.GLOB) && globVals != null
691 && globVals.size() > 0)
693 Enumeration<String> gve = Collections.enumeration(globVals);
694 while (gve.hasMoreElements())
696 String v = gve.nextElement();
697 SubVals vsv = new SubVals(globSubVals, v);
698 addValue(linkedId, givenLinkedId, type, avs, vsv, v,
700 // if we're using defaultLinkedId and the arg increments the
702 if (gve.hasMoreElements() && usingDefaultLinkedId
703 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
705 // increment the default linkedId
706 linkedId = defaultLinkedId(true);
707 // get new avm and avs
708 avm = linkedArgs.get(linkedId);
709 avs = avm.getOrCreateArgValues(a);
715 // addValue(linkedId, type, avs, val, argIndex, true);
716 addValue(linkedId, givenLinkedId, type, avs,
717 addNewSubVals ? subvals : null, val, argIndex, true);
720 else if (a.hasOption(Opt.BOOLEAN))
722 setBoolean(linkedId, givenLinkedId, type, avs, !negated,
724 setNegated(linkedId, avs, negated);
726 else if (a.hasOption(Opt.UNARY))
728 setBoolean(linkedId, givenLinkedId, type, avs, true, argIndex);
731 // remove the '*' or 'open*' linkedId that should be empty if it was
733 if ((MATCHALLLINKEDIDS.equals(linkedId)
734 || MATCHOPENEDLINKEDIDS.equals(linkedId))
735 && linkedArgs.containsKey(linkedId))
737 linkedArgs.remove(linkedId);
743 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
746 incrementCount(linkedId, avs);
749 // store in appropriate place
750 if (a.hasOption(Opt.LINKED))
752 // store the order of linkedIds
753 if (!linkedOrder.contains(linkedId))
754 linkedOrder.add(linkedId);
757 // store arg in the list of args used
758 if (!argList.contains(a))
762 private String defaultLinkedId(boolean increment)
764 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
765 .append(Integer.toString(defaultLinkedIdCounter)).toString();
768 while (linkedArgs.containsKey(defaultLinkedId))
770 defaultLinkedIdCounter++;
771 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
772 .append(Integer.toString(defaultLinkedIdCounter))
776 getOrCreateLinkedArgValuesMap(defaultLinkedId);
777 return defaultLinkedId;
780 public String makeSubstitutions(String val, String linkedId)
782 return makeSubstitutions(val, linkedId, false);
785 public String makeSubstitutions(String val, String linkedId,
788 if (!this.substitutions || val == null)
793 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
795 int closeBracket = val.indexOf(']');
796 if (val.length() == closeBracket)
798 subvals = val.substring(0, closeBracket + 1);
799 rest = val.substring(closeBracket + 1);
806 if (rest.contains(LINKEDIDAUTOCOUNTER))
808 rest = rest.replace(LINKEDIDAUTOCOUNTER,
809 String.valueOf(linkedIdAutoCounter));
811 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
813 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
814 String.valueOf(++linkedIdAutoCounter));
816 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
818 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
819 String.valueOf(defaultLinkedIdCounter));
821 ArgValuesMap avm = linkedArgs.get(linkedId);
824 if (rest.contains(LINKEDIDBASENAME))
826 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
828 if (rest.contains(LINKEDIDEXTENSION))
830 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
832 if (rest.contains(LINKEDIDDIRNAME))
834 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
839 if (rest.contains(ARGFILEBASENAME))
841 rest = rest.replace(ARGFILEBASENAME,
842 FileUtils.getBasename(new File(argFile)));
844 if (rest.contains(ARGFILEDIRNAME))
846 rest = rest.replace(ARGFILEDIRNAME,
847 FileUtils.getDirname(new File(argFile)));
852 if (rest.contains(ONTHEFLYCOUNTER))
854 rest = rest.replace(ONTHEFLYCOUNTER,
855 String.valueOf(ontheflyCounter));
857 if (rest.contains(INCREMENTONTHEFLYCOUNTER))
859 rest = rest.replace(INCREMENTONTHEFLYCOUNTER,
860 String.valueOf(++ontheflyCounter));
862 if (currentStructureFilename != null)
864 if (rest.contains(STRUCTUREBASENAME))
866 rest = rest.replace(STRUCTUREBASENAME, FileUtils
867 .getBasename(new File(currentStructureFilename)));
869 if (rest.contains(STRUCTUREDIRNAME))
871 rest = rest.replace(STRUCTUREDIRNAME,
872 FileUtils.getDirname(new File(currentStructureFilename)));
877 return new StringBuilder(subvals).append(rest).toString();
881 * A helper method to take a list of String args where we're expecting
882 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
883 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
884 * "file2", "file3"} *and remove these from the original list object* so that
885 * processing can continue from where it has left off, e.g. args has become
886 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
887 * carries on from the next --arg if available.
889 protected static List<String> getShellGlobbedFilenameValues(Arg a,
890 List<String> args, int i)
892 List<String> vals = new ArrayList<>();
893 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
895 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
896 if (!a.hasOption(Opt.GLOB))
902 public BootstrapArgs getBootstrapArgs()
904 return bootstrapArgs;
907 public boolean isSet(Arg a)
909 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
912 public boolean isSetAtAll(Arg a)
914 for (String linkedId : linkedOrder)
916 if (isSet(linkedId, a))
922 public boolean isSet(String linkedId, Arg a)
924 ArgValuesMap avm = linkedArgs.get(linkedId);
925 return avm == null ? false : avm.containsArg(a);
928 public boolean getBoolean(Arg a)
930 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
932 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
935 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
938 public boolean getBool(String linkedId, Arg a)
940 ArgValuesMap avm = linkedArgs.get(linkedId);
942 return a.getDefaultBoolValue();
943 ArgValues avs = avm.getArgValues(a);
944 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
947 public List<String> getLinkedIds()
952 public ArgValuesMap getLinkedArgs(String id)
954 return linkedArgs.get(id);
958 public String toString()
960 StringBuilder sb = new StringBuilder();
961 sb.append("UNLINKED\n");
962 sb.append(argValuesMapToString(linkedArgs.get(null)));
963 if (getLinkedIds() != null)
965 sb.append("LINKED\n");
966 for (String id : getLinkedIds())
968 // already listed these as UNLINKED args
972 ArgValuesMap avm = getLinkedArgs(id);
973 sb.append("ID: '").append(id).append("'\n");
974 sb.append(argValuesMapToString(avm));
977 return sb.toString();
980 private static String argValuesMapToString(ArgValuesMap avm)
984 StringBuilder sb = new StringBuilder();
985 for (Arg a : avm.getArgKeys())
987 ArgValues v = avm.getArgValues(a);
988 sb.append(v.toString());
991 return sb.toString();
994 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
995 boolean initsubstitutions, BootstrapArgs bsa)
997 List<File> argFiles = new ArrayList<>();
999 for (String pattern : argFilenameGlobs)
1001 // I don't think we want to dedup files, making life easier
1002 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
1005 return parseArgFileList(argFiles, initsubstitutions, bsa);
1008 public static ArgParser parseArgFileList(List<File> argFiles,
1009 boolean initsubstitutions, BootstrapArgs bsa)
1011 List<String> argsList = new ArrayList<>();
1012 for (File argFile : argFiles)
1014 if (!argFile.exists())
1016 String message = Arg.ARGFILE.argString() + EQUALS + "\""
1017 + argFile.getPath() + "\": File does not exist.";
1018 Jalview.exit(message, ExitCode.FILE_NOT_FOUND);
1022 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
1023 .append(EQUALS).append(argFile.getCanonicalPath())
1025 argsList.add(setargfile);
1026 argsList.addAll(readArgFile(argFile));
1027 argsList.add(Arg.UNSETARGFILE.argString());
1028 } catch (IOException e)
1030 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
1031 + "\": File could not be read.";
1032 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
1035 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
1037 return new ArgParser(argsList, initsubstitutions, true, bsa);
1040 protected static List<String> readArgFile(File argFile)
1042 List<String> args = new ArrayList<>();
1043 if (argFile != null && argFile.exists())
1047 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
1049 if (line != null && line.length() > 0
1050 && line.charAt(0) != ARGFILECOMMENT)
1053 } catch (IOException e)
1055 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
1056 + "\": File could not be read.";
1057 Console.debug(message, e);
1058 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
1064 // the following methods look for the "*" linkedId and add the argvalue to all
1065 // linkedId ArgValues if it does.
1067 * This version inserts the subvals sv into all created values
1069 private void addValue(String linkedId, String givenLinkedId, Type type,
1070 ArgValues avs, SubVals sv, String v, int argIndex, boolean doSubs)
1072 this.argValueOperation(Op.ADDVALUE, linkedId, givenLinkedId, type, avs,
1073 sv, v, false, argIndex, doSubs);
1076 private void setBoolean(String linkedId, String givenLinkedId, Type type,
1077 ArgValues avs, boolean b, int argIndex)
1079 this.argValueOperation(Op.SETBOOLEAN, linkedId, givenLinkedId, type,
1080 avs, null, null, b, argIndex, false);
1083 private void setNegated(String linkedId, ArgValues avs, boolean b)
1085 this.argValueOperation(Op.SETNEGATED, linkedId, null, null, avs, null,
1089 private void incrementCount(String linkedId, ArgValues avs)
1091 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, null, avs,
1092 null, null, false, 0, false);
1097 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1100 private void argValueOperation(Op op, String linkedId,
1101 String givenLinkedId, Type type, ArgValues avs, SubVals sv,
1102 String v, boolean b, int argIndex, boolean doSubs)
1104 // default to merge subvals if subvals are provided
1105 argValueOperation(op, linkedId, givenLinkedId, type, avs, sv, true, v,
1106 b, argIndex, doSubs);
1110 * The following operations look for the "*" and "open*" linkedIds and add the
1111 * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1112 * supplied, they are inserted into all new set values.
1115 * The ArgParser.Op operation
1117 * The String linkedId from the ArgValuesMap
1119 * The Arg.Type to attach to this ArgValue
1121 * The ArgValues for this linkedId
1123 * Use these SubVals on the ArgValue
1125 * Merge the SubVals with any existing on the value. False will
1126 * replace unless sv is null
1128 * The value of the ArgValue (may contain subvals).
1130 * The boolean value of the ArgValue.
1132 * The argIndex for the ArgValue.
1134 * Whether to perform substitutions on the subvals and value.
1136 private void argValueOperation(Op op, String linkedId,
1137 String givenLinkedId, Type type, ArgValues avs, SubVals sv,
1138 boolean merge, String v, boolean b, int argIndex, boolean doSubs)
1142 List<String> wildcardLinkedIds = null;
1143 if (a.hasOption(Opt.ALLOWMULTIID))
1147 case MATCHALLLINKEDIDS:
1148 wildcardLinkedIds = getLinkedIds();
1150 case MATCHOPENEDLINKEDIDS:
1151 wildcardLinkedIds = this.storedLinkedIds;
1156 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1157 // to storedLinkedIds
1158 if (linkedId != null && wildcardLinkedIds == null
1159 && a.hasOption(Opt.STORED)
1160 && !storedLinkedIds.contains(linkedId))
1162 storedLinkedIds.add(linkedId);
1165 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1167 if (wildcardLinkedIds != null)
1169 for (String matchedLinkedId : wildcardLinkedIds)
1171 // skip incorrectly stored wildcard ids!
1172 if (matchedLinkedId == null
1173 || MATCHALLLINKEDIDS.equals(matchedLinkedId)
1174 || MATCHOPENEDLINKEDIDS.equals(matchedLinkedId))
1178 ArgValuesMap avm = linkedArgs.get(matchedLinkedId);
1179 // don't set an output if there isn't an input
1180 if (a.hasOption(Opt.REQUIREINPUT)
1181 && !avm.hasArgWithOption(Opt.INPUT))
1184 ArgValues tavs = avm.getOrCreateArgValues(a);
1194 sv = new SubVals(sv, val, merge);
1195 val = makeSubstitutions(sv.getContent(), matchedLinkedId);
1197 tavs.addValue(sv, type, val, argIndex, true, givenLinkedId);
1203 val = makeSubstitutions(v, matchedLinkedId);
1205 tavs.addValue(type, val, argIndex, true, givenLinkedId);
1207 finaliseStoringArgValue(matchedLinkedId, tavs);
1211 tavs.setBoolean(type, b, argIndex, true, givenLinkedId);
1212 finaliseStoringArgValue(matchedLinkedId, tavs);
1216 tavs.setNegated(b, true);
1219 case INCREMENTCOUNT:
1220 tavs.incrementCount();
1230 else // no wildcard linkedId -- do it simpler
1240 val = makeSubstitutions(v, linkedId);
1241 sv = new SubVals(sv, val);
1243 avs.addValue(sv, type, val, argIndex, false, givenLinkedId);
1249 val = makeSubstitutions(v, linkedId);
1251 avs.addValue(type, val, argIndex, false, givenLinkedId);
1253 finaliseStoringArgValue(linkedId, avs);
1257 avs.setBoolean(type, b, argIndex, false, givenLinkedId);
1258 finaliseStoringArgValue(linkedId, avs);
1262 avs.setNegated(b, false);
1265 case INCREMENTCOUNT:
1266 avs.incrementCount();
1275 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1277 if (linkedArgs.containsKey(linkedId)
1278 && linkedArgs.get(linkedId) != null)
1279 return linkedArgs.get(linkedId);
1281 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1282 return linkedArgs.get(linkedId);
1285 public boolean isOldStyle()
1287 return oldArguments;
1290 public boolean isMixedStyle()
1292 return mixedArguments;
1295 public String[] getMixedExamples()
1297 return mixedExamples;
1300 public void setStructureFilename(String s)
1302 this.currentStructureFilename = s;