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.Cache;
37 import jalview.bin.Console;
38 import jalview.bin.Jalview;
39 import jalview.bin.Jalview.ExitCode;
40 import jalview.bin.argparser.Arg.Opt;
41 import jalview.bin.argparser.Arg.Type;
42 import jalview.util.ArgParserUtils;
43 import jalview.util.FileUtils;
44 import jalview.util.HttpUtils;
46 public class ArgParser
48 protected static final String SINGLEDASH = "-";
50 protected static final String DOUBLEDASH = "--";
52 public static final char EQUALS = '=';
54 public static final String STDOUTFILENAME = "-";
56 protected static final String NEGATESTRING = "no";
59 * the default linked id prefix used for no id (ie when not even square braces
62 protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
65 * the linkedId string used to match all linkedIds seen so far
67 protected static final String MATCHALLLINKEDIDS = "*";
70 * the linkedId string used to match all of the last --open'ed linkedIds
72 protected static final String MATCHOPENEDLINKEDIDS = "open*";
75 * the counter added to the default linked id prefix
77 private int defaultLinkedIdCounter = 0;
80 * the substitution string used to use the defaultLinkedIdCounter
82 private static final String DEFAULTLINKEDIDCOUNTER = "{}";
85 * the linked id prefix used for --open files. NOW the same as DEFAULT
87 protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
90 * the counter used for {n} substitutions
92 private int linkedIdAutoCounter = 0;
95 * the linked id substitution string used to increment the idCounter (and use
96 * the incremented value)
98 private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
101 * the linked id substitution string used to use the idCounter
103 private static final String LINKEDIDAUTOCOUNTER = "{n}";
106 * the linked id substitution string used to use the filename extension of
109 private static final String LINKEDIDEXTENSION = "{extension}";
112 * the linked id substitution string used to use the base filename of --append
115 private static final String LINKEDIDBASENAME = "{basename}";
118 * the linked id substitution string used to use the dir path of --append or
121 private static final String LINKEDIDDIRNAME = "{dirname}";
124 * On-the-fly substitution (not made at argument parsing time)! the current
125 * structure filename extension
127 private static final String STRUCTUREEXTENSION = "{structureextension}";
130 * On-the-fly substitution (not made at argument parsing time)! the current
131 * structure filename base
133 private static final String STRUCTUREBASENAME = "{structurebasename}";
136 * On-the-fly substitution (not made at argument parsing time)! the current
137 * structure filename dir path
139 private static final String STRUCTUREDIRNAME = "{structuredirname}";
142 * On-the-fly substitution (not made at argument parsing time)! increment the
143 * on-the-fly counter and substitute the incremented value
145 private static final String INCREMENTONTHEFLYCOUNTER = "{++m}";
148 * On-the-fly substitution (not made at argument parsing time)! the current
149 * substitute with the on-the-fly counter
151 private static final String ONTHEFLYCOUNTER = "{m}";
154 * the string used for on-the-fly structure filename substitutions
156 private String currentStructureFilename = null;
159 * the counter used for on-the-fly {m} substitutions
161 private int ontheflyCounter = 0;
164 * the current argfile
166 private String argFile = null;
169 * the linked id substitution string used to use the dir path of the latest
171 /** --argfile name */
172 private static final String ARGFILEBASENAME = "{argfilebasename}";
175 * the linked id substitution string used to use the dir path of the latest
178 private static final String ARGFILEDIRNAME = "{argfiledirname}";
181 * flag to say whether {n} subtitutions in output filenames should be made.
182 * Turn on and off with --substitutions and --nosubstitutions Start with it on
184 private boolean substitutions = true;
187 * flag to say whether the default linkedId is the current default linked id
191 private boolean allLinkedIds = false;
194 * flag to say whether the structure arguments should be applied to all
195 * structures with this linked id
197 private boolean allStructures = false;
199 protected static final Map<String, Arg> argMap;
201 protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
203 protected List<String> linkedOrder = new ArrayList<>();
205 protected List<String> storedLinkedIds = new ArrayList<>();
207 protected List<Arg> argList = new ArrayList<>();
209 private static final char ARGFILECOMMENT = '#';
211 private int argIndex = 0;
213 private BootstrapArgs bootstrapArgs = null;
215 private boolean oldArguments = false;
217 private boolean mixedArguments = false;
220 * saved examples of mixed arguments
222 private String[] mixedExamples = new String[] { null, null };
226 argMap = new HashMap<>();
227 for (Arg a : EnumSet.allOf(Arg.class))
229 for (String argName : a.getNames())
231 if (argMap.containsKey(argName))
233 Console.warn("Trying to add argument name multiple times: '"
235 if (argMap.get(argName) != a)
238 "Trying to add argument name multiple times for different Args: '"
239 + argMap.get(argName).getName() + ":" + argName
240 + "' and '" + a.getName() + ":" + argName
245 argMap.put(argName, a);
250 public ArgParser(String[] args)
252 this(args, false, null);
255 public ArgParser(String[] args, boolean initsubstitutions,
259 * Make a mutable new ArrayList so that shell globbing parser works.
260 * (When shell file globbing is used, there are a sequence of non-Arg
261 * arguments (which are the expanded globbed filenames) that need to be
262 * consumed by the --append/--argfile/etc Arg which is most easily done
263 * by removing these filenames from the list one at a time. This can't be
264 * done with an ArrayList made with only Arrays.asList(String[] args) as
265 * that is not mutable. )
267 this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
271 public ArgParser(List<String> args, boolean initsubstitutions)
273 this(args, initsubstitutions, false, null);
276 public ArgParser(List<String> args, boolean initsubstitutions,
277 boolean allowPrivate, BootstrapArgs bsa)
279 // do nothing if there are no "--" args and (some "-" args || >0 arg is
283 for (String arg : args)
285 if (arg.startsWith(DOUBLEDASH))
288 if (mixedExamples[1] == null)
290 mixedExamples[1] = arg;
293 else if ((arg.startsWith("-") && !arg.equals(STDOUTFILENAME))
294 || arg.equals("open"))
297 if (mixedExamples[0] == null)
299 mixedExamples[0] = arg;
307 mixedArguments = true;
315 if (oldArguments || mixedArguments)
317 // leave it to the old style -- parse an empty list
318 parse(new ArrayList<String>(), false, false);
322 // preprocess for associated files only if no actual --args supplied
323 Console.debug("Supplied args are " + args);
324 if (!dd && !Cache.getDefault("NOARGPREPROCESSING", false))
326 ArgParserUtils.preProcessArgs(args);
327 Console.debug("Preprocessed args are " + args);
332 this.bootstrapArgs = bsa;
336 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
338 parse(args, initsubstitutions, allowPrivate);
341 private void parse(List<String> args, boolean initsubstitutions,
342 boolean allowPrivate)
344 this.substitutions = initsubstitutions;
347 * If the first argument does not start with "--" or "-" or is not "open",
348 * and is a filename that exists or a URL, it is probably a file/list of
349 * files to open so we insert an Arg.OPEN argument before it. This will
350 * mean the list of files at the start of the arguments are all opened
355 String arg0 = args.get(0);
357 && (!arg0.startsWith(DOUBLEDASH) && !arg0.startsWith("-")
358 && !arg0.equals("open") && (new File(arg0).exists()
359 || HttpUtils.startsWithHttpOrHttps(arg0))))
361 // insert "--open" at the start
362 args.add(0, Arg.OPEN.argString());
366 for (int i = 0; i < args.size(); i++)
368 String arg = args.get(i);
370 // look for double-dash, e.g. --arg
371 if (arg.startsWith(DOUBLEDASH))
373 String argName = null;
375 List<String> globVals = null; // for Opt.GLOB only
376 SubVals globSubVals = null; // also for use by Opt.GLOB only
377 String linkedId = null;
378 String givenLinkedId = null; // this is preserved to add to each
382 // look for equals e.g. --arg=value
383 int equalPos = arg.indexOf(EQUALS);
386 argName = arg.substring(DOUBLEDASH.length(), equalPos);
387 val = arg.substring(equalPos + 1);
391 argName = arg.substring(DOUBLEDASH.length());
394 // look for linked ID e.g. --arg[linkedID]
395 int idOpen = argName.indexOf('[');
396 int idClose = argName.indexOf(']');
397 if (idOpen > -1 && idClose == argName.length() - 1)
399 linkedId = argName.substring(idOpen + 1, idClose);
400 givenLinkedId = linkedId;
401 argName = argName.substring(0, idOpen);
404 // look for type modification e.g. --help-opening
405 int dashPos = argName.indexOf(SINGLEDASH);
408 String potentialArgName = argName.substring(0, dashPos);
409 Arg potentialArg = argMap.get(potentialArgName);
410 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
412 String typeName = argName.substring(dashPos + 1);
415 type = Type.valueOf(typeName);
416 } catch (IllegalArgumentException e)
420 argName = argName.substring(0, dashPos);
424 Arg a = argMap.get(argName);
425 // check for boolean prepended by "no" e.g. --nowrap
426 boolean negated = false;
429 if (argName.startsWith(NEGATESTRING) && argMap
430 .containsKey(argName.substring(NEGATESTRING.length())))
432 argName = argName.substring(NEGATESTRING.length());
433 a = argMap.get(argName);
438 // after all other args, look for Opt.PREFIXKEV args if still not
440 for (Arg potentialArg : EnumSet.allOf(Arg.class))
442 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
443 && argName.startsWith(potentialArg.getName())
446 val = argName.substring(potentialArg.getName().length())
448 argName = argName.substring(0,
449 potentialArg.getName().length());
457 // check for config errors
461 Console.error("Argument '" + arg + "' not recognised. Exiting.");
463 "Invalid argument used." + System.lineSeparator() + "Use"
464 + System.lineSeparator() + "jalview "
465 + Arg.HELP.argString() + System.lineSeparator()
466 + "for a usage statement.",
467 ExitCode.INVALID_ARGUMENT);
470 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
473 "Argument '" + a.argString() + "' is private. Ignoring.");
476 if (!a.hasOption(Opt.BOOLEAN) && negated)
478 // used "no" with a non-boolean option
479 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
480 + "' not a boolean option. Ignoring.");
483 if (!a.hasOption(Opt.STRING) && equalPos > -1)
485 // set --argname=value when arg does not accept values
486 Console.error("Argument '" + a.argString()
487 + "' does not expect a value (given as '" + arg
491 if (!a.hasOption(Opt.LINKED) && linkedId != null)
493 // set --argname[linkedId] when arg does not use linkedIds
494 Console.error("Argument '" + a.argString()
495 + "' does not expect a linked id (given as '" + arg
501 if (a.hasOption(Opt.STRING))
505 if (a.hasOption(Opt.GLOB))
507 // strip off and save the SubVals to be added individually later
508 globSubVals = new SubVals(val);
509 // make substitutions before looking for files
510 String fileGlob = makeSubstitutions(globSubVals.getContent(),
512 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
516 // val is already set -- will be saved in the ArgValue later in
522 // There is no "=" so value is next arg or args (possibly shell
524 if (i + 1 >= args.size())
526 // no value to take for arg, which wants a value
527 Console.error("Argument '" + a.getName()
528 + "' requires a value, none given. Ignoring.");
531 // deal with bash globs here (--arg val* is expanded before reaching
532 // the JVM). Note that SubVals cannot be used in this case.
533 // If using the --arg=val then the glob is preserved and Java globs
534 // will be used later. SubVals can be used.
535 if (a.hasOption(Opt.GLOB))
537 // if this is the first argument with a file list at the start of
538 // the args we add filenames from index i instead of i+1
539 globVals = getShellGlobbedFilenameValues(a, args, i + 1);
543 val = args.get(i + 1);
548 // make NOACTION adjustments
549 // default and auto counter increments
552 linkedIdAutoCounter++;
554 else if (a == Arg.SUBSTITUTIONS)
556 substitutions = !negated;
558 else if (a == Arg.SETARGFILE)
562 else if (a == Arg.UNSETARGFILE)
566 else if (a == Arg.ALL)
568 allLinkedIds = !negated;
570 else if (a == Arg.ALLSTRUCTURES)
572 allStructures = !negated;
575 if (a.hasOption(Opt.STORED))
577 // reset the lastOpenedLinkedIds list
578 this.storedLinkedIds = new ArrayList<>();
581 // this is probably only Arg.NEW and Arg.OPEN
582 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
584 // use the next default prefixed OPENLINKEDID
585 defaultLinkedId(true);
588 String autoCounterString = null;
589 String defaultLinkedId = defaultLinkedId(false);
590 boolean usingDefaultLinkedId = false;
591 if (a.hasOption(Opt.LINKED))
593 if (linkedId == null)
595 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWMULTIID)
596 && val.contains(MATCHALLLINKEDIDS))
598 // --output=*.ext is shorthand for --output {basename}.ext
599 // --output=*/*.ext is shorthand for
600 // --output {dirname}/{basename}.ext
601 // (or --image=*.ext)
602 linkedId = allLinkedIds ? MATCHALLLINKEDIDS
603 : MATCHOPENEDLINKEDIDS;
604 val = FileUtils.convertWildcardsToPath(val, MATCHALLLINKEDIDS,
605 LINKEDIDDIRNAME, LINKEDIDBASENAME);
607 else if (allLinkedIds && a.hasOption(Opt.ALLOWMULTIID))
609 linkedId = MATCHALLLINKEDIDS;
613 // user has made conscious decision for these args to apply to
614 // all, so set givenLinkedId too
615 givenLinkedId = linkedId;
617 else if (a.hasOption(Opt.ALLOWMULTIID)
618 && this.storedLinkedIds != null
619 && this.storedLinkedIds.size() > 0)
621 linkedId = MATCHOPENEDLINKEDIDS;
625 // use default linkedId for linked arguments
626 linkedId = defaultLinkedId;
627 usingDefaultLinkedId = true;
628 Console.debug("Changing linkedId to '" + linkedId + "' from "
634 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
636 // turn {n} to the autoCounter
637 autoCounterString = Integer.toString(linkedIdAutoCounter);
638 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
640 Console.debug("Changing linkedId to '" + linkedId + "' from "
643 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
645 // turn {++n} to the incremented autoCounter
646 autoCounterString = Integer.toString(++linkedIdAutoCounter);
647 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
649 Console.debug("Changing linkedId to '" + linkedId + "' from "
655 // do not continue in this block for NOACTION args
656 if (a.hasOption(Opt.NOACTION))
659 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
661 // not dealing with both NODUPLICATEVALUES and GLOB
662 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
664 Console.error("Argument '" + a.argString()
665 + "' cannot contain a duplicate value ('" + val
666 + "'). Ignoring this and subsequent occurrences.");
670 // check for unique id
671 SubVals subvals = new SubVals(val);
672 boolean addNewSubVals = false;
673 String id = subvals.get(ArgValues.ID);
674 if (id != null && avm.hasId(a, id))
676 Console.error("Argument '" + a.argString()
677 + "' has a duplicate id ('" + id + "'). Ignoring.");
681 // set allstructures to all non-primary structure options in this linked
682 // id if --allstructures has been set
683 if (allStructures && (a.hasType(Type.STRUCTURE)
684 // || a.getType() == Type.STRUCTUREIMAGE)
685 ) && !a.hasOption(Opt.PRIMARY))
687 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
688 // && !subvals.has("structureid"))
690 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
691 addNewSubVals = true;
695 ArgValues avs = avm.getOrCreateArgValues(a);
697 // store appropriate String value(s)
698 if (a.hasOption(Opt.STRING))
700 if (a.hasOption(Opt.GLOB) && globVals != null
701 && globVals.size() > 0)
703 Enumeration<String> gve = Collections.enumeration(globVals);
704 while (gve.hasMoreElements())
706 String v = gve.nextElement();
707 SubVals vsv = new SubVals(globSubVals, v);
708 addValue(linkedId, givenLinkedId, type, avs, vsv, v,
710 // if we're using defaultLinkedId and the arg increments the
712 if (gve.hasMoreElements() && usingDefaultLinkedId
713 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
715 // increment the default linkedId
716 linkedId = defaultLinkedId(true);
717 // get new avm and avs
718 avm = linkedArgs.get(linkedId);
719 avs = avm.getOrCreateArgValues(a);
725 // addValue(linkedId, type, avs, val, argIndex, true);
726 addValue(linkedId, givenLinkedId, type, avs,
727 addNewSubVals ? subvals : null, val, argIndex, true);
730 else if (a.hasOption(Opt.BOOLEAN))
732 setBoolean(linkedId, givenLinkedId, type, avs, !negated,
734 setNegated(linkedId, avs, negated);
736 else if (a.hasOption(Opt.UNARY))
738 setBoolean(linkedId, givenLinkedId, type, avs, true, argIndex);
741 // remove the '*' or 'open*' linkedId that should be empty if it was
743 if ((MATCHALLLINKEDIDS.equals(linkedId)
744 || MATCHOPENEDLINKEDIDS.equals(linkedId))
745 && linkedArgs.containsKey(linkedId))
747 linkedArgs.remove(linkedId);
753 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
756 incrementCount(linkedId, avs);
759 // store in appropriate place
760 if (a.hasOption(Opt.LINKED))
762 // store the order of linkedIds
763 if (!linkedOrder.contains(linkedId))
764 linkedOrder.add(linkedId);
767 // store arg in the list of args used
768 if (!argList.contains(a))
772 private String defaultLinkedId(boolean increment)
774 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
775 .append(Integer.toString(defaultLinkedIdCounter)).toString();
778 while (linkedArgs.containsKey(defaultLinkedId))
780 defaultLinkedIdCounter++;
781 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
782 .append(Integer.toString(defaultLinkedIdCounter))
786 getOrCreateLinkedArgValuesMap(defaultLinkedId);
787 return defaultLinkedId;
790 public String makeSubstitutions(String val, String linkedId)
792 return makeSubstitutions(val, linkedId, false);
795 public String makeSubstitutions(String val, String linkedId,
798 if (!this.substitutions || val == null)
803 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
805 int closeBracket = val.indexOf(']');
806 if (val.length() == closeBracket)
808 subvals = val.substring(0, closeBracket + 1);
809 rest = val.substring(closeBracket + 1);
816 if (rest.contains(LINKEDIDAUTOCOUNTER))
818 rest = rest.replace(LINKEDIDAUTOCOUNTER,
819 String.valueOf(linkedIdAutoCounter));
821 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
823 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
824 String.valueOf(++linkedIdAutoCounter));
826 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
828 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
829 String.valueOf(defaultLinkedIdCounter));
831 ArgValuesMap avm = linkedArgs.get(linkedId);
834 if (rest.contains(LINKEDIDBASENAME))
836 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
838 if (rest.contains(LINKEDIDEXTENSION))
840 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
842 if (rest.contains(LINKEDIDDIRNAME))
844 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
849 if (rest.contains(ARGFILEBASENAME))
851 rest = rest.replace(ARGFILEBASENAME,
852 FileUtils.getBasename(new File(argFile)));
854 if (rest.contains(ARGFILEDIRNAME))
856 rest = rest.replace(ARGFILEDIRNAME,
857 FileUtils.getDirname(new File(argFile)));
862 if (rest.contains(ONTHEFLYCOUNTER))
864 rest = rest.replace(ONTHEFLYCOUNTER,
865 String.valueOf(ontheflyCounter));
867 if (rest.contains(INCREMENTONTHEFLYCOUNTER))
869 rest = rest.replace(INCREMENTONTHEFLYCOUNTER,
870 String.valueOf(++ontheflyCounter));
872 if (currentStructureFilename != null)
874 if (rest.contains(STRUCTUREBASENAME))
876 rest = rest.replace(STRUCTUREBASENAME, FileUtils
877 .getBasename(new File(currentStructureFilename)));
879 if (rest.contains(STRUCTUREDIRNAME))
881 rest = rest.replace(STRUCTUREDIRNAME,
882 FileUtils.getDirname(new File(currentStructureFilename)));
887 return new StringBuilder(subvals).append(rest).toString();
891 * A helper method to take a list of String args where we're expecting
892 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
893 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
894 * "file2", "file3"} *and remove these from the original list object* so that
895 * processing can continue from where it has left off, e.g. args has become
896 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
897 * carries on from the next --arg if available.
899 protected static List<String> getShellGlobbedFilenameValues(Arg a,
900 List<String> args, int i)
902 List<String> vals = new ArrayList<>();
903 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
905 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
906 if (!a.hasOption(Opt.GLOB))
912 public BootstrapArgs getBootstrapArgs()
914 return bootstrapArgs;
917 public boolean isSet(Arg a)
919 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
922 public boolean isSetAtAll(Arg a)
924 for (String linkedId : linkedOrder)
926 if (isSet(linkedId, a))
932 public boolean isSet(String linkedId, Arg a)
934 ArgValuesMap avm = linkedArgs.get(linkedId);
935 return avm == null ? false : avm.containsArg(a);
938 public boolean getBoolean(Arg a)
940 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
942 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
945 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
948 public boolean getBool(String linkedId, Arg a)
950 ArgValuesMap avm = linkedArgs.get(linkedId);
952 return a.getDefaultBoolValue();
953 ArgValues avs = avm.getArgValues(a);
954 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
957 public List<String> getLinkedIds()
962 public ArgValuesMap getLinkedArgs(String id)
964 return linkedArgs.get(id);
968 public String toString()
970 StringBuilder sb = new StringBuilder();
971 sb.append("UNLINKED\n");
972 sb.append(argValuesMapToString(linkedArgs.get(null)));
973 if (getLinkedIds() != null)
975 sb.append("LINKED\n");
976 for (String id : getLinkedIds())
978 // already listed these as UNLINKED args
982 ArgValuesMap avm = getLinkedArgs(id);
983 sb.append("ID: '").append(id).append("'\n");
984 sb.append(argValuesMapToString(avm));
987 return sb.toString();
990 private static String argValuesMapToString(ArgValuesMap avm)
994 StringBuilder sb = new StringBuilder();
995 for (Arg a : avm.getArgKeys())
997 ArgValues v = avm.getArgValues(a);
998 sb.append(v.toString());
1001 return sb.toString();
1004 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
1005 boolean initsubstitutions, BootstrapArgs bsa)
1007 List<File> argFiles = new ArrayList<>();
1009 for (String pattern : argFilenameGlobs)
1011 // I don't think we want to dedup files, making life easier
1012 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
1015 return parseArgFileList(argFiles, initsubstitutions, bsa);
1018 public static ArgParser parseArgFileList(List<File> argFiles,
1019 boolean initsubstitutions, BootstrapArgs bsa)
1021 List<String> argsList = new ArrayList<>();
1022 for (File argFile : argFiles)
1024 if (!argFile.exists())
1026 String message = Arg.ARGFILE.argString() + EQUALS + "\""
1027 + argFile.getPath() + "\": File does not exist.";
1028 Jalview.exit(message, ExitCode.FILE_NOT_FOUND);
1032 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
1033 .append(EQUALS).append(argFile.getCanonicalPath())
1035 argsList.add(setargfile);
1036 argsList.addAll(readArgFile(argFile));
1037 argsList.add(Arg.UNSETARGFILE.argString());
1038 } catch (IOException e)
1040 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
1041 + "\": File could not be read.";
1042 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
1045 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
1047 return new ArgParser(argsList, initsubstitutions, true, bsa);
1050 protected static List<String> readArgFile(File argFile)
1052 List<String> args = new ArrayList<>();
1053 if (argFile != null && argFile.exists())
1057 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
1059 if (line != null && line.length() > 0
1060 && line.charAt(0) != ARGFILECOMMENT)
1063 } catch (IOException e)
1065 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
1066 + "\": File could not be read.";
1067 Console.debug(message, e);
1068 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
1074 // the following methods look for the "*" linkedId and add the argvalue to all
1075 // linkedId ArgValues if it does.
1077 * This version inserts the subvals sv into all created values
1079 private void addValue(String linkedId, String givenLinkedId, Type type,
1080 ArgValues avs, SubVals sv, String v, int argIndex, boolean doSubs)
1082 this.argValueOperation(Op.ADDVALUE, linkedId, givenLinkedId, type, avs,
1083 sv, v, false, argIndex, doSubs);
1086 private void setBoolean(String linkedId, String givenLinkedId, Type type,
1087 ArgValues avs, boolean b, int argIndex)
1089 this.argValueOperation(Op.SETBOOLEAN, linkedId, givenLinkedId, type,
1090 avs, null, null, b, argIndex, false);
1093 private void setNegated(String linkedId, ArgValues avs, boolean b)
1095 this.argValueOperation(Op.SETNEGATED, linkedId, null, null, avs, null,
1099 private void incrementCount(String linkedId, ArgValues avs)
1101 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, null, avs,
1102 null, null, false, 0, false);
1107 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1110 private void argValueOperation(Op op, String linkedId,
1111 String givenLinkedId, Type type, ArgValues avs, SubVals sv,
1112 String v, boolean b, int argIndex, boolean doSubs)
1114 // default to merge subvals if subvals are provided
1115 argValueOperation(op, linkedId, givenLinkedId, type, avs, sv, true, v,
1116 b, argIndex, doSubs);
1120 * The following operations look for the "*" and "open*" linkedIds and add the
1121 * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1122 * supplied, they are inserted into all new set values.
1125 * The ArgParser.Op operation
1127 * The String linkedId from the ArgValuesMap
1129 * The Arg.Type to attach to this ArgValue
1131 * The ArgValues for this linkedId
1133 * Use these SubVals on the ArgValue
1135 * Merge the SubVals with any existing on the value. False will
1136 * replace unless sv is null
1138 * The value of the ArgValue (may contain subvals).
1140 * The boolean value of the ArgValue.
1142 * The argIndex for the ArgValue.
1144 * Whether to perform substitutions on the subvals and value.
1146 private void argValueOperation(Op op, String linkedId,
1147 String givenLinkedId, Type type, ArgValues avs, SubVals sv,
1148 boolean merge, String v, boolean b, int argIndex, boolean doSubs)
1152 List<String> wildcardLinkedIds = null;
1153 if (a.hasOption(Opt.ALLOWMULTIID))
1157 case MATCHALLLINKEDIDS:
1158 wildcardLinkedIds = getLinkedIds();
1160 case MATCHOPENEDLINKEDIDS:
1161 wildcardLinkedIds = this.storedLinkedIds;
1166 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1167 // to storedLinkedIds
1168 if (linkedId != null && wildcardLinkedIds == null
1169 && a.hasOption(Opt.STORED)
1170 && !storedLinkedIds.contains(linkedId))
1172 storedLinkedIds.add(linkedId);
1175 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1177 if (wildcardLinkedIds != null)
1179 for (String matchedLinkedId : wildcardLinkedIds)
1181 // skip incorrectly stored wildcard ids!
1182 if (matchedLinkedId == null
1183 || MATCHALLLINKEDIDS.equals(matchedLinkedId)
1184 || MATCHOPENEDLINKEDIDS.equals(matchedLinkedId))
1188 ArgValuesMap avm = linkedArgs.get(matchedLinkedId);
1189 // don't set an output if there isn't an input
1190 if (a.hasOption(Opt.REQUIREINPUT)
1191 && !avm.hasArgWithOption(Opt.INPUT))
1194 ArgValues tavs = avm.getOrCreateArgValues(a);
1204 sv = new SubVals(sv, val, merge);
1205 val = makeSubstitutions(sv.getContent(), matchedLinkedId);
1207 tavs.addValue(sv, type, val, argIndex, true, givenLinkedId);
1213 val = makeSubstitutions(v, matchedLinkedId);
1215 tavs.addValue(type, val, argIndex, true, givenLinkedId);
1217 finaliseStoringArgValue(matchedLinkedId, tavs);
1221 tavs.setBoolean(type, b, argIndex, true, givenLinkedId);
1222 finaliseStoringArgValue(matchedLinkedId, tavs);
1226 tavs.setNegated(b, true);
1229 case INCREMENTCOUNT:
1230 tavs.incrementCount();
1240 else // no wildcard linkedId -- do it simpler
1250 val = makeSubstitutions(v, linkedId);
1251 sv = new SubVals(sv, val);
1253 avs.addValue(sv, type, val, argIndex, false, givenLinkedId);
1259 val = makeSubstitutions(v, linkedId);
1261 avs.addValue(type, val, argIndex, false, givenLinkedId);
1263 finaliseStoringArgValue(linkedId, avs);
1267 avs.setBoolean(type, b, argIndex, false, givenLinkedId);
1268 finaliseStoringArgValue(linkedId, avs);
1272 avs.setNegated(b, false);
1275 case INCREMENTCOUNT:
1276 avs.incrementCount();
1285 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1287 if (linkedArgs.containsKey(linkedId)
1288 && linkedArgs.get(linkedId) != null)
1289 return linkedArgs.get(linkedId);
1291 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1292 return linkedArgs.get(linkedId);
1295 public boolean isOldStyle()
1297 return oldArguments;
1300 public boolean isMixedStyle()
1302 return mixedArguments;
1305 public String[] getMixedExamples()
1307 return mixedExamples;
1310 public void setStructureFilename(String s)
1312 this.currentStructureFilename = s;