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);
321 this.bootstrapArgs = bsa;
323 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
324 parse(args, initsubstitutions, allowPrivate);
327 private void parse(List<String> args, boolean initsubstitutions,
328 boolean allowPrivate)
330 this.substitutions = initsubstitutions;
333 * If the first argument does not start with "--" or "-" or is not "open",
334 * and is a filename that exists or a URL, it is probably a file/list of
335 * files to open so we insert an Arg.OPEN argument before it. This will
336 * mean the list of files at the start of the arguments are all opened
341 String arg0 = args.get(0);
343 && (!arg0.startsWith(DOUBLEDASH) && !arg0.startsWith("-")
344 && !arg0.equals("open") && (new File(arg0).exists()
345 || HttpUtils.startsWithHttpOrHttps(arg0))))
347 // insert "--open" at the start
348 args.add(0, Arg.OPEN.argString());
352 for (int i = 0; i < args.size(); i++)
354 String arg = args.get(i);
356 // look for double-dash, e.g. --arg
357 if (arg.startsWith(DOUBLEDASH))
359 String argName = null;
361 List<String> globVals = null; // for Opt.GLOB only
362 SubVals globSubVals = null; // also for use by Opt.GLOB only
363 String linkedId = null;
366 // look for equals e.g. --arg=value
367 int equalPos = arg.indexOf(EQUALS);
370 argName = arg.substring(DOUBLEDASH.length(), equalPos);
371 val = arg.substring(equalPos + 1);
375 argName = arg.substring(DOUBLEDASH.length());
378 // look for linked ID e.g. --arg[linkedID]
379 int idOpen = argName.indexOf('[');
380 int idClose = argName.indexOf(']');
381 if (idOpen > -1 && idClose == argName.length() - 1)
383 linkedId = argName.substring(idOpen + 1, idClose);
384 argName = argName.substring(0, idOpen);
387 // look for type modification e.g. --help-opening
388 int dashPos = argName.indexOf(SINGLEDASH);
391 String potentialArgName = argName.substring(0, dashPos);
392 Arg potentialArg = argMap.get(potentialArgName);
393 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
395 String typeName = argName.substring(dashPos + 1);
398 type = Type.valueOf(typeName);
399 } catch (IllegalArgumentException e)
403 argName = argName.substring(0, dashPos);
407 Arg a = argMap.get(argName);
408 // check for boolean prepended by "no" e.g. --nowrap
409 boolean negated = false;
412 if (argName.startsWith(NEGATESTRING) && argMap
413 .containsKey(argName.substring(NEGATESTRING.length())))
415 argName = argName.substring(NEGATESTRING.length());
416 a = argMap.get(argName);
421 // after all other args, look for Opt.PREFIXKEV args if still not
423 for (Arg potentialArg : EnumSet.allOf(Arg.class))
425 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
426 && argName.startsWith(potentialArg.getName())
429 val = argName.substring(potentialArg.getName().length())
431 argName = argName.substring(0,
432 potentialArg.getName().length());
440 // check for config errors
444 Console.error("Argument '" + arg + "' not recognised. Exiting.");
446 "Invalid argument used." + System.lineSeparator() + "Use"
447 + System.lineSeparator() + "jalview "
448 + Arg.HELP.argString() + System.lineSeparator()
449 + "for a usage statement.",
450 ExitCode.INVALID_ARGUMENT);
453 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
456 "Argument '" + a.argString() + "' is private. Ignoring.");
459 if (!a.hasOption(Opt.BOOLEAN) && negated)
461 // used "no" with a non-boolean option
462 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
463 + "' not a boolean option. Ignoring.");
466 if (!a.hasOption(Opt.STRING) && equalPos > -1)
468 // set --argname=value when arg does not accept values
469 Console.error("Argument '" + a.argString()
470 + "' does not expect a value (given as '" + arg
474 if (!a.hasOption(Opt.LINKED) && linkedId != null)
476 // set --argname[linkedId] when arg does not use linkedIds
477 Console.error("Argument '" + a.argString()
478 + "' does not expect a linked id (given as '" + arg
484 if (a.hasOption(Opt.STRING))
488 if (a.hasOption(Opt.GLOB))
490 // strip off and save the SubVals to be added individually later
491 globSubVals = new SubVals(val);
492 // make substitutions before looking for files
493 String fileGlob = makeSubstitutions(globSubVals.getContent(),
495 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
499 // val is already set -- will be saved in the ArgValue later in
505 // There is no "=" so value is next arg or args (possibly shell
507 if (i + 1 >= args.size())
509 // no value to take for arg, which wants a value
510 Console.error("Argument '" + a.getName()
511 + "' requires a value, none given. Ignoring.");
514 // deal with bash globs here (--arg val* is expanded before reaching
515 // the JVM). Note that SubVals cannot be used in this case.
516 // If using the --arg=val then the glob is preserved and Java globs
517 // will be used later. SubVals can be used.
518 if (a.hasOption(Opt.GLOB))
520 // if this is the first argument with a file list at the start of
521 // the args we add filenames from index i instead of i+1
522 globVals = getShellGlobbedFilenameValues(a, args, i + 1);
526 val = args.get(i + 1);
531 // make NOACTION adjustments
532 // default and auto counter increments
535 linkedIdAutoCounter++;
537 else if (a == Arg.SUBSTITUTIONS)
539 substitutions = !negated;
541 else if (a == Arg.SETARGFILE)
545 else if (a == Arg.UNSETARGFILE)
549 else if (a == Arg.ALL)
551 allLinkedIds = !negated;
553 else if (a == Arg.ALLSTRUCTURES)
555 allStructures = !negated;
558 if (a.hasOption(Opt.STORED))
560 // reset the lastOpenedLinkedIds list
561 this.storedLinkedIds = new ArrayList<>();
564 // this is probably only Arg.NEW and Arg.OPEN
565 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
567 // use the next default prefixed OPENLINKEDID
568 defaultLinkedId(true);
571 String autoCounterString = null;
572 String defaultLinkedId = defaultLinkedId(false);
573 boolean usingDefaultLinkedId = false;
574 if (a.hasOption(Opt.LINKED))
576 if (linkedId == null)
578 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWMULTIID)
579 && val.contains(MATCHALLLINKEDIDS))
581 // --output=*.ext is shorthand for --output {basename}.ext
582 // --output=*/*.ext is shorthand for
583 // --output {dirname}/{basename}.ext
584 // (or --image=*.ext)
585 linkedId = allLinkedIds ? MATCHALLLINKEDIDS
586 : MATCHOPENEDLINKEDIDS;
587 val = FileUtils.convertWildcardsToPath(val, MATCHALLLINKEDIDS,
588 LINKEDIDDIRNAME, LINKEDIDBASENAME);
590 else if (allLinkedIds && a.hasOption(Opt.ALLOWMULTIID))
592 linkedId = MATCHALLLINKEDIDS;
594 else if (a.hasOption(Opt.ALLOWMULTIID)
595 && this.storedLinkedIds != null
596 && this.storedLinkedIds.size() > 0)
598 linkedId = MATCHOPENEDLINKEDIDS;
602 // use default linkedId for linked arguments
603 linkedId = defaultLinkedId;
604 usingDefaultLinkedId = true;
605 Console.debug("Changing linkedId to '" + linkedId + "' from "
611 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
613 // turn {n} to the autoCounter
614 autoCounterString = Integer.toString(linkedIdAutoCounter);
615 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
617 Console.debug("Changing linkedId to '" + linkedId + "' from "
620 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
622 // turn {++n} to the incremented autoCounter
623 autoCounterString = Integer.toString(++linkedIdAutoCounter);
624 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
626 Console.debug("Changing linkedId to '" + linkedId + "' from "
632 // do not continue in this block for NOACTION args
633 if (a.hasOption(Opt.NOACTION))
636 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
638 // not dealing with both NODUPLICATEVALUES and GLOB
639 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
641 Console.error("Argument '" + a.argString()
642 + "' cannot contain a duplicate value ('" + val
643 + "'). Ignoring this and subsequent occurrences.");
647 // check for unique id
648 SubVals subvals = new SubVals(val);
649 boolean addNewSubVals = false;
650 String id = subvals.get(ArgValues.ID);
651 if (id != null && avm.hasId(a, id))
653 Console.error("Argument '" + a.argString()
654 + "' has a duplicate id ('" + id + "'). Ignoring.");
658 // set allstructures to all non-primary structure options in this linked
659 // id if --allstructures has been set
660 if (allStructures && (a.getType() == Type.STRUCTURE
661 // || a.getType() == Type.STRUCTUREIMAGE)
662 ) && !a.hasOption(Opt.PRIMARY))
664 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
665 // && !subvals.has("structureid"))
667 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
668 addNewSubVals = true;
672 ArgValues avs = avm.getOrCreateArgValues(a);
674 // store appropriate String value(s)
675 if (a.hasOption(Opt.STRING))
677 if (a.hasOption(Opt.GLOB) && globVals != null
678 && globVals.size() > 0)
680 Enumeration<String> gve = Collections.enumeration(globVals);
681 while (gve.hasMoreElements())
683 String v = gve.nextElement();
684 SubVals vsv = new SubVals(globSubVals, v);
685 addValue(linkedId, type, avs, vsv, v, argIndex++, true);
686 // if we're using defaultLinkedId and the arg increments the
688 if (gve.hasMoreElements() && usingDefaultLinkedId
689 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
691 // increment the default linkedId
692 linkedId = defaultLinkedId(true);
693 // get new avm and avs
694 avm = linkedArgs.get(linkedId);
695 avs = avm.getOrCreateArgValues(a);
701 // addValue(linkedId, type, avs, val, argIndex, true);
702 addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
703 val, argIndex, true);
706 else if (a.hasOption(Opt.BOOLEAN))
708 setBoolean(linkedId, type, avs, !negated, argIndex);
709 setNegated(linkedId, avs, negated);
711 else if (a.hasOption(Opt.UNARY))
713 setBoolean(linkedId, type, avs, true, argIndex);
716 // remove the '*' or 'open*' linkedId that should be empty if it was
718 if ((MATCHALLLINKEDIDS.equals(linkedId)
719 || MATCHOPENEDLINKEDIDS.equals(linkedId))
720 && linkedArgs.containsKey(linkedId))
722 linkedArgs.remove(linkedId);
728 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
731 incrementCount(linkedId, avs);
734 // store in appropriate place
735 if (a.hasOption(Opt.LINKED))
737 // store the order of linkedIds
738 if (!linkedOrder.contains(linkedId))
739 linkedOrder.add(linkedId);
742 // store arg in the list of args used
743 if (!argList.contains(a))
747 private String defaultLinkedId(boolean increment)
749 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
750 .append(Integer.toString(defaultLinkedIdCounter)).toString();
753 while (linkedArgs.containsKey(defaultLinkedId))
755 defaultLinkedIdCounter++;
756 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
757 .append(Integer.toString(defaultLinkedIdCounter))
761 getOrCreateLinkedArgValuesMap(defaultLinkedId);
762 return defaultLinkedId;
765 public String makeSubstitutions(String val, String linkedId)
767 return makeSubstitutions(val, linkedId, false);
770 public String makeSubstitutions(String val, String linkedId,
773 if (!this.substitutions || val == null)
778 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
780 int closeBracket = val.indexOf(']');
781 if (val.length() == closeBracket)
783 subvals = val.substring(0, closeBracket + 1);
784 rest = val.substring(closeBracket + 1);
791 if (rest.contains(LINKEDIDAUTOCOUNTER))
793 rest = rest.replace(LINKEDIDAUTOCOUNTER,
794 String.valueOf(linkedIdAutoCounter));
796 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
798 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
799 String.valueOf(++linkedIdAutoCounter));
801 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
803 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
804 String.valueOf(defaultLinkedIdCounter));
806 ArgValuesMap avm = linkedArgs.get(linkedId);
809 if (rest.contains(LINKEDIDBASENAME))
811 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
813 if (rest.contains(LINKEDIDEXTENSION))
815 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
817 if (rest.contains(LINKEDIDDIRNAME))
819 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
824 if (rest.contains(ARGFILEBASENAME))
826 rest = rest.replace(ARGFILEBASENAME,
827 FileUtils.getBasename(new File(argFile)));
829 if (rest.contains(ARGFILEDIRNAME))
831 rest = rest.replace(ARGFILEDIRNAME,
832 FileUtils.getDirname(new File(argFile)));
837 if (rest.contains(ONTHEFLYCOUNTER))
839 rest = rest.replace(ONTHEFLYCOUNTER,
840 String.valueOf(ontheflyCounter));
842 if (rest.contains(INCREMENTONTHEFLYCOUNTER))
844 rest = rest.replace(INCREMENTONTHEFLYCOUNTER,
845 String.valueOf(++ontheflyCounter));
847 if (currentStructureFilename != null)
849 if (rest.contains(STRUCTUREBASENAME))
851 rest = rest.replace(STRUCTUREBASENAME, FileUtils
852 .getBasename(new File(currentStructureFilename)));
854 if (rest.contains(STRUCTUREDIRNAME))
856 rest = rest.replace(STRUCTUREDIRNAME,
857 FileUtils.getDirname(new File(currentStructureFilename)));
862 return new StringBuilder(subvals).append(rest).toString();
866 * A helper method to take a list of String args where we're expecting
867 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
868 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
869 * "file2", "file3"} *and remove these from the original list object* so that
870 * processing can continue from where it has left off, e.g. args has become
871 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
872 * carries on from the next --arg if available.
874 protected static List<String> getShellGlobbedFilenameValues(Arg a,
875 List<String> args, int i)
877 List<String> vals = new ArrayList<>();
878 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
880 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
881 if (!a.hasOption(Opt.GLOB))
887 public BootstrapArgs getBootstrapArgs()
889 return bootstrapArgs;
892 public boolean isSet(Arg a)
894 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
897 public boolean isSetAtAll(Arg a)
899 for (String linkedId : linkedOrder)
901 if (isSet(linkedId, a))
907 public boolean isSet(String linkedId, Arg a)
909 ArgValuesMap avm = linkedArgs.get(linkedId);
910 return avm == null ? false : avm.containsArg(a);
913 public boolean getBoolean(Arg a)
915 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
917 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
920 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
923 public boolean getBool(String linkedId, Arg a)
925 ArgValuesMap avm = linkedArgs.get(linkedId);
927 return a.getDefaultBoolValue();
928 ArgValues avs = avm.getArgValues(a);
929 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
932 public List<String> getLinkedIds()
937 public ArgValuesMap getLinkedArgs(String id)
939 return linkedArgs.get(id);
943 public String toString()
945 StringBuilder sb = new StringBuilder();
946 sb.append("UNLINKED\n");
947 sb.append(argValuesMapToString(linkedArgs.get(null)));
948 if (getLinkedIds() != null)
950 sb.append("LINKED\n");
951 for (String id : getLinkedIds())
953 // already listed these as UNLINKED args
957 ArgValuesMap avm = getLinkedArgs(id);
958 sb.append("ID: '").append(id).append("'\n");
959 sb.append(argValuesMapToString(avm));
962 return sb.toString();
965 private static String argValuesMapToString(ArgValuesMap avm)
969 StringBuilder sb = new StringBuilder();
970 for (Arg a : avm.getArgKeys())
972 ArgValues v = avm.getArgValues(a);
973 sb.append(v.toString());
976 return sb.toString();
979 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
980 boolean initsubstitutions, BootstrapArgs bsa)
982 List<File> argFiles = new ArrayList<>();
984 for (String pattern : argFilenameGlobs)
986 // I don't think we want to dedup files, making life easier
987 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
990 return parseArgFileList(argFiles, initsubstitutions, bsa);
993 public static ArgParser parseArgFileList(List<File> argFiles,
994 boolean initsubstitutions, BootstrapArgs bsa)
996 List<String> argsList = new ArrayList<>();
997 for (File argFile : argFiles)
999 if (!argFile.exists())
1001 String message = Arg.ARGFILE.argString() + EQUALS + "\""
1002 + argFile.getPath() + "\": File does not exist.";
1003 Jalview.exit(message, ExitCode.FILE_NOT_FOUND);
1007 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
1008 .append(EQUALS).append(argFile.getCanonicalPath())
1010 argsList.add(setargfile);
1011 argsList.addAll(readArgFile(argFile));
1012 argsList.add(Arg.UNSETARGFILE.argString());
1013 } catch (IOException e)
1015 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
1016 + "\": File could not be read.";
1017 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
1020 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
1022 return new ArgParser(argsList, initsubstitutions, true, bsa);
1025 protected static List<String> readArgFile(File argFile)
1027 List<String> args = new ArrayList<>();
1028 if (argFile != null && argFile.exists())
1032 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
1034 if (line != null && line.length() > 0
1035 && line.charAt(0) != ARGFILECOMMENT)
1038 } catch (IOException e)
1040 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
1041 + "\": File could not be read.";
1042 Console.debug(message, e);
1043 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
1049 // the following methods look for the "*" linkedId and add the argvalue to all
1050 // linkedId ArgValues if it does.
1052 * This version inserts the subvals sv into all created values
1054 private void addValue(String linkedId, Type type, ArgValues avs,
1055 SubVals sv, String v, int argIndex, boolean doSubs)
1057 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
1061 private void setBoolean(String linkedId, Type type, ArgValues avs,
1062 boolean b, int argIndex)
1064 this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
1065 b, argIndex, false);
1068 private void setNegated(String linkedId, ArgValues avs, boolean b)
1070 this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
1074 private void incrementCount(String linkedId, ArgValues avs)
1076 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
1077 null, false, 0, false);
1082 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1085 private void argValueOperation(Op op, String linkedId, Type type,
1086 ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
1089 // default to merge subvals if subvals are provided
1090 argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
1095 * The following operations look for the "*" and "open*" linkedIds and add the
1096 * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1097 * supplied, they are inserted into all new set values.
1100 * The ArgParser.Op operation
1102 * The String linkedId from the ArgValuesMap
1104 * The Arg.Type to attach to this ArgValue
1106 * The ArgValues for this linkedId
1108 * Use these SubVals on the ArgValue
1110 * Merge the SubVals with any existing on the value. False will
1111 * replace unless sv is null
1113 * The value of the ArgValue (may contain subvals).
1115 * The boolean value of the ArgValue.
1117 * The argIndex for the ArgValue.
1119 * Whether to perform substitutions on the subvals and value.
1121 private void argValueOperation(Op op, String linkedId, Type type,
1122 ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
1123 int argIndex, boolean doSubs)
1127 List<String> wildcardLinkedIds = null;
1128 if (a.hasOption(Opt.ALLOWMULTIID))
1132 case MATCHALLLINKEDIDS:
1133 wildcardLinkedIds = getLinkedIds();
1135 case MATCHOPENEDLINKEDIDS:
1136 wildcardLinkedIds = this.storedLinkedIds;
1141 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1142 // to storedLinkedIds
1143 if (linkedId != null && wildcardLinkedIds == null
1144 && a.hasOption(Opt.STORED)
1145 && !storedLinkedIds.contains(linkedId))
1147 storedLinkedIds.add(linkedId);
1150 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1152 if (wildcardLinkedIds != null)
1154 for (String id : wildcardLinkedIds)
1156 // skip incorrectly stored wildcard ids!
1157 if (id == null || MATCHALLLINKEDIDS.equals(id)
1158 || MATCHOPENEDLINKEDIDS.equals(id))
1162 ArgValuesMap avm = linkedArgs.get(id);
1163 // don't set an output if there isn't an input
1164 if (a.hasOption(Opt.REQUIREINPUT)
1165 && !avm.hasArgWithOption(Opt.INPUT))
1168 ArgValues tavs = avm.getOrCreateArgValues(a);
1178 sv = new SubVals(sv, val, merge);
1179 val = makeSubstitutions(sv.getContent(), id);
1181 tavs.addValue(sv, type, val, argIndex, true);
1187 val = makeSubstitutions(v, id);
1189 tavs.addValue(type, val, argIndex, true);
1191 finaliseStoringArgValue(id, tavs);
1195 tavs.setBoolean(type, b, argIndex, true);
1196 finaliseStoringArgValue(id, tavs);
1200 tavs.setNegated(b, true);
1203 case INCREMENTCOUNT:
1204 tavs.incrementCount();
1214 else // no wildcard linkedId -- do it simpler
1224 val = makeSubstitutions(v, linkedId);
1225 sv = new SubVals(sv, val);
1227 avs.addValue(sv, type, val, argIndex, false);
1233 val = makeSubstitutions(v, linkedId);
1235 avs.addValue(type, val, argIndex, false);
1237 finaliseStoringArgValue(linkedId, avs);
1241 avs.setBoolean(type, b, argIndex, false);
1242 finaliseStoringArgValue(linkedId, avs);
1246 avs.setNegated(b, false);
1249 case INCREMENTCOUNT:
1250 avs.incrementCount();
1259 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1261 if (linkedArgs.containsKey(linkedId)
1262 && linkedArgs.get(linkedId) != null)
1263 return linkedArgs.get(linkedId);
1265 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1266 return linkedArgs.get(linkedId);
1269 public boolean isOldStyle()
1271 return oldArguments;
1274 public boolean isMixedStyle()
1276 return mixedArguments;
1279 public String[] getMixedExamples()
1281 return mixedExamples;
1284 public void setStructureFilename(String s)
1286 this.currentStructureFilename = s;