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.
24 import java.io.IOException;
25 import java.net.URLDecoder;
26 import java.nio.file.Files;
27 import java.nio.file.Paths;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.EnumSet;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Locale;
37 import jalview.util.FileUtils;
39 public class ArgParser
41 private static final String DOUBLEDASH = "--";
43 private static final String NEGATESTRING = "no";
45 // the linked id used for no id (not even square braces)
46 private static final String DEFAULTLINKEDID = "";
48 // the linked id used to increment the idCounter (and use the incremented
50 private static final String INCREMENTAUTOCOUNTERLINKEDID = "{++n}";
52 // the linked id used to use the idCounter
53 private static final String AUTOCOUNTERLINKEDID = "{n}";
55 private int idCounter = 0;
57 private static enum Opt
59 BOOLEAN, STRING, UNARY, MULTI, LINKED, NODUPLICATEVALUES, BOOTSTRAP,
66 NOCALCULATION, NOMENUBAR, NOSTATUS, SHOWOVERVIEW, ANNOTATIONS, COLOUR,
67 FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, NOANNOTATION, NOANNOTATION2,
68 NODISPLAY, NOGUI, NONEWS, NOQUESTIONNAIRE, NOSORTBYTREE, NOUSAGESTATS,
69 OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, SORTBYTREE, TREE, VDOC,
72 HELP("h"), CALCULATION, MENUBAR, STATUS, SHOWOVERVIEW, ANNOTATIONS,
73 COLOUR, FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, ANNOTATION,
74 ANNOTATION2, DISPLAY, GUI, NEWS, NOQUESTIONNAIRE, SORTBYTREE,
75 USAGESTATS, OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, TREE, VDOC,
76 VSESS, OUTPUT, OUTPUTTYPE, SSANNOTATION, NOTEMPFAC, TEMPFAC,
77 TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP,
78 NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, CLOSE, DEBUG("d"), QUIET("q"),
83 HELP.setOptions(Opt.UNARY);
84 CALCULATION.setOptions(true, Opt.BOOLEAN); // default "true" implies only
85 // expecting "--nocalculation"
86 MENUBAR.setOptions(true, Opt.BOOLEAN);
87 STATUS.setOptions(true, Opt.BOOLEAN);
88 SHOWOVERVIEW.setOptions(Opt.UNARY, Opt.LINKED);
89 ANNOTATIONS.setOptions(Opt.STRING, Opt.LINKED);
90 COLOUR.setOptions(Opt.STRING, Opt.LINKED);
91 FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
92 GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
93 GROUPS.setOptions(Opt.STRING, Opt.LINKED);
94 HEADLESS.setOptions(Opt.UNARY, Opt.BOOTSTRAP);
95 JABAWS.setOptions(Opt.STRING);
96 ANNOTATION.setOptions(true, Opt.BOOLEAN);
97 ANNOTATION2.setOptions(true, Opt.BOOLEAN);
98 DISPLAY.setOptions(true, Opt.BOOLEAN);
99 GUI.setOptions(true, Opt.BOOLEAN);
100 NEWS.setOptions(true, Opt.BOOLEAN);
101 NOQUESTIONNAIRE.setOptions(Opt.UNARY); // unary as --questionnaire=val
102 // expects a string value
103 SORTBYTREE.setOptions(true, Opt.BOOLEAN);
104 USAGESTATS.setOptions(true, Opt.BOOLEAN);
105 OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB);
106 OPEN2.setOptions(Opt.STRING, Opt.LINKED);
107 PROPS.setOptions(Opt.STRING, Opt.BOOTSTRAP);
108 QUESTIONNAIRE.setOptions(Opt.STRING);
109 SETPROP.setOptions(Opt.STRING);
110 TREE.setOptions(Opt.STRING);
112 VDOC.setOptions(Opt.UNARY);
113 VSESS.setOptions(Opt.UNARY);
115 OUTPUT.setOptions(Opt.STRING, Opt.LINKED);
116 OUTPUTTYPE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
118 SSANNOTATION.setOptions(Opt.BOOLEAN, Opt.LINKED);
119 NOTEMPFAC.setOptions(Opt.UNARY, Opt.LINKED);
120 TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
121 TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
122 TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
123 TEMPFAC_SHADING.setOptions(Opt.BOOLEAN, Opt.LINKED);
124 TITLE.setOptions(Opt.STRING, Opt.LINKED);
125 PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
126 NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
127 STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
128 WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
129 IMAGE.setOptions(Opt.STRING, Opt.LINKED);
130 QUIT.setOptions(Opt.UNARY);
131 CLOSE.setOptions(Opt.UNARY, Opt.LINKED);
132 DEBUG.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP);
133 QUIET.setOptions(Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP);
134 ARGFILE.setOptions(Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB);
135 // BOOTSTRAP args are parsed before a full parse of arguments and
136 // so are accessible at an earlier stage to (e.g.) set debug log level,
137 // provide a props file (that might set log level), run headlessly, read
138 // an argfile instead of other args.
142 private final String[] argNames;
144 private Opt[] argOptions;
146 private boolean defaultBoolValue = false;
148 public String toLongString()
150 StringBuilder sb = new StringBuilder();
151 sb.append("Arg: ").append(this.name());
152 for (String name : getNames())
154 sb.append(", '").append(name).append("'");
156 sb.append("\nOptions: ");
157 boolean first = true;
158 for (Opt o : argOptions)
164 sb.append(o.toString());
168 return sb.toString();
176 private Arg(String... names)
178 int length = (names == null || names.length == 0
179 || (names.length == 1 && names[0] == null)) ? 1
181 this.argNames = new String[length];
182 this.argNames[0] = this.getName();
184 System.arraycopy(names, 0, this.argNames, 1, names.length);
187 public String[] getNames()
192 public String getName()
194 return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
198 public final String toString()
203 public boolean hasOption(Opt o)
205 if (argOptions == null)
207 for (Opt option : argOptions)
215 protected void setOptions(Opt... options)
217 setOptions(false, options);
220 protected void setOptions(boolean defaultBoolValue, Opt... options)
222 this.defaultBoolValue = defaultBoolValue;
223 argOptions = options;
226 protected boolean getDefaultBoolValue()
228 return defaultBoolValue;
232 public static class ArgValues
234 private static final String ID = "id";
238 private int argCount = 0;
240 private boolean boolValue = false;
242 private boolean negated = false;
244 private int boolIndex = -1;
246 private List<Integer> argsIndexes;
248 private List<ArgValue> argValueList;
250 private Map<String, ArgValue> idMap = new HashMap<>();
252 protected ArgValues(Arg a)
255 this.argValueList = new ArrayList<ArgValue>();
256 this.boolValue = arg.getDefaultBoolValue();
264 protected int getCount()
269 protected void incrementCount()
274 protected void setNegated(boolean b)
279 protected boolean isNegated()
284 protected void setBoolean(boolean b, int i)
290 protected boolean getBoolean()
292 return this.boolValue;
296 public String toString()
298 if (argValueList == null)
300 StringBuilder sb = new StringBuilder();
301 sb.append(arg.toLongString());
302 if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
303 sb.append("Boolean: ").append(boolValue).append("; Default: ")
304 .append(arg.getDefaultBoolValue()).append("; Negated: ")
305 .append(negated).append("\n");
306 if (arg.hasOption(Opt.STRING))
308 sb.append("Values:");
309 boolean first = true;
310 for (ArgValue av : argValueList)
312 String v = av.getValue();
316 sb.append(v).append("'");
321 sb.append("Count: ").append(argCount).append("\n");
322 return sb.toString();
325 protected void addValue()
330 protected void addValue(String val, int argIndex)
332 addArgValue(new ArgValue(val, argIndex));
335 protected void addArgValue(ArgValue av)
337 if ((!arg.hasOption(Opt.MULTI) && argValueList.size() > 0)
338 || (arg.hasOption(Opt.NODUPLICATEVALUES)
339 && argValueList.contains(av.getValue())))
341 if (argValueList == null)
343 argValueList = new ArrayList<ArgValue>();
345 SubVals sv = ArgParser.getSubVals(av.getValue());
348 String id = sv.get(ID);
352 argValueList.add(av);
355 protected boolean hasValue(String val)
357 return argValueList.contains(val);
360 protected ArgValue getArgValue()
362 if (arg.hasOption(Opt.MULTI))
363 Console.warn("Requesting single value for multi value argument");
364 return argValueList.size() > 0 ? argValueList.get(0) : null;
367 protected List<ArgValue> getArgValueList()
372 protected boolean hasId(String id)
374 return idMap.containsKey(id);
377 protected ArgValue getId(String id)
379 return idMap.get(id);
384 private List<String> vargs = null;
386 private boolean isApplet;
388 // private AppletParams appletParams;
390 public boolean isApplet()
395 public String nextValue()
397 return vargs.remove(0);
405 public String getValue(String arg)
407 return getValue(arg, false);
410 public String getValue(String arg, boolean utf8decode)
412 int index = vargs.indexOf(arg);
417 ret = vargs.get(index + 1).toString();
420 if (utf8decode && ret != null)
424 dc = URLDecoder.decode(ret, "UTF-8");
426 } catch (Exception e)
428 // TODO: log failure to decode
436 private static final Map<String, Arg> argMap;
438 private Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
440 private List<String> linkedOrder = null;
442 private List<Arg> argList;
446 argMap = new HashMap<>();
447 for (Arg a : EnumSet.allOf(Arg.class))
449 for (String argName : a.getNames())
451 if (argMap.containsKey(argName))
453 Console.warn("Trying to add argument name multiple times: '"
454 + argName + "'"); // RESTORE THIS WHEN MERGED
455 if (argMap.get(argName) != a)
458 "Trying to add argument name multiple times for different Args: '"
459 + argMap.get(argName).getName() + ":" + argName
460 + "' and '" + a.getName() + ":" + argName
465 argMap.put(argName, a);
470 public ArgParser(String[] args)
472 // make a mutable new ArrayList so that shell globbing parser works
473 this(new ArrayList<>(Arrays.asList(args)));
476 public ArgParser(List<String> args)
481 private void init(List<String> args)
483 // Enumeration<String> argE = Collections.enumeration(args);
485 // while (argE.hasMoreElements())
486 for (int i = 0; i < args.size(); i++)
488 // String arg = argE.nextElement();
489 String arg = args.get(i);
490 String argName = null;
492 List<String> vals = null; // for Opt.GLOB only
493 String linkedId = null;
494 if (arg.startsWith(DOUBLEDASH))
496 int equalPos = arg.indexOf('=');
499 argName = arg.substring(DOUBLEDASH.length(), equalPos);
500 val = arg.substring(equalPos + 1);
504 argName = arg.substring(DOUBLEDASH.length());
506 int idOpen = argName.indexOf('[');
507 int idClose = argName.indexOf(']');
509 if (idOpen > -1 && idClose == argName.length() - 1)
511 linkedId = argName.substring(idOpen + 1, idClose);
512 argName = argName.substring(0, idOpen);
515 Arg a = argMap.get(argName);
516 // check for boolean prepended by "no"
517 boolean negated = false;
518 if (a == null && argName.startsWith(NEGATESTRING) && argMap
519 .containsKey(argName.substring(NEGATESTRING.length())))
521 argName = argName.substring(NEGATESTRING.length());
522 a = argMap.get(argName);
526 // check for config errors
530 Console.error("Argument '" + arg + "' not recognised. Ignoring.");
533 if (!a.hasOption(Opt.BOOLEAN) && negated)
535 // used "no" with a non-boolean option
536 Console.error("Argument '--" + NEGATESTRING + argName
537 + "' not a boolean option. Ignoring.");
540 if (!a.hasOption(Opt.STRING) && equalPos > -1)
542 // set --argname=value when arg does not accept values
543 Console.error("Argument '--" + argName
544 + "' does not expect a value (given as '" + arg
548 if (!a.hasOption(Opt.LINKED) && linkedId != null)
550 // set --argname[linkedId] when arg does not use linkedIds
551 Console.error("Argument '--" + argName
552 + "' does not expect a linked id (given as '" + arg
557 if (a.hasOption(Opt.STRING) && equalPos == -1)
559 // take next arg as value if required, and '=' was not found
560 // if (!argE.hasMoreElements())
561 if (i + 1 >= args.size())
563 // no value to take for arg, which wants a value
564 Console.error("Argument '" + a.getName()
565 + "' requires a value, none given. Ignoring.");
568 // deal with bash globs here (--arg val* is expanded before reaching
569 // the JVM). Note that SubVals cannot be used in this case.
570 // If using the --arg=val then the glob is preserved and Java globs
571 // will be used later. SubVals can be used.
572 if (a.hasOption(Opt.GLOB))
574 vals.addAll(getShellGlobbedFilenameValues(a, args, i + 1));
578 val = args.get(i + 1);
582 String autoCounterString = null;
583 if (a.hasOption(Opt.LINKED))
585 if (linkedId == null)
587 // use default linkedId for linked arguments
588 linkedId = DEFAULTLINKEDID;
590 "Changing linkedId to '" + linkedId + "' from " + arg);
592 else if (linkedId.equals(AUTOCOUNTERLINKEDID))
594 // turn {n} to the autoCounter
595 autoCounterString = Integer.toString(idCounter);
596 linkedId = autoCounterString;
598 "Changing linkedId to '" + linkedId + "' from " + arg);
600 else if (linkedId.equals(INCREMENTAUTOCOUNTERLINKEDID))
602 // turn {++n} to the incremented autoCounter
603 autoCounterString = Integer.toString(++idCounter);
604 linkedId = autoCounterString;
606 "Changing linkedId to '" + linkedId + "' from " + arg);
610 if (!linkedArgs.containsKey(linkedId))
611 linkedArgs.put(linkedId, new ArgValuesMap());
613 ArgValuesMap avm = linkedArgs.get(linkedId);
615 // not dealing with both NODUPLICATEVALUES and GLOB
616 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
618 Console.error("Argument '--" + argName
619 + "' cannot contain a duplicate value ('" + val
620 + "'). Ignoring this and subsequent occurrences.");
624 // check for unique id
625 SubVals sv = ArgParser.getSubVals(val);
626 String id = sv.get(ArgValues.ID);
627 if (id != null && avm.hasId(a, id))
629 Console.error("Argument '--" + argName + "' has a duplicate id ('"
630 + id + "'). Ignoring.");
634 ArgValues avs = avm.getOrCreateArgValues(a);
637 avs = new ArgValues(a);
639 // store appropriate value
640 if (a.hasOption(Opt.STRING))
642 if (a.hasOption(Opt.GLOB) && vals != null && vals.size() > 0)
644 for (String v : vals)
645 avs.addValue(val, argIndex++);
649 avs.addValue(val, argIndex);
652 else if (a.hasOption(Opt.BOOLEAN))
654 avs.setBoolean(!negated, argIndex);
655 avs.setNegated(negated);
657 else if (a.hasOption(Opt.UNARY))
659 avs.setBoolean(true, argIndex);
661 avs.incrementCount();
663 // store in appropriate place
664 if (a.hasOption(Opt.LINKED))
666 // allow a default linked id for single usage
667 if (linkedId == null)
668 linkedId = DEFAULTLINKEDID;
669 // store the order of linkedIds
670 if (linkedOrder == null)
671 linkedOrder = new ArrayList<>();
672 if (!linkedOrder.contains(linkedId))
673 linkedOrder.add(linkedId);
676 // store arg in the list of args used
678 argList = new ArrayList<>();
679 if (!argList.contains(a))
686 * A helper method to take a list of String args where we're expecting
687 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
688 * and the index of the globbed arg, here 1. It returns a
689 * List<String> {"file1", "file2", "file3"}
690 * *and remove these from the original list object* so that processing
691 * can continue from where it has left off, e.g. args has become
692 * {"--previousargs", "--arg", "--otheroptionsornot"}
693 * so the next increment carries on from the next --arg if available.
695 private static List<String> getShellGlobbedFilenameValues(Arg a,
696 List<String> args, int i)
698 List<String> vals = new ArrayList<>();
699 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
701 vals.add(args.remove(i));
702 if (!a.hasOption(Opt.GLOB))
708 public boolean isSet(Arg a)
710 return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
713 public boolean isSet(String linkedId, Arg a)
715 ArgValuesMap avm = linkedArgs.get(linkedId);
716 return avm == null ? false : avm.containsArg(a);
719 public boolean getBool(Arg a)
721 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
723 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
726 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
729 public boolean getBool(String linkedId, Arg a)
731 ArgValuesMap avm = linkedArgs.get(linkedId);
733 return a.getDefaultBoolValue();
734 ArgValues avs = avm.getArgValues(a);
735 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
738 public List<String> linkedIds()
743 public ArgValuesMap linkedArgs(String id)
745 return linkedArgs.get(id);
749 public String toString()
751 StringBuilder sb = new StringBuilder();
752 sb.append("UNLINKED\n");
753 sb.append(argValuesMapToString(linkedArgs.get(null)));
754 if (linkedIds() != null)
756 sb.append("LINKED\n");
757 for (String id : linkedIds())
759 // already listed these as UNLINKED args
763 ArgValuesMap avm = linkedArgs(id);
764 sb.append("ID: '").append(id).append("'\n");
765 sb.append(argValuesMapToString(avm));
768 return sb.toString();
771 private static String argValuesMapToString(ArgValuesMap avm)
775 StringBuilder sb = new StringBuilder();
776 for (Arg a : avm.getArgKeys())
778 ArgValues v = avm.getArgValues(a);
779 sb.append(v.toString());
782 return sb.toString();
785 public static SubVals getSubVals(String item)
787 return new SubVals(item);
791 * A helper class to keep an index of argument position with argument values
793 public static class ArgValue
795 private int argIndex;
797 private String value;
801 protected ArgValue(String value, int argIndex)
804 this.argIndex = argIndex;
807 protected String getValue()
812 protected int getArgIndex()
817 protected void setId(String i)
822 protected String getId()
829 * A helper class to parse a string of the possible forms "content"
830 * "[index]content", "[keyName=keyValue]content" and return the integer index,
831 * the strings keyName and keyValue, and the content after the square brackets
832 * (if present). Values not set `will be -1 or null.
834 public static class SubVals
836 private static int NOTSET = -1;
838 private int index = NOTSET;
840 private Map<String, String> subVals = null;
842 private static char SEPARATOR = ';';
844 private String content = null;
846 public SubVals(String item)
848 this.parseVals(item);
851 public void parseVals(String item)
855 if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
857 int openBracket = item.indexOf('[');
858 int closeBracket = item.indexOf(']');
859 String subvalsString = item.substring(openBracket + 1,
861 this.content = item.substring(closeBracket + 1);
862 boolean setIndex = false;
863 for (String subvalString : subvalsString
864 .split(Character.toString(SEPARATOR)))
866 int equals = subvalString.indexOf('=');
870 subVals = new HashMap<>();
871 subVals.put(subvalString.substring(0, equals),
872 subvalString.substring(equals + 1));
878 this.index = Integer.parseInt(subvalString);
880 } catch (NumberFormatException e)
882 Console.warn("Failed to obtain subvalue or index from '"
883 + item + "'. Setting index=0 and using content='"
897 public boolean notSet()
899 // notSet is true if content present but nonsensical
900 return index == NOTSET && subVals == null;
903 public String get(String key)
905 return subVals == null ? null : subVals.get(key);
908 public boolean has(String key)
910 return subVals == null ? false : subVals.containsKey(key);
913 public int getIndex()
918 public String getContent()
925 * Helper class to allow easy extraction of information about specific
926 * argument values (without having to check for null etc all the time)
928 protected static class ArgValuesMap
930 protected Map<Arg, ArgValues> m;
932 protected ArgValuesMap()
937 protected ArgValuesMap(Map<Arg, ArgValues> map)
942 private Map<Arg, ArgValues> getMap()
947 private void newMap()
949 m = new HashMap<Arg, ArgValues>();
952 private void newArg(Arg a)
957 m.put(a, new ArgValues(a));
960 protected void addArgValue(Arg a, ArgValue av)
962 if (getMap() == null)
963 m = new HashMap<Arg, ArgValues>();
965 if (!m.containsKey(a))
966 m.put(a, new ArgValues(a));
967 ArgValues avs = m.get(a);
971 protected ArgValues getArgValues(Arg a)
973 return m == null ? null : m.get(a);
976 protected ArgValues getOrCreateArgValues(Arg a)
978 ArgValues avs = m.get(a);
981 return getArgValues(a);
984 protected List<ArgValue> getArgValueList(Arg a)
986 ArgValues avs = getArgValues(a);
987 return avs == null ? new ArrayList<>() : avs.getArgValueList();
990 protected ArgValue getArgValue(Arg a)
992 List<ArgValue> vals = getArgValueList(a);
993 return (vals == null || vals.size() == 0) ? null : vals.get(0);
996 protected String getValue(Arg a)
998 ArgValue av = getArgValue(a);
999 return av == null ? null : av.getValue();
1002 protected boolean containsArg(Arg a)
1004 if (m == null || !m.containsKey(a))
1006 return a.hasOption(Opt.STRING) ? getArgValue(a) != null
1007 : this.getBoolean(a);
1010 protected boolean hasValue(Arg a, String val)
1012 if (m == null || !m.containsKey(a))
1014 for (ArgValue av : getArgValueList(a))
1016 String avVal = av.getValue();
1017 if ((val == null && avVal == null)
1018 || (val != null && val.equals(avVal)))
1026 protected boolean getBoolean(Arg a)
1028 ArgValues av = getArgValues(a);
1029 return av == null ? false : av.getBoolean();
1032 protected Set<Arg> getArgKeys()
1037 protected ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv,
1040 ArgValue closestAv = null;
1041 int thisArgIndex = thisAv.getArgIndex();
1042 ArgValues compareAvs = this.getArgValues(a);
1043 int closestPreviousIndex = -1;
1044 for (ArgValue av : compareAvs.getArgValueList())
1046 int argIndex = av.getArgIndex();
1047 if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
1049 closestPreviousIndex = argIndex;
1056 protected ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a)
1058 // this looks for the *next* arg that *might* be referring back to
1059 // a thisAv. Such an arg would have no subValues (if it does it should
1060 // specify an id in the subValues so wouldn't need to be guessed).
1061 ArgValue closestAv = null;
1062 int thisArgIndex = thisAv.getArgIndex();
1063 ArgValues compareAvs = this.getArgValues(a);
1064 int closestNextIndex = Integer.MAX_VALUE;
1065 for (ArgValue av : compareAvs.getArgValueList())
1067 int argIndex = av.getArgIndex();
1068 if (argIndex > thisArgIndex && argIndex < closestNextIndex)
1070 closestNextIndex = argIndex;
1077 protected ArgValue[] getArgValuesReferringTo(String key, String value,
1080 // this looks for the *next* arg that *might* be referring back to
1081 // a thisAv. Such an arg would have no subValues (if it does it should
1082 // specify an id in the subValues so wouldn't need to be guessed).
1083 List<ArgValue> avList = new ArrayList<>();
1084 Arg[] args = a == null ? (Arg[]) this.getMap().keySet().toArray()
1087 for (Arg keyArg : args)
1089 for (ArgValue av : this.getArgValueList(keyArg))
1094 return (ArgValue[]) avList.toArray();
1097 protected boolean hasId(Arg a, String id)
1099 ArgValues avs = this.getArgValues(a);
1100 return avs == null ? false : avs.hasId(id);
1103 protected ArgValue getId(Arg a, String id)
1105 ArgValues avs = this.getArgValues(a);
1106 return avs == null ? null : avs.getId(id);
1110 public static ArgParser parseArgFiles(List<String> argFilenameGlobs)
1112 List<File> argFiles = new ArrayList<>();
1114 for (String pattern : argFilenameGlobs)
1116 // I don't think we want to dedup files, making life easier
1117 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
1120 return parseArgFileList(argFiles);
1123 public static ArgParser parseArgFileList(List<File> argFiles)
1125 List<String> argsList = new ArrayList<>();
1126 for (File argFile : argFiles)
1128 if (!argFile.exists())
1130 System.err.println(DOUBLEDASH
1131 + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
1132 + argFile.getPath() + "\": File does not exist.");
1137 argsList.addAll(Files.readAllLines(Paths.get(argFile.getPath())));
1138 } catch (IOException e)
1140 System.err.println(DOUBLEDASH
1141 + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
1142 + argFile.getPath() + "\": File could not be read.");
1146 return new ArgParser(argsList);
1149 public static class BootstrapArgs
1152 private static Map<Arg, List<String>> bootstrapArgMap = new HashMap<>();
1154 public static BootstrapArgs getBootstrapArgs(String[] args)
1156 List<String> argList = new ArrayList<>(Arrays.asList(args));
1157 return new BootstrapArgs(argList);
1160 private BootstrapArgs(List<String> args)
1165 private void init(List<String> args)
1169 for (int i = 0; i < args.size(); i++)
1171 String arg = args.get(i);
1172 String argName = null;
1174 if (arg.startsWith(ArgParser.DOUBLEDASH))
1176 int equalPos = arg.indexOf('=');
1179 argName = arg.substring(ArgParser.DOUBLEDASH.length(),
1181 val = arg.substring(equalPos + 1);
1185 argName = arg.substring(ArgParser.DOUBLEDASH.length());
1189 Arg a = argMap.get(argName);
1191 if (a == null || !a.hasOption(Opt.BOOTSTRAP))
1193 // not a valid bootstrap arg
1197 if (a.hasOption(Opt.STRING))
1201 addAll(a, ArgParser.getShellGlobbedFilenameValues(a, args,
1206 if (a.hasOption(Opt.GLOB))
1207 addAll(a, FileUtils.getFilenamesFromGlob(val));
1220 public boolean contains(Arg a)
1222 return bootstrapArgMap.containsKey(a);
1225 public List<String> getList(Arg a)
1227 return bootstrapArgMap.get(a);
1230 private List<String> getOrCreateList(Arg a)
1232 List<String> l = getList(a);
1235 l = new ArrayList<>();
1241 private void putList(Arg a, List<String> l)
1243 bootstrapArgMap.put(a, l);
1247 * Creates a new list if not used before,
1248 * adds the value unless the existing list is non-empty
1249 * and the arg is not MULTI (so first expressed value is
1252 private void add(Arg a, String s)
1254 List<String> l = getOrCreateList(a);
1255 if (a.hasOption(Opt.MULTI) || l.size() == 0)
1261 private void addAll(Arg a, List<String> al)
1263 List<String> l = getOrCreateList(a);
1264 if (a.hasOption(Opt.MULTI))
1271 * Retrieves the first value even if MULTI.
1272 * A convenience for non-MULTI args.
1274 public String get(Arg a)
1276 if (!bootstrapArgMap.containsKey(a))
1278 List<String> aL = bootstrapArgMap.get(a);
1279 return (aL == null || aL.size() == 0) ? null : aL.get(0);