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(STDOUTFILENAME))
254 || arg.equals("open"))
257 if (mixedExamples[0] == null)
259 mixedExamples[0] = arg;
267 mixedArguments = true;
275 if (oldArguments || mixedArguments)
277 // leave it to the old style -- parse an empty list
278 parse(new ArrayList<String>(), false, false);
283 this.bootstrapArgs = bsa;
285 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
286 parse(args, initsubstitutions, allowPrivate);
289 private void parse(List<String> args, boolean initsubstitutions,
290 boolean allowPrivate)
292 this.substitutions = initsubstitutions;
295 * If the first argument does not start with "--" or "-" or is not "open",
296 * and is a filename that exists or a URL, it is probably a file/list of
297 * files to open so we insert an Arg.OPEN argument before it. This will
298 * mean the list of files at the start of the arguments are all opened
303 String arg0 = args.get(0);
305 && (!arg0.startsWith(DOUBLEDASH) && !arg0.startsWith("-")
306 && !arg0.equals("open") && (new File(arg0).exists()
307 || HttpUtils.startsWithHttpOrHttps(arg0))))
309 // insert "--open" at the start
310 args.add(0, Arg.OPEN.argString());
314 for (int i = 0; i < args.size(); i++)
316 String arg = args.get(i);
318 // look for double-dash, e.g. --arg
319 if (arg.startsWith(DOUBLEDASH))
321 String argName = null;
323 List<String> globVals = null; // for Opt.GLOB only
324 SubVals globSubVals = null; // also for use by Opt.GLOB only
325 String linkedId = null;
328 // look for equals e.g. --arg=value
329 int equalPos = arg.indexOf(EQUALS);
332 argName = arg.substring(DOUBLEDASH.length(), equalPos);
333 val = arg.substring(equalPos + 1);
337 argName = arg.substring(DOUBLEDASH.length());
340 // look for linked ID e.g. --arg[linkedID]
341 int idOpen = argName.indexOf('[');
342 int idClose = argName.indexOf(']');
343 if (idOpen > -1 && idClose == argName.length() - 1)
345 linkedId = argName.substring(idOpen + 1, idClose);
346 argName = argName.substring(0, idOpen);
349 // look for type modification e.g. --help-opening
350 int dashPos = argName.indexOf(SINGLEDASH);
353 String potentialArgName = argName.substring(0, dashPos);
354 Arg potentialArg = argMap.get(potentialArgName);
355 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
357 String typeName = argName.substring(dashPos + 1);
360 type = Type.valueOf(typeName);
361 } catch (IllegalArgumentException e)
365 argName = argName.substring(0, dashPos);
369 Arg a = argMap.get(argName);
370 // check for boolean prepended by "no" e.g. --nowrap
371 boolean negated = false;
374 if (argName.startsWith(NEGATESTRING) && argMap
375 .containsKey(argName.substring(NEGATESTRING.length())))
377 argName = argName.substring(NEGATESTRING.length());
378 a = argMap.get(argName);
383 // after all other args, look for Opt.PREFIXKEV args if still not
385 for (Arg potentialArg : EnumSet.allOf(Arg.class))
387 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
388 && argName.startsWith(potentialArg.getName())
391 val = argName.substring(potentialArg.getName().length())
393 argName = argName.substring(0,
394 potentialArg.getName().length());
402 // check for config errors
406 Console.error("Argument '" + arg + "' not recognised. Exiting.");
408 "Invalid argument used." + System.lineSeparator() + "Use"
409 + System.lineSeparator() + "jalview "
410 + Arg.HELP.argString() + System.lineSeparator()
411 + "for a usage statement.",
412 ExitCode.INVALID_ARGUMENT);
415 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
418 "Argument '" + a.argString() + "' is private. Ignoring.");
421 if (!a.hasOption(Opt.BOOLEAN) && negated)
423 // used "no" with a non-boolean option
424 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
425 + "' not a boolean option. Ignoring.");
428 if (!a.hasOption(Opt.STRING) && equalPos > -1)
430 // set --argname=value when arg does not accept values
431 Console.error("Argument '" + a.argString()
432 + "' does not expect a value (given as '" + arg
436 if (!a.hasOption(Opt.LINKED) && linkedId != null)
438 // set --argname[linkedId] when arg does not use linkedIds
439 Console.error("Argument '" + a.argString()
440 + "' does not expect a linked id (given as '" + arg
446 if (a.hasOption(Opt.STRING))
450 if (a.hasOption(Opt.GLOB))
452 // strip off and save the SubVals to be added individually later
453 globSubVals = new SubVals(val);
454 // make substitutions before looking for files
455 String fileGlob = makeSubstitutions(globSubVals.getContent(),
457 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
461 // val is already set -- will be saved in the ArgValue later in
467 // There is no "=" so value is next arg or args (possibly shell
469 if (i + 1 >= args.size())
471 // no value to take for arg, which wants a value
472 Console.error("Argument '" + a.getName()
473 + "' requires a value, none given. Ignoring.");
476 // deal with bash globs here (--arg val* is expanded before reaching
477 // the JVM). Note that SubVals cannot be used in this case.
478 // If using the --arg=val then the glob is preserved and Java globs
479 // will be used later. SubVals can be used.
480 if (a.hasOption(Opt.GLOB))
482 // if this is the first argument with a file list at the start of
483 // the args we add filenames from index i instead of i+1
484 globVals = getShellGlobbedFilenameValues(a, args, i + 1);
488 val = args.get(i + 1);
493 // make NOACTION adjustments
494 // default and auto counter increments
497 linkedIdAutoCounter++;
499 else if (a == Arg.SUBSTITUTIONS)
501 substitutions = !negated;
503 else if (a == Arg.SETARGFILE)
507 else if (a == Arg.UNSETARGFILE)
511 else if (a == Arg.ALL)
513 allLinkedIds = !negated;
515 else if (a == Arg.ALLSTRUCTURES)
517 allStructures = !negated;
520 if (a.hasOption(Opt.STORED))
522 // reset the lastOpenedLinkedIds list
523 this.storedLinkedIds = new ArrayList<>();
526 // this is probably only Arg.NEW and Arg.OPEN
527 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
529 // use the next default prefixed OPENLINKEDID
530 defaultLinkedId(true);
533 String autoCounterString = null;
534 String defaultLinkedId = defaultLinkedId(false);
535 boolean usingDefaultLinkedId = false;
536 if (a.hasOption(Opt.LINKED))
538 if (linkedId == null)
540 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWMULTIID)
541 && val.contains(MATCHALLLINKEDIDS))
543 // --output=*.ext is shorthand for --output {basename}.ext
544 // --output=*/*.ext is shorthand for
545 // --output {dirname}/{basename}.ext
546 // (or --image=*.ext)
547 linkedId = allLinkedIds ? MATCHALLLINKEDIDS
548 : MATCHOPENEDLINKEDIDS;
549 val = FileUtils.convertWildcardsToPath(val, MATCHALLLINKEDIDS,
550 LINKEDIDDIRNAME, LINKEDIDBASENAME);
552 else if (allLinkedIds && a.hasOption(Opt.ALLOWMULTIID))
554 linkedId = MATCHALLLINKEDIDS;
556 else if (a.hasOption(Opt.ALLOWMULTIID)
557 && this.storedLinkedIds != null
558 && this.storedLinkedIds.size() > 0)
560 linkedId = MATCHOPENEDLINKEDIDS;
564 // use default linkedId for linked arguments
565 linkedId = defaultLinkedId;
566 usingDefaultLinkedId = true;
567 Console.debug("Changing linkedId to '" + linkedId + "' from "
573 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
575 // turn {n} to the autoCounter
576 autoCounterString = Integer.toString(linkedIdAutoCounter);
577 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
579 Console.debug("Changing linkedId to '" + linkedId + "' from "
582 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
584 // turn {++n} to the incremented autoCounter
585 autoCounterString = Integer.toString(++linkedIdAutoCounter);
586 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
588 Console.debug("Changing linkedId to '" + linkedId + "' from "
594 // do not continue in this block for NOACTION args
595 if (a.hasOption(Opt.NOACTION))
598 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
600 // not dealing with both NODUPLICATEVALUES and GLOB
601 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
603 Console.error("Argument '" + a.argString()
604 + "' cannot contain a duplicate value ('" + val
605 + "'). Ignoring this and subsequent occurrences.");
609 // check for unique id
610 SubVals subvals = new SubVals(val);
611 boolean addNewSubVals = false;
612 String id = subvals.get(ArgValues.ID);
613 if (id != null && avm.hasId(a, id))
615 Console.error("Argument '" + a.argString()
616 + "' has a duplicate id ('" + id + "'). Ignoring.");
620 // set allstructures to all non-primary structure options in this linked
621 // id if --allstructures has been set
623 && (a.getType() == Type.STRUCTURE
624 || a.getType() == Type.STRUCTUREIMAGE)
625 && !a.hasOption(Opt.PRIMARY))
627 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
628 // && !subvals.has("structureid"))
630 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
631 addNewSubVals = true;
635 ArgValues avs = avm.getOrCreateArgValues(a);
637 // store appropriate String value(s)
638 if (a.hasOption(Opt.STRING))
640 if (a.hasOption(Opt.GLOB) && globVals != null
641 && globVals.size() > 0)
643 Enumeration<String> gve = Collections.enumeration(globVals);
644 while (gve.hasMoreElements())
646 String v = gve.nextElement();
647 SubVals vsv = new SubVals(globSubVals, v);
648 addValue(linkedId, type, avs, vsv, v, argIndex++, true);
649 // if we're using defaultLinkedId and the arg increments the
651 if (gve.hasMoreElements() && usingDefaultLinkedId
652 && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
654 // increment the default linkedId
655 linkedId = defaultLinkedId(true);
656 // get new avm and avs
657 avm = linkedArgs.get(linkedId);
658 avs = avm.getOrCreateArgValues(a);
664 // addValue(linkedId, type, avs, val, argIndex, true);
665 addValue(linkedId, type, avs, addNewSubVals ? subvals : null,
666 val, argIndex, true);
669 else if (a.hasOption(Opt.BOOLEAN))
671 setBoolean(linkedId, type, avs, !negated, argIndex);
672 setNegated(linkedId, avs, negated);
674 else if (a.hasOption(Opt.UNARY))
676 setBoolean(linkedId, type, avs, true, argIndex);
679 // remove the '*' or 'open*' linkedId that should be empty if it was
681 if ((MATCHALLLINKEDIDS.equals(linkedId)
682 || MATCHOPENEDLINKEDIDS.equals(linkedId))
683 && linkedArgs.containsKey(linkedId))
685 linkedArgs.remove(linkedId);
691 private void finaliseStoringArgValue(String linkedId, ArgValues avs)
694 incrementCount(linkedId, avs);
697 // store in appropriate place
698 if (a.hasOption(Opt.LINKED))
700 // store the order of linkedIds
701 if (!linkedOrder.contains(linkedId))
702 linkedOrder.add(linkedId);
705 // store arg in the list of args used
706 if (!argList.contains(a))
710 private String defaultLinkedId(boolean increment)
712 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
713 .append(Integer.toString(defaultLinkedIdCounter)).toString();
716 while (linkedArgs.containsKey(defaultLinkedId))
718 defaultLinkedIdCounter++;
719 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
720 .append(Integer.toString(defaultLinkedIdCounter))
724 getOrCreateLinkedArgValuesMap(defaultLinkedId);
725 return defaultLinkedId;
728 public String makeSubstitutions(String val, String linkedId)
730 if (!this.substitutions || val == null)
735 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
737 int closeBracket = val.indexOf(']');
738 if (val.length() == closeBracket)
740 subvals = val.substring(0, closeBracket + 1);
741 rest = val.substring(closeBracket + 1);
748 if (rest.contains(LINKEDIDAUTOCOUNTER))
749 rest = rest.replace(LINKEDIDAUTOCOUNTER,
750 String.valueOf(linkedIdAutoCounter));
751 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
752 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
753 String.valueOf(++linkedIdAutoCounter));
754 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
755 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
756 String.valueOf(defaultLinkedIdCounter));
757 ArgValuesMap avm = linkedArgs.get(linkedId);
760 if (rest.contains(LINKEDIDBASENAME))
762 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
764 if (rest.contains(LINKEDIDEXTENSION))
766 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
768 if (rest.contains(LINKEDIDDIRNAME))
770 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
775 if (rest.contains(ARGFILEBASENAME))
777 rest = rest.replace(ARGFILEBASENAME,
778 FileUtils.getBasename(new File(argFile)));
780 if (rest.contains(ARGFILEDIRNAME))
782 rest = rest.replace(ARGFILEDIRNAME,
783 FileUtils.getDirname(new File(argFile)));
787 return new StringBuilder(subvals).append(rest).toString();
791 * A helper method to take a list of String args where we're expecting
792 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
793 * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
794 * "file2", "file3"} *and remove these from the original list object* so that
795 * processing can continue from where it has left off, e.g. args has become
796 * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
797 * carries on from the next --arg if available.
799 protected static List<String> getShellGlobbedFilenameValues(Arg a,
800 List<String> args, int i)
802 List<String> vals = new ArrayList<>();
803 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
805 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
806 if (!a.hasOption(Opt.GLOB))
812 public BootstrapArgs getBootstrapArgs()
814 return bootstrapArgs;
817 public boolean isSet(Arg a)
819 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
822 public boolean isSetAtAll(Arg a)
824 for (String linkedId : linkedOrder)
826 if (isSet(linkedId, a))
832 public boolean isSet(String linkedId, Arg a)
834 ArgValuesMap avm = linkedArgs.get(linkedId);
835 return avm == null ? false : avm.containsArg(a);
838 public boolean getBoolean(Arg a)
840 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
842 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
845 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
848 public boolean getBool(String linkedId, Arg a)
850 ArgValuesMap avm = linkedArgs.get(linkedId);
852 return a.getDefaultBoolValue();
853 ArgValues avs = avm.getArgValues(a);
854 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
857 public List<String> getLinkedIds()
862 public ArgValuesMap getLinkedArgs(String id)
864 return linkedArgs.get(id);
868 public String toString()
870 StringBuilder sb = new StringBuilder();
871 sb.append("UNLINKED\n");
872 sb.append(argValuesMapToString(linkedArgs.get(null)));
873 if (getLinkedIds() != null)
875 sb.append("LINKED\n");
876 for (String id : getLinkedIds())
878 // already listed these as UNLINKED args
882 ArgValuesMap avm = getLinkedArgs(id);
883 sb.append("ID: '").append(id).append("'\n");
884 sb.append(argValuesMapToString(avm));
887 return sb.toString();
890 private static String argValuesMapToString(ArgValuesMap avm)
894 StringBuilder sb = new StringBuilder();
895 for (Arg a : avm.getArgKeys())
897 ArgValues v = avm.getArgValues(a);
898 sb.append(v.toString());
901 return sb.toString();
904 public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
905 boolean initsubstitutions, BootstrapArgs bsa)
907 List<File> argFiles = new ArrayList<>();
909 for (String pattern : argFilenameGlobs)
911 // I don't think we want to dedup files, making life easier
912 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
915 return parseArgFileList(argFiles, initsubstitutions, bsa);
918 public static ArgParser parseArgFileList(List<File> argFiles,
919 boolean initsubstitutions, BootstrapArgs bsa)
921 List<String> argsList = new ArrayList<>();
922 for (File argFile : argFiles)
924 if (!argFile.exists())
926 String message = Arg.ARGFILE.argString() + EQUALS + "\""
927 + argFile.getPath() + "\": File does not exist.";
928 Jalview.exit(message, ExitCode.FILE_NOT_FOUND);
932 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
933 .append(EQUALS).append(argFile.getCanonicalPath())
935 argsList.add(setargfile);
936 argsList.addAll(readArgFile(argFile));
937 argsList.add(Arg.UNSETARGFILE.argString());
938 } catch (IOException e)
940 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
941 + "\": File could not be read.";
942 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
945 // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
947 return new ArgParser(argsList, initsubstitutions, true, bsa);
950 protected static List<String> readArgFile(File argFile)
952 List<String> args = new ArrayList<>();
953 if (argFile != null && argFile.exists())
957 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
959 if (line != null && line.length() > 0
960 && line.charAt(0) != ARGFILECOMMENT)
963 } catch (IOException e)
965 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
966 + "\": File could not be read.";
967 Console.debug(message, e);
968 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
974 public static enum Position
980 * get from following Arg of type a or subval of same name (lowercase)
982 public static String getValueFromSubValOrArg(ArgValuesMap avm,
983 ArgValue av, Arg a, SubVals sv)
985 return getFromSubValArgOrPref(avm, av, a, sv, null, null, null);
989 * get from following Arg of type a or subval key or preference pref or
992 public static String getFromSubValArgOrPref(ArgValuesMap avm, ArgValue av,
993 Arg a, SubVals sv, String key, String pref, String def)
995 return getFromSubValArgOrPref(avm, a, Position.AFTER, av, sv, key, pref,
1000 * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
1001 * Arg of type a or subval key or preference pref or default def
1003 public static String getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1004 Position pos, ArgValue av, SubVals sv, String key, String pref,
1007 return getFromSubValArgOrPrefWithSubstitutions(null, avm, a, pos, av,
1008 sv, key, pref, def);
1011 public static String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap,
1012 ArgValuesMap avm, Arg a, Position pos, ArgValue av, SubVals sv,
1013 String key, String pref, String def)
1017 String value = null;
1018 if (sv != null && sv.has(key) && sv.get(key) != null)
1020 value = ap == null ? sv.get(key)
1021 : sv.getWithSubstitutions(ap, avm.getLinkedId(), key);
1023 else if (avm != null && avm.containsArg(a))
1025 if (pos == Position.FIRST && avm.getValue(a) != null)
1026 value = avm.getValue(a);
1027 else if (pos == Position.BEFORE
1028 && avm.getClosestPreviousArgValueOfArg(av, a) != null)
1029 value = avm.getClosestPreviousArgValueOfArg(av, a).getValue();
1030 else if (pos == Position.AFTER
1031 && avm.getClosestNextArgValueOfArg(av, a) != null)
1032 value = avm.getClosestNextArgValueOfArg(av, a).getValue();
1034 // look for allstructures subval for Type.STRUCTURE*
1035 Arg arg = av.getArg();
1036 if (value == null && arg.hasOption(Opt.PRIMARY)
1037 && arg.getType() == Type.STRUCTURE
1038 && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
1039 || a.getType() == Type.STRUCTUREIMAGE))
1041 ArgValue av2 = avm.getArgValueOfArgWithSubValKey(a,
1042 Arg.ALLSTRUCTURES.getName());
1045 value = av2.getValue();
1051 value = pref != null ? Cache.getDefault(pref, def) : def;
1056 public static boolean getBoolFromSubValOrArg(ArgValuesMap avm, Arg a,
1059 return getFromSubValArgOrPref(avm, a, sv, null, null, false);
1062 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1063 SubVals sv, String key, String pref, boolean def)
1065 return getFromSubValArgOrPref(avm, a, sv, key, pref, def, false);
1068 public static boolean getFromSubValArgOrPref(ArgValuesMap avm, Arg a,
1069 SubVals sv, String key, String pref, boolean def,
1072 if ((key == null && a == null) || (sv == null && a == null))
1075 boolean usingArgKey = false;
1082 String nokey = ArgParser.NEGATESTRING + key;
1084 // look for key or nokey in subvals first (if using Arg check options)
1087 // check for true boolean
1088 if (sv.has(key) && sv.get(key) != null)
1092 if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
1095 "Looking for boolean in subval from non-boolean/non-unary Arg "
1100 return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
1103 // check for negative boolean (subval "no..." will be "true")
1104 if (sv.has(nokey) && sv.get(nokey) != null)
1108 if (!(a.hasOption(Opt.BOOLEAN)))
1111 "Looking for negative boolean in subval from non-boolean Arg "
1116 return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
1121 if (avm != null && avm.containsArg(a))
1122 return avm.getBoolean(a);
1124 // return preference or default
1125 boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
1126 return pref != null ? (invertPref ? !prefVal : prefVal) : def;
1129 // the following methods look for the "*" linkedId and add the argvalue to all
1130 // linkedId ArgValues if it does.
1132 * This version inserts the subvals sv into all created values
1134 private void addValue(String linkedId, Type type, ArgValues avs,
1135 SubVals sv, String v, int argIndex, boolean doSubs)
1137 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, sv, v, false,
1141 private void addValue(String linkedId, Type type, ArgValues avs, String v,
1142 int argIndex, boolean doSubs)
1144 this.argValueOperation(Op.ADDVALUE, linkedId, type, avs, null, v, false,
1148 private void setBoolean(String linkedId, Type type, ArgValues avs,
1149 boolean b, int argIndex)
1151 this.argValueOperation(Op.SETBOOLEAN, linkedId, type, avs, null, null,
1152 b, argIndex, false);
1155 private void setNegated(String linkedId, ArgValues avs, boolean b)
1157 this.argValueOperation(Op.SETNEGATED, linkedId, null, avs, null, null,
1161 private void incrementCount(String linkedId, ArgValues avs)
1163 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, avs, null,
1164 null, false, 0, false);
1169 ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1172 private void argValueOperation(Op op, String linkedId, Type type,
1173 ArgValues avs, SubVals sv, String v, boolean b, int argIndex,
1176 // default to merge subvals if subvals are provided
1177 argValueOperation(op, linkedId, type, avs, sv, true, v, b, argIndex,
1182 * The following operations look for the "*" and "open*" linkedIds and add the
1183 * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1184 * supplied, they are inserted into all new set values.
1187 * The ArgParser.Op operation
1189 * The String linkedId from the ArgValuesMap
1191 * The Arg.Type to attach to this ArgValue
1193 * The ArgValues for this linkedId
1195 * Use these SubVals on the ArgValue
1197 * Merge the SubVals with any existing on the value. False will
1198 * replace unless sv is null
1200 * The value of the ArgValue (may contain subvals).
1202 * The boolean value of the ArgValue.
1204 * The argIndex for the ArgValue.
1206 * Whether to perform substitutions on the subvals and value.
1208 private void argValueOperation(Op op, String linkedId, Type type,
1209 ArgValues avs, SubVals sv, boolean merge, String v, boolean b,
1210 int argIndex, boolean doSubs)
1214 List<String> wildcardLinkedIds = null;
1215 if (a.hasOption(Opt.ALLOWMULTIID))
1219 case MATCHALLLINKEDIDS:
1220 wildcardLinkedIds = getLinkedIds();
1222 case MATCHOPENEDLINKEDIDS:
1223 wildcardLinkedIds = this.storedLinkedIds;
1228 // if we're not a wildcard linkedId and the arg is marked to be stored, add
1229 // to storedLinkedIds
1230 if (linkedId != null && wildcardLinkedIds == null
1231 && a.hasOption(Opt.STORED)
1232 && !storedLinkedIds.contains(linkedId))
1234 storedLinkedIds.add(linkedId);
1237 // if we are a wildcard linkedId, apply the arg and value to all appropriate
1239 if (wildcardLinkedIds != null)
1241 for (String id : wildcardLinkedIds)
1243 // skip incorrectly stored wildcard ids!
1244 if (id == null || MATCHALLLINKEDIDS.equals(id)
1245 || MATCHOPENEDLINKEDIDS.equals(id))
1249 ArgValuesMap avm = linkedArgs.get(id);
1250 // don't set an output if there isn't an input
1251 if (a.hasOption(Opt.REQUIREINPUT)
1252 && !avm.hasArgWithOption(Opt.INPUT))
1255 ArgValues tavs = avm.getOrCreateArgValues(a);
1265 sv = new SubVals(sv, val, merge);
1266 val = makeSubstitutions(sv.getContent(), id);
1268 tavs.addValue(sv, type, val, argIndex, true);
1274 val = makeSubstitutions(v, id);
1276 tavs.addValue(type, val, argIndex, true);
1278 finaliseStoringArgValue(id, tavs);
1282 tavs.setBoolean(type, b, argIndex, true);
1283 finaliseStoringArgValue(id, tavs);
1287 tavs.setNegated(b, true);
1290 case INCREMENTCOUNT:
1291 tavs.incrementCount();
1301 else // no wildcard linkedId -- do it simpler
1311 val = makeSubstitutions(v, linkedId);
1312 sv = new SubVals(sv, val);
1314 avs.addValue(sv, type, val, argIndex, false);
1320 val = makeSubstitutions(v, linkedId);
1322 avs.addValue(type, val, argIndex, false);
1324 finaliseStoringArgValue(linkedId, avs);
1328 avs.setBoolean(type, b, argIndex, false);
1329 finaliseStoringArgValue(linkedId, avs);
1333 avs.setNegated(b, false);
1336 case INCREMENTCOUNT:
1337 avs.incrementCount();
1346 private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1348 if (linkedArgs.containsKey(linkedId)
1349 && linkedArgs.get(linkedId) != null)
1350 return linkedArgs.get(linkedId);
1352 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1353 return linkedArgs.get(linkedId);
1356 public boolean isOldStyle()
1358 return oldArguments;
1361 public boolean isMixedStyle()
1363 return mixedArguments;
1366 public String[] getMixedExamples()
1368 return mixedExamples;