2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.bin.argparser;
24 import java.io.IOException;
25 import java.nio.file.Files;
26 import java.nio.file.Paths;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.EnumSet;
31 import java.util.Enumeration;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Locale;
37 import jalview.bin.Cache;
38 import jalview.bin.Console;
39 import jalview.bin.Jalview;
40 import jalview.bin.Jalview.ExitCode;
41 import jalview.bin.argparser.Arg.Opt;
42 import jalview.bin.argparser.Arg.Type;
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 * the current argfile
126 private String argFile = null;
129 * the linked id substitution string used to use the dir path of the latest
131 /** --argfile name */
132 private static final String ARGFILEBASENAME = "{argfilebasename}";
135 * the linked id substitution string used to use the dir path of the latest
138 private static final String ARGFILEDIRNAME = "{argfiledirname}";
141 * flag to say whether {n} subtitutions in output filenames should be made.
142 * Turn on and off with --substitutions and --nosubstitutions Start with it on
144 private boolean substitutions = true;
147 * flag to say whether the default linkedId is the current default linked id
151 private boolean allLinkedIds = false;
154 * flag to say whether the structure arguments should be applied to all
155 * structures with this linked id
157 private boolean allStructures = false;
159 protected static final Map<String, Arg> argMap;
161 protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
163 protected List<String> linkedOrder = new ArrayList<>();
165 protected List<String> storedLinkedIds = new ArrayList<>();
167 protected List<Arg> argList = new ArrayList<>();
169 private static final char ARGFILECOMMENT = '#';
171 private int argIndex = 0;
173 private BootstrapArgs bootstrapArgs = null;
175 private boolean oldArguments = false;
177 private boolean mixedArguments = false;
180 * saved examples of mixed arguments
182 private String[] mixedExamples = new String[] { null, null };
186 argMap = new HashMap<>();
187 for (Arg a : EnumSet.allOf(Arg.class))
189 for (String argName : a.getNames())
191 if (argMap.containsKey(argName))
193 Console.warn("Trying to add argument name multiple times: '"
195 if (argMap.get(argName) != a)
198 "Trying to add argument name multiple times for different Args: '"
199 + argMap.get(argName).getName() + ":" + argName
200 + "' and '" + a.getName() + ":" + argName
205 argMap.put(argName, a);
210 public ArgParser(String[] args)
212 this(args, false, null);
215 public ArgParser(String[] args, boolean initsubstitutions,
219 * Make a mutable new ArrayList so that shell globbing parser works.
220 * (When shell file globbing is used, there are a sequence of non-Arg
221 * arguments (which are the expanded globbed filenames) that need to be
222 * consumed by the --append/--argfile/etc Arg which is most easily done
223 * by removing these filenames from the list one at a time. This can't be
224 * done with an ArrayList made with only Arrays.asList(String[] args) as
225 * that is not mutable. )
227 this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
231 public ArgParser(List<String> args, boolean initsubstitutions)
233 this(args, initsubstitutions, false, null);
236 public ArgParser(List<String> args, boolean initsubstitutions,
237 boolean allowPrivate, BootstrapArgs bsa)
239 // do nothing if there are no "--" args and (some "-" args || >0 arg is
243 for (String arg : args)
245 if (arg.startsWith(DOUBLEDASH))
248 if (mixedExamples[1] == null)
250 mixedExamples[1] = arg;
253 else if (arg.startsWith("-") || arg.equals("open"))
256 if (mixedExamples[0] == null)
258 mixedExamples[0] = arg;
266 mixedArguments = true;
274 if (oldArguments || mixedArguments)
276 // leave it to the old style -- parse an empty list
277 parse(new ArrayList<String>(), false, false);
282 this.bootstrapArgs = bsa;
284 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
285 parse(args, initsubstitutions, allowPrivate);
288 private void parse(List<String> args, boolean initsubstitutions,
289 boolean allowPrivate)
291 this.substitutions = initsubstitutions;
294 * If the first argument does not start with "--" or "-" or is not "open",
295 * and is a filename that exists or a URL, it is probably a file/list of
296 * files to open so we insert an Arg.OPEN argument before it. This will
297 * mean the list of files at the start of the arguments are all opened
302 String arg0 = args.get(0);
304 && (!arg0.startsWith(DOUBLEDASH) && !arg0.startsWith("-")
305 && !arg0.equals("open") && (new File(arg0).exists()
306 || HttpUtils.startsWithHttpOrHttps(arg0))))
308 // insert "--open" at the start
309 args.add(0, Arg.OPEN.argString());
313 for (int i = 0; i < args.size(); i++)
315 String arg = args.get(i);
317 // look for double-dash, e.g. --arg
318 if (arg.startsWith(DOUBLEDASH))
320 String argName = null;
322 List<String> globVals = null; // for Opt.GLOB only
323 SubVals globSubVals = null; // also for use by Opt.GLOB only
324 String linkedId = null;
327 // look for equals e.g. --arg=value
328 int equalPos = arg.indexOf(EQUALS);
331 argName = arg.substring(DOUBLEDASH.length(), equalPos);
332 val = arg.substring(equalPos + 1);
336 argName = arg.substring(DOUBLEDASH.length());
339 // look for linked ID e.g. --arg[linkedID]
340 int idOpen = argName.indexOf('[');
341 int idClose = argName.indexOf(']');
342 if (idOpen > -1 && idClose == argName.length() - 1)
344 linkedId = argName.substring(idOpen + 1, idClose);
345 argName = argName.substring(0, idOpen);
348 // look for type modification e.g. --help-opening
349 int dashPos = argName.indexOf(SINGLEDASH);
352 String potentialArgName = argName.substring(0, dashPos);
353 Arg potentialArg = argMap.get(potentialArgName);
354 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
356 String typeName = argName.substring(dashPos + 1);
359 type = Type.valueOf(typeName);
360 } catch (IllegalArgumentException e)
364 argName = argName.substring(0, dashPos);
368 Arg a = argMap.get(argName);
369 // check for boolean prepended by "no" e.g. --nowrap
370 boolean negated = false;
373 if (argName.startsWith(NEGATESTRING) && argMap
374 .containsKey(argName.substring(NEGATESTRING.length())))
376 argName = argName.substring(NEGATESTRING.length());
377 a = argMap.get(argName);
382 // after all other args, look for Opt.PREFIXKEV args if still not
384 for (Arg potentialArg : EnumSet.allOf(Arg.class))
386 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
387 && argName.startsWith(potentialArg.getName())
390 val = argName.substring(potentialArg.getName().length())
392 argName = argName.substring(0,
393 potentialArg.getName().length());
401 // check for config errors
405 Console.error("Argument '" + arg + "' not recognised. Exiting.");
407 "Invalid argument used." + System.lineSeparator() + "Use"
408 + System.lineSeparator() + "jalview "
409 + Arg.HELP.argString() + System.lineSeparator()
410 + "for a usage statement.",
411 ExitCode.INVALID_ARGUMENT);
414 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
417 "Argument '" + a.argString() + "' is private. Ignoring.");
420 if (!a.hasOption(Opt.BOOLEAN) && negated)
422 // used "no" with a non-boolean option
423 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
424 + "' not a boolean option. Ignoring.");
427 if (!a.hasOption(Opt.STRING) && equalPos > -1)
429 // set --argname=value when arg does not accept values
430 Console.error("Argument '" + a.argString()
431 + "' does not expect a value (given as '" + arg
435 if (!a.hasOption(Opt.LINKED) && linkedId != null)
437 // set --argname[linkedId] when arg does not use linkedIds
438 Console.error("Argument '" + a.argString()
439 + "' does not expect a linked id (given as '" + arg
445 if (a.hasOption(Opt.STRING))
449 if (a.hasOption(Opt.GLOB))
451 // strip off and save the SubVals to be added individually later
452 globSubVals = new SubVals(val);
453 // make substitutions before looking for files
454 String fileGlob = makeSubstitutions(globSubVals.getContent(),
456 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
460 // val is already set -- will be saved in the ArgValue later in
466 // There is no "=" so value is next arg or args (possibly shell
468 if (i + 1 >= args.size())
470 // no value to take for arg, which wants a value
471 Console.error("Argument '" + a.getName()
472 + "' requires a value, none given. Ignoring.");
475 // deal with bash globs here (--arg val* is expanded before reaching
476 // the JVM). Note that SubVals cannot be used in this case.
477 // If using the --arg=val then the glob is preserved and Java globs
478 // will be used later. SubVals can be used.
479 if (a.hasOption(Opt.GLOB))
481 // if this is the first argument with a file list at the start of
482 // the args we add filenames from index i instead of i+1
483 globVals = getShellGlobbedFilenameValues(a, args, i + 1);
487 val = args.get(i + 1);
492 // make NOACTION adjustments
493 // default and auto counter increments
496 linkedIdAutoCounter++;
498 else if (a == Arg.SUBSTITUTIONS)
500 substitutions = !negated;
502 else if (a == Arg.SETARGFILE)
506 else if (a == Arg.UNSETARGFILE)
510 else if (a == Arg.ALL)
512 allLinkedIds = !negated;
514 else if (a == Arg.ALLSTRUCTURES)
516 allStructures = !negated;
519 if (a.hasOption(Opt.STORED))
521 // reset the lastOpenedLinkedIds list
522 this.storedLinkedIds = new ArrayList<>();
525 // this is probably only Arg.NEW and Arg.OPEN
526 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
528 // use the next default prefixed OPENLINKEDID
529 defaultLinkedId(true);
532 String autoCounterString = null;
533 String defaultLinkedId = defaultLinkedId(false);
534 boolean usingDefaultLinkedId = false;
535 if (a.hasOption(Opt.LINKED))
537 if (linkedId == null)
539 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWMULTIID)
540 && val.contains(MATCHALLLINKEDIDS))
542 // --output=*.ext is shorthand for --output {basename}.ext
543 // --output=*/*.ext is shorthand for
544 // --output {dirname}/{basename}.ext
545 // (or --image=*.ext)
546 linkedId = allLinkedIds ? MATCHALLLINKEDIDS
547 : MATCHOPENEDLINKEDIDS;
548 val = FileUtils.convertWildcardsToPath(val, MATCHALLLINKEDIDS,
549 LINKEDIDDIRNAME, LINKEDIDBASENAME);
551 else if (allLinkedIds && a.hasOption(Opt.ALLOWMULTIID))
553 linkedId = MATCHALLLINKEDIDS;
555 else if (a.hasOption(Opt.ALLOWMULTIID)
556 && this.storedLinkedIds != null
557 && this.storedLinkedIds.size() > 0)
559 linkedId = MATCHOPENEDLINKEDIDS;
563 // use default linkedId for linked arguments
564 linkedId = defaultLinkedId;
565 usingDefaultLinkedId = true;
566 Console.debug("Changing linkedId to '" + linkedId + "' from "
572 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
574 // turn {n} to the autoCounter
575 autoCounterString = Integer.toString(linkedIdAutoCounter);
576 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
578 Console.debug("Changing linkedId to '" + linkedId + "' from "
581 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
583 // turn {++n} to the incremented autoCounter
584 autoCounterString = Integer.toString(++linkedIdAutoCounter);
585 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
587 Console.debug("Changing linkedId to '" + linkedId + "' from "
593 // do not continue in this block for NOACTION args
594 if (a.hasOption(Opt.NOACTION))
597 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
599 // not dealing with both NODUPLICATEVALUES and GLOB
600 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
602 Console.error("Argument '" + a.argString()
603 + "' cannot contain a duplicate value ('" + val
604 + "'). Ignoring this and subsequent occurrences.");
608 // check for unique id
609 SubVals subvals = new SubVals(val);
610 boolean addNewSubVals = false;
611 String id = subvals.get(ArgValues.ID);
612 if (id != null && avm.hasId(a, id))
614 Console.error("Argument '" + a.argString()
615 + "' has a duplicate id ('" + id + "'). Ignoring.");
619 // set allstructures to all non-primary structure options in this linked
620 // id if --allstructures has been set
622 && (a.getType() == Type.STRUCTURE
623 || a.getType() == Type.STRUCTUREIMAGE)
624 && !a.hasOption(Opt.PRIMARY))
626 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
627 // && !subvals.has("structureid"))
629 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
630 addNewSubVals = true;
634 ArgValues avs = avm.getOrCreateArgValues(a);
636 // store appropriate String value(s)
637 if (a.hasOption(Opt.STRING))
639 if (a.hasOption(Opt.GLOB) && globVals != null
640 && globVals.size() > 0)
642 Enumeration<String> gve = Collections.enumeration(globVals);
643 while (gve.hasMoreElements())
645 String v = gve.nextElement();
646 SubVals vsv = new SubVals(globSubVals, v);
647 addValue(linkedId, type, avs, vsv, v, argIndex++, true);
648 // if we're using defaultLinkedId and the arg increments the
650 if (gve.hasMoreElements() && usingDefaultLinkedId
651 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
653 // increment the default linkedId
654 linkedId = defaultLinkedId(true);
655 // get new avm and avs
656 avm = linkedArgs.get(linkedId);
657 avs = avm.getOrCreateArgValues(a);
663 // addValue(linkedId, type, avs, val, argIndex, true);
664 addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
665 val, argIndex, true);
668 else if (a.hasOption(Opt.BOOLEAN))
670 setBoolean(linkedId, type, avs, !negated, argIndex);
671 setNegated(linkedId, avs, negated);
673 else if (a.hasOption(Opt.UNARY))
675 setBoolean(linkedId, type, avs, true, argIndex);
678 // remove the '*' or 'open*' linkedId that should be empty if it was
680 if ((MATCHALLLINKEDIDS.equals(linkedId)
681 || MATCHOPENEDLINKEDIDS.equals(linkedId))
682 && linkedArgs.containsKey(linkedId))
684 linkedArgs.remove(linkedId);
690 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
693 incrementCount(linkedId, avs);
696 // store in appropriate place
697 if (a.hasOption(Opt.LINKED))
699 // store the order of linkedIds
700 if (!linkedOrder.contains(linkedId))
701 linkedOrder.add(linkedId);
704 // store arg in the list of args used
705 if (!argList.contains(a))
709 private String defaultLinkedId(boolean increment)
711 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
712 .append(Integer.toString(defaultLinkedIdCounter)).toString();
715 while (linkedArgs.containsKey(defaultLinkedId))
717 defaultLinkedIdCounter++;
718 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
719 .append(Integer.toString(defaultLinkedIdCounter))
723 getOrCreateLinkedArgValuesMap(defaultLinkedId);
724 return defaultLinkedId;
727 public String makeSubstitutions(String val, String linkedId)
729 if (!this.substitutions || val == null)
734 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
736 int closeBracket = val.indexOf(']');
737 if (val.length() == closeBracket)
739 subvals = val.substring(0, closeBracket + 1);
740 rest = val.substring(closeBracket + 1);
747 if (rest.contains(LINKEDIDAUTOCOUNTER))
748 rest = rest.replace(LINKEDIDAUTOCOUNTER,
749 String.valueOf(linkedIdAutoCounter));
750 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
751 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
752 String.valueOf(++linkedIdAutoCounter));
753 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
754 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
755 String.valueOf(defaultLinkedIdCounter));
756 ArgValuesMap avm = linkedArgs.get(linkedId);
759 if (rest.contains(LINKEDIDBASENAME))
761 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
763 if (rest.contains(LINKEDIDEXTENSION))
765 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
767 if (rest.contains(LINKEDIDDIRNAME))
769 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
774 if (rest.contains(ARGFILEBASENAME))
776 rest = rest.replace(ARGFILEBASENAME,
777 FileUtils.getBasename(new File(argFile)));
779 if (rest.contains(ARGFILEDIRNAME))
781 rest = rest.replace(ARGFILEDIRNAME,
782 FileUtils.getDirname(new File(argFile)));
786 return new StringBuilder(subvals).append(rest).toString();
790 * A helper method to take a list of String args where we're expecting
791 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
792 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
793 * "file2", "file3"} *and remove these from the original list object* so that
794 * processing can continue from where it has left off, e.g. args has become
795 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
796 * carries on from the next --arg if available.
798 protected static List<String> getShellGlobbedFilenameValues(Arg a,
799 List<String> args, int i)
801 List<String> vals = new ArrayList<>();
802 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
804 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
805 if (!a.hasOption(Opt.GLOB))
811 public BootstrapArgs getBootstrapArgs()
813 return bootstrapArgs;
816 public boolean isSet(Arg a)
818 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
821 public boolean isSetAtAll(Arg a)
823 for (String linkedId : linkedOrder)
825 if (isSet(linkedId, a))
831 public boolean isSet(String linkedId, Arg a)
833 ArgValuesMap avm = linkedArgs.get(linkedId);
834 return avm == null ? false : avm.containsArg(a);
837 public boolean getBoolean(Arg a)
839 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
841 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
844 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
847 public boolean getBool(String linkedId, Arg a)
849 ArgValuesMap avm = linkedArgs.get(linkedId);
851 return a.getDefaultBoolValue();
852 ArgValues avs = avm.getArgValues(a);
853 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
856 public List<String> getLinkedIds()
861 public ArgValuesMap getLinkedArgs(String id)
863 return linkedArgs.get(id);
867 public String toString()
869 StringBuilder sb = new StringBuilder();
870 sb.append("UNLINKED\n");
871 sb.append(argValuesMapToString(linkedArgs.get(null)));
872 if (getLinkedIds() != null)
874 sb.append("LINKED\n");
875 for (String id : getLinkedIds())
877 // already listed these as UNLINKED args
881 ArgValuesMap avm = getLinkedArgs(id);
882 sb.append("ID: '").append(id).append("'\n");
883 sb.append(argValuesMapToString(avm));
886 return sb.toString();
889 private static String argValuesMapToString(ArgValuesMap avm)
893 StringBuilder sb = new StringBuilder();
894 for (Arg a : avm.getArgKeys())
896 ArgValues v = avm.getArgValues(a);
897 sb.append(v.toString());
900 return sb.toString();
903 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
904 boolean initsubstitutions, BootstrapArgs bsa)
906 List<File> argFiles = new ArrayList<>();
908 for (String pattern : argFilenameGlobs)
910 // I don't think we want to dedup files, making life easier
911 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
914 return parseArgFileList(argFiles, initsubstitutions, bsa);
917 public static ArgParser parseArgFileList(List<File> argFiles,
918 boolean initsubstitutions, BootstrapArgs bsa)
920 List<String> argsList = new ArrayList<>();
921 for (File argFile : argFiles)
923 if (!argFile.exists())
925 String message = Arg.ARGFILE.argString() + EQUALS + "\""
926 + argFile.getPath() + "\": File does not exist.";
927 Jalview.exit(message, ExitCode.FILE_NOT_FOUND);
931 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
932 .append(EQUALS).append(argFile.getCanonicalPath())
934 argsList.add(setargfile);
935 argsList.addAll(readArgFile(argFile));
936 argsList.add(Arg.UNSETARGFILE.argString());
937 } catch (IOException e)
939 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
940 + "\": File could not be read.";
941 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
944 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
946 return new ArgParser(argsList, initsubstitutions, true, bsa);
949 protected static List<String> readArgFile(File argFile)
951 List<String> args = new ArrayList<>();
952 if (argFile != null && argFile.exists())
956 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
958 if (line != null && line.length() > 0
959 && line.charAt(0) != ARGFILECOMMENT)
962 } catch (IOException e)
964 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
965 + "\": File could not be read.";
966 Console.debug(message, e);
967 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
973 public static enum Position
979 * get from following Arg of type a or subval of same name (lowercase)
981 public static String getValueFromSubValOrArg(ArgValuesMap avm,
982 ArgValue av, Arg a, SubVals sv)
984 return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
988 * get from following Arg of type a or subval key or preference pref or
991 public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
992 Arg a, SubVals sv, String key, String pref, String def)
994 return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref,
999 * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
1000 * Arg of type a or subval key or preference pref or default def
1002 public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1003 Position pos, ArgValue av, SubVals sv, String key, String pref,
1006 return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
1007 sv, key, pref, def);
1010 public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
1011 ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
1012 String key, String pref, String def)
1016 String value = null;
1017 if (sv != null && sv.has(key) && sv.get(key) != null)
1019 value = ap == null ? sv.get(key)
1020 : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
1022 else if (avm != null && avm.containsArg(a))
1024 if (pos == Position.FIRST && avm.getValue(a) != null)
1025 value = avm.getValue(a);
1026 else if (pos == Position.BEFORE
1027 && avm.getClosestPreviousArgValueOfArg(av, a) != null)
1028 value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
1029 else if (pos == Position.AFTER
1030 && avm.getClosestNextArgValueOfArg(av, a) != null)
1031 value = avm.getClosestNextArgValueOfArg(av, a).getValue();
1033 // look for allstructures subval for Type.STRUCTURE*
1034 Arg arg = av.getArg();
1035 if (value == null && arg.hasOption(Opt.PRIMARY)
1036 && arg.getType() == Type.STRUCTURE
1037 && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
1038 || a.getType() == Type.STRUCTUREIMAGE))
1040 ArgValue av2 = avm.getArgValueOfArgWithSubValKey(a,
1041 Arg.ALLSTRUCTURES.getName());
1044 value = av2.getValue();
1050 value = pref != null ? Cache.getDefault(pref, def) : def;
1055 public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
1058 return getFromSubValArgOrPref(avm, a, sv, null, null, false);
1061 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1062 SubVals sv, String key, String pref, boolean def)
1064 return getFromSubValArgOrPref(avm, a, sv, key, pref, def, false);
1067 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1068 SubVals sv, String key, String pref, boolean def,
1071 if ((key == null && a == null) || (sv == null && a == null))
1074 boolean usingArgKey = false;
1081 String nokey = ArgParser.NEGATESTRING + key;
1083 // look for key or nokey in subvals first (if using Arg check options)
1086 // check for true boolean
1087 if (sv.has(key) && sv.get(key) != null)
1091 if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
1094 "Looking for boolean in subval from non-boolean/non-unary Arg "
1099 return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
1102 // check for negative boolean (subval "no..." will be "true")
1103 if (sv.has(nokey) && sv.get(nokey) != null)
1107 if (!(a.hasOption(Opt.BOOLEAN)))
1110 "Looking for negative boolean in subval from non-boolean Arg "
1115 return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
1120 if (avm != null && avm.containsArg(a))
1121 return avm.getBoolean(a);
1123 // return preference or default
1124 boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
1125 return pref != null ? (invertPref ? !prefVal : prefVal) : def;
1128 // the following methods look for the "*" linkedId and add the argvalue to all
1129 // linkedId ArgValues if it does.
1131 * This version inserts the subvals sv into all created values
1133 private void addValue(String linkedId, Type type, ArgValues avs,
1134 SubVals sv, String v, int argIndex, boolean doSubs)
1136 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
1140 private void addValue(String linkedId, Type type, ArgValues avs, String v,
1141 int argIndex, boolean doSubs)
1143 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, null, v, false,
1147 private void setBoolean(String linkedId, Type type, ArgValues avs,
1148 boolean b, int argIndex)
1150 this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
1151 b, argIndex, false);
1154 private void setNegated(String linkedId, ArgValues avs, boolean b)
1156 this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
1160 private void incrementCount(String linkedId, ArgValues avs)
1162 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
1163 null, false, 0, false);
1168 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1171 private void argValueOperation(Op op, String linkedId, Type type,
1172 ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
1175 // default to merge subvals if subvals are provided
1176 argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
1181 * The following operations look for the "*" and "open*" linkedIds and add the
1182 * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1183 * supplied, they are inserted into all new set values.
1186 * The ArgParser.Op operation
1188 * The String linkedId from the ArgValuesMap
1190 * The Arg.Type to attach to this ArgValue
1192 * The ArgValues for this linkedId
1194 * Use these SubVals on the ArgValue
1196 * Merge the SubVals with any existing on the value. False will
1197 * replace unless sv is null
1199 * The value of the ArgValue (may contain subvals).
1201 * The boolean value of the ArgValue.
1203 * The argIndex for the ArgValue.
1205 * Whether to perform substitutions on the subvals and value.
1207 private void argValueOperation(Op op, String linkedId, Type type,
1208 ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
1209 int argIndex, boolean doSubs)
1213 List<String> wildcardLinkedIds = null;
1214 if (a.hasOption(Opt.ALLOWMULTIID))
1218 case MATCHALLLINKEDIDS:
1219 wildcardLinkedIds = getLinkedIds();
1221 case MATCHOPENEDLINKEDIDS:
1222 wildcardLinkedIds = this.storedLinkedIds;
1227 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1228 // to storedLinkedIds
1229 if (linkedId != null && wildcardLinkedIds == null
1230 && a.hasOption(Opt.STORED)
1231 && !storedLinkedIds.contains(linkedId))
1233 storedLinkedIds.add(linkedId);
1236 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1238 if (wildcardLinkedIds != null)
1240 for (String id : wildcardLinkedIds)
1242 // skip incorrectly stored wildcard ids!
1243 if (id == null || MATCHALLLINKEDIDS.equals(id)
1244 || MATCHOPENEDLINKEDIDS.equals(id))
1248 ArgValuesMap avm = linkedArgs.get(id);
1249 // don't set an output if there isn't an input
1250 if (a.hasOption(Opt.REQUIREINPUT)
1251 && !avm.hasArgWithOption(Opt.INPUT))
1254 ArgValues tavs = avm.getOrCreateArgValues(a);
1264 sv = new SubVals(sv, val, merge);
1265 val = makeSubstitutions(sv.getContent(), id);
1267 tavs.addValue(sv, type, val, argIndex, true);
1273 val = makeSubstitutions(v, id);
1275 tavs.addValue(type, val, argIndex, true);
1277 finaliseStoringArgValue(id, tavs);
1281 tavs.setBoolean(type, b, argIndex, true);
1282 finaliseStoringArgValue(id, tavs);
1286 tavs.setNegated(b, true);
1289 case INCREMENTCOUNT:
1290 tavs.incrementCount();
1300 else // no wildcard linkedId -- do it simpler
1310 val = makeSubstitutions(v, linkedId);
1311 sv = new SubVals(sv, val);
1313 avs.addValue(sv, type, val, argIndex, false);
1319 val = makeSubstitutions(v, linkedId);
1321 avs.addValue(type, val, argIndex, false);
1323 finaliseStoringArgValue(linkedId, avs);
1327 avs.setBoolean(type, b, argIndex, false);
1328 finaliseStoringArgValue(linkedId, avs);
1332 avs.setNegated(b, false);
1335 case INCREMENTCOUNT:
1336 avs.incrementCount();
1345 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1347 if (linkedArgs.containsKey(linkedId)
1348 && linkedArgs.get(linkedId) != null)
1349 return linkedArgs.get(linkedId);
1351 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1352 return linkedArgs.get(linkedId);
1355 public boolean isOldStyle()
1357 return oldArguments;
1360 public boolean isMixedStyle()
1362 return mixedArguments;
1365 public String[] getMixedExamples()
1367 return mixedExamples;