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 default linked id prefix used for no id (not even square braces)
46 private static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
48 // the counter added to the default linked id prefix
49 private int defaultLinkedIdCounter = 0;
51 // the linked id used to increment the idCounter (and use the incremented
53 private static final String INCREMENTAUTOCOUNTERLINKEDID = "{++n}";
55 // the linked id used to use the idCounter
56 private static final String AUTOCOUNTERLINKEDID = "{n}";
58 private int idCounter = 0;
60 // flag to say whether {n} subtitutions in output filenames should be made.
61 // Turn on and off with --subs and --nosubs
62 private boolean substitutions = false;
64 private static enum Opt
66 BOOLEAN, STRING, UNARY, MULTI, LINKED, NODUPLICATEVALUES, BOOTSTRAP,
73 NOCALCULATION, NOMENUBAR, NOSTATUS, SHOWOVERVIEW, ANNOTATIONS, COLOUR,
74 FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, NOANNOTATION, NOANNOTATION2,
75 NODISPLAY, NOGUI, NONEWS, NOQUESTIONNAIRE, NOSORTBYTREE, NOUSAGESTATS,
76 OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, SORTBYTREE, TREE, VDOC,
79 HELP("h"), CALCULATION, MENUBAR, STATUS, SHOWOVERVIEW, ANNOTATIONS,
80 COLOUR, FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, ANNOTATION,
81 ANNOTATION2, DISPLAY, GUI, NEWS, NOQUESTIONNAIRE, SORTBYTREE,
82 USAGESTATS, OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, TREE, VDOC,
83 VSESS, OUTPUT, OUTPUTTYPE, SSANNOTATION, NOTEMPFAC, TEMPFAC,
84 TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP,
85 NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, CLOSE, DEBUG("d"), QUIET("q"),
86 ARGFILE, INCREMENT, NPP("n++"), SUBSTITUTIONS, NIL;
90 HELP.setOptions(Opt.UNARY);
91 CALCULATION.setOptions(true, Opt.BOOLEAN); // default "true" implies only
92 // expecting "--nocalculation"
93 MENUBAR.setOptions(true, Opt.BOOLEAN);
94 STATUS.setOptions(true, Opt.BOOLEAN);
95 SHOWOVERVIEW.setOptions(Opt.UNARY, Opt.LINKED);
96 ANNOTATIONS.setOptions(Opt.STRING, Opt.LINKED);
97 COLOUR.setOptions(Opt.STRING, Opt.LINKED);
98 FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
99 GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
100 GROUPS.setOptions(Opt.STRING, Opt.LINKED);
101 HEADLESS.setOptions(Opt.UNARY, Opt.BOOTSTRAP);
102 JABAWS.setOptions(Opt.STRING);
103 ANNOTATION.setOptions(true, Opt.BOOLEAN);
104 ANNOTATION2.setOptions(true, Opt.BOOLEAN);
105 DISPLAY.setOptions(true, Opt.BOOLEAN);
106 GUI.setOptions(true, Opt.BOOLEAN);
107 NEWS.setOptions(true, Opt.BOOLEAN);
108 NOQUESTIONNAIRE.setOptions(Opt.UNARY); // unary as --questionnaire=val
109 // expects a string value
110 SORTBYTREE.setOptions(true, Opt.BOOLEAN);
111 USAGESTATS.setOptions(true, Opt.BOOLEAN);
112 OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB);
113 OPEN2.setOptions(Opt.STRING, Opt.LINKED);
114 PROPS.setOptions(Opt.STRING, Opt.BOOTSTRAP);
115 QUESTIONNAIRE.setOptions(Opt.STRING);
116 SETPROP.setOptions(Opt.STRING);
117 TREE.setOptions(Opt.STRING);
119 VDOC.setOptions(Opt.UNARY);
120 VSESS.setOptions(Opt.UNARY);
122 OUTPUT.setOptions(Opt.STRING, Opt.LINKED);
123 OUTPUTTYPE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
125 SSANNOTATION.setOptions(Opt.BOOLEAN, Opt.LINKED);
126 NOTEMPFAC.setOptions(Opt.UNARY, Opt.LINKED);
127 TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
128 TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
129 TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
130 TEMPFAC_SHADING.setOptions(Opt.BOOLEAN, Opt.LINKED);
131 TITLE.setOptions(Opt.STRING, Opt.LINKED);
132 PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
133 NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
134 STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
135 WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
136 IMAGE.setOptions(Opt.STRING, Opt.LINKED);
137 QUIT.setOptions(Opt.UNARY);
138 CLOSE.setOptions(Opt.UNARY, Opt.LINKED);
139 DEBUG.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP);
140 QUIET.setOptions(Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP);
141 ARGFILE.setOptions(Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB);
142 INCREMENT.setOptions(Opt.UNARY, Opt.MULTI, Opt.NOACTION);
143 NPP.setOptions(Opt.UNARY, Opt.MULTI, Opt.NOACTION);
144 SUBSTITUTIONS.setOptions(Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION);
145 NIL.setOptions(Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.NOACTION);
146 // BOOTSTRAP args are parsed before a full parse of arguments and
147 // so are accessible at an earlier stage to (e.g.) set debug log level,
148 // provide a props file (that might set log level), run headlessly, read
149 // an argfile instead of other args.
152 private final String[] argNames;
154 private Opt[] argOptions;
156 private boolean defaultBoolValue = false;
158 public String toLongString()
160 StringBuilder sb = new StringBuilder();
161 sb.append("Arg: ").append(this.name());
162 for (String name : getNames())
164 sb.append(", '").append(name).append("'");
166 sb.append("\nOptions: ");
167 boolean first = true;
168 for (Opt o : argOptions)
174 sb.append(o.toString());
178 return sb.toString();
186 private Arg(String... names)
188 int length = (names == null || names.length == 0
189 || (names.length == 1 && names[0] == null)) ? 1
191 this.argNames = new String[length];
192 this.argNames[0] = this.getName();
194 System.arraycopy(names, 0, this.argNames, 1, names.length);
197 public String[] getNames()
202 public String getName()
204 return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
208 public final String toString()
213 public boolean hasOption(Opt o)
215 if (argOptions == null)
217 for (Opt option : argOptions)
225 protected void setOptions(Opt... options)
227 setOptions(false, options);
230 protected void setOptions(boolean defaultBoolValue, Opt... options)
232 this.defaultBoolValue = defaultBoolValue;
233 argOptions = options;
236 protected boolean getDefaultBoolValue()
238 return defaultBoolValue;
242 public static class ArgValues
244 private static final String ID = "id";
248 private int argCount = 0;
250 private boolean boolValue = false;
252 private boolean negated = false;
254 private int boolIndex = -1;
256 private List<Integer> argsIndexes;
258 private List<ArgValue> argValueList;
260 private Map<String, ArgValue> idMap = new HashMap<>();
262 protected ArgValues(Arg a)
265 this.argValueList = new ArrayList<ArgValue>();
266 this.boolValue = arg.getDefaultBoolValue();
274 protected int getCount()
279 protected void incrementCount()
284 protected void setNegated(boolean b)
289 protected boolean isNegated()
294 protected void setBoolean(boolean b, int i)
300 protected boolean getBoolean()
302 return this.boolValue;
306 public String toString()
308 if (argValueList == null)
310 StringBuilder sb = new StringBuilder();
311 sb.append(arg.toLongString());
312 if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
313 sb.append("Boolean: ").append(boolValue).append("; Default: ")
314 .append(arg.getDefaultBoolValue()).append("; Negated: ")
315 .append(negated).append("\n");
316 if (arg.hasOption(Opt.STRING))
318 sb.append("Values:");
319 boolean first = true;
320 for (ArgValue av : argValueList)
322 String v = av.getValue();
326 sb.append(v).append("'");
331 sb.append("Count: ").append(argCount).append("\n");
332 return sb.toString();
335 protected void addValue()
340 protected void addValue(String val, int argIndex)
342 addArgValue(new ArgValue(val, argIndex));
345 protected void addArgValue(ArgValue av)
347 if ((!arg.hasOption(Opt.MULTI) && argValueList.size() > 0)
348 || (arg.hasOption(Opt.NODUPLICATEVALUES)
349 && argValueList.contains(av.getValue())))
351 if (argValueList == null)
353 argValueList = new ArrayList<ArgValue>();
355 SubVals sv = ArgParser.getSubVals(av.getValue());
358 String id = sv.get(ID);
362 argValueList.add(av);
365 protected boolean hasValue(String val)
367 return argValueList.contains(val);
370 protected ArgValue getArgValue()
372 if (arg.hasOption(Opt.MULTI))
373 Console.warn("Requesting single value for multi value argument");
374 return argValueList.size() > 0 ? argValueList.get(0) : null;
377 protected List<ArgValue> getArgValueList()
382 protected boolean hasId(String id)
384 return idMap.containsKey(id);
387 protected ArgValue getId(String id)
389 return idMap.get(id);
394 private List<String> vargs = null;
396 private boolean isApplet;
398 // private AppletParams appletParams;
400 public boolean isApplet()
405 public String nextValue()
407 return vargs.remove(0);
415 public String getValue(String arg)
417 return getValue(arg, false);
420 public String getValue(String arg, boolean utf8decode)
422 int index = vargs.indexOf(arg);
427 ret = vargs.get(index + 1).toString();
430 if (utf8decode && ret != null)
434 dc = URLDecoder.decode(ret, "UTF-8");
436 } catch (Exception e)
438 // TODO: log failure to decode
446 private static final Map<String, Arg> argMap;
448 private Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
450 private List<String> linkedOrder = null;
452 private List<Arg> argList;
456 argMap = new HashMap<>();
457 for (Arg a : EnumSet.allOf(Arg.class))
459 for (String argName : a.getNames())
461 if (argMap.containsKey(argName))
463 Console.warn("Trying to add argument name multiple times: '"
464 + argName + "'"); // RESTORE THIS WHEN MERGED
465 if (argMap.get(argName) != a)
468 "Trying to add argument name multiple times for different Args: '"
469 + argMap.get(argName).getName() + ":" + argName
470 + "' and '" + a.getName() + ":" + argName
475 argMap.put(argName, a);
480 public ArgParser(String[] args)
482 // make a mutable new ArrayList so that shell globbing parser works
483 this(new ArrayList<>(Arrays.asList(args)));
486 public ArgParser(List<String> args)
491 private void init(List<String> args)
493 // Enumeration<String> argE = Collections.enumeration(args);
495 // while (argE.hasMoreElements())
496 for (int i = 0; i < args.size(); i++)
498 // String arg = argE.nextElement();
499 String arg = args.get(i);
500 String argName = null;
502 List<String> vals = null; // for Opt.GLOB only
503 String linkedId = null;
504 if (arg.startsWith(DOUBLEDASH))
506 int equalPos = arg.indexOf('=');
509 argName = arg.substring(DOUBLEDASH.length(), equalPos);
510 val = arg.substring(equalPos + 1);
514 argName = arg.substring(DOUBLEDASH.length());
516 int idOpen = argName.indexOf('[');
517 int idClose = argName.indexOf(']');
519 if (idOpen > -1 && idClose == argName.length() - 1)
521 linkedId = argName.substring(idOpen + 1, idClose);
522 argName = argName.substring(0, idOpen);
525 Arg a = argMap.get(argName);
526 // check for boolean prepended by "no"
527 boolean negated = false;
528 if (a == null && argName.startsWith(NEGATESTRING) && argMap
529 .containsKey(argName.substring(NEGATESTRING.length())))
531 argName = argName.substring(NEGATESTRING.length());
532 a = argMap.get(argName);
536 // check for config errors
540 Console.error("Argument '" + arg + "' not recognised. Ignoring.");
543 if (!a.hasOption(Opt.BOOLEAN) && negated)
545 // used "no" with a non-boolean option
546 Console.error("Argument '--" + NEGATESTRING + argName
547 + "' not a boolean option. Ignoring.");
550 if (!a.hasOption(Opt.STRING) && equalPos > -1)
552 // set --argname=value when arg does not accept values
553 Console.error("Argument '--" + argName
554 + "' does not expect a value (given as '" + arg
558 if (!a.hasOption(Opt.LINKED) && linkedId != null)
560 // set --argname[linkedId] when arg does not use linkedIds
561 Console.error("Argument '--" + argName
562 + "' does not expect a linked id (given as '" + arg
567 if (a.hasOption(Opt.STRING) && equalPos == -1)
569 // take next arg as value if required, and '=' was not found
570 // if (!argE.hasMoreElements())
571 if (i + 1 >= args.size())
573 // no value to take for arg, which wants a value
574 Console.error("Argument '" + a.getName()
575 + "' requires a value, none given. Ignoring.");
578 // deal with bash globs here (--arg val* is expanded before reaching
579 // the JVM). Note that SubVals cannot be used in this case.
580 // If using the --arg=val then the glob is preserved and Java globs
581 // will be used later. SubVals can be used.
582 if (a.hasOption(Opt.GLOB))
584 vals.addAll(getShellGlobbedFilenameValues(a, args, i + 1));
588 val = args.get(i + 1);
592 // make NOACTION adjustments
593 // default and auto counter increments
594 if (a == Arg.INCREMENT)
596 defaultLinkedIdCounter++;
598 else if (a == Arg.NPP)
602 else if (a == Arg.SUBSTITUTIONS)
604 substitutions = !negated;
607 String autoCounterString = null;
608 boolean usingAutoCounterLinkedId = false;
609 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
610 .append(Integer.toString(defaultLinkedIdCounter))
612 boolean usingDefaultLinkedId = false;
613 if (a.hasOption(Opt.LINKED))
615 if (linkedId == null)
617 // use default linkedId for linked arguments
618 linkedId = defaultLinkedId;
619 usingDefaultLinkedId = true;
621 "Changing linkedId to '" + linkedId + "' from " + arg);
623 else if (linkedId.equals(AUTOCOUNTERLINKEDID))
625 // turn {n} to the autoCounter
626 autoCounterString = Integer.toString(idCounter);
627 linkedId = autoCounterString;
628 usingAutoCounterLinkedId = true;
630 "Changing linkedId to '" + linkedId + "' from " + arg);
632 else if (linkedId.equals(INCREMENTAUTOCOUNTERLINKEDID))
634 // turn {++n} to the incremented autoCounter
635 autoCounterString = Integer.toString(++idCounter);
636 linkedId = autoCounterString;
637 usingAutoCounterLinkedId = true;
639 "Changing linkedId to '" + linkedId + "' from " + arg);
643 if (!linkedArgs.containsKey(linkedId))
644 linkedArgs.put(linkedId, new ArgValuesMap());
646 // do not continue for NOACTION args
647 if (a.hasOption(Opt.NOACTION))
650 ArgValuesMap avm = linkedArgs.get(linkedId);
652 // not dealing with both NODUPLICATEVALUES and GLOB
653 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
655 Console.error("Argument '--" + argName
656 + "' cannot contain a duplicate value ('" + val
657 + "'). Ignoring this and subsequent occurrences.");
661 // check for unique id
662 SubVals sv = ArgParser.getSubVals(val);
663 String id = sv.get(ArgValues.ID);
664 if (id != null && avm.hasId(a, id))
666 Console.error("Argument '--" + argName + "' has a duplicate id ('"
667 + id + "'). Ignoring.");
671 ArgValues avs = avm.getOrCreateArgValues(a);
674 avs = new ArgValues(a);
676 // store appropriate value
677 if (a.hasOption(Opt.STRING))
679 if (a.hasOption(Opt.GLOB) && vals != null && vals.size() > 0)
681 for (String v : vals)
682 avs.addValue(val, argIndex++);
686 avs.addValue(val, argIndex);
689 else if (a.hasOption(Opt.BOOLEAN))
691 avs.setBoolean(!negated, argIndex);
692 avs.setNegated(negated);
694 else if (a.hasOption(Opt.UNARY))
696 avs.setBoolean(true, argIndex);
698 avs.incrementCount();
700 // store in appropriate place
701 if (a.hasOption(Opt.LINKED))
703 // allow a default linked id for single usage
704 if (linkedId == null)
705 linkedId = defaultLinkedId;
706 // store the order of linkedIds
707 if (linkedOrder == null)
708 linkedOrder = new ArrayList<>();
709 if (!linkedOrder.contains(linkedId))
710 linkedOrder.add(linkedId);
713 // store arg in the list of args used
715 argList = new ArrayList<>();
716 if (!argList.contains(a))
723 * A helper method to take a list of String args where we're expecting
724 * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
725 * and the index of the globbed arg, here 1. It returns a
726 * List<String> {"file1", "file2", "file3"}
727 * *and remove these from the original list object* so that processing
728 * can continue from where it has left off, e.g. args has become
729 * {"--previousargs", "--arg", "--otheroptionsornot"}
730 * so the next increment carries on from the next --arg if available.
732 private static List<String> getShellGlobbedFilenameValues(Arg a,
733 List<String> args, int i)
735 List<String> vals = new ArrayList<>();
736 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
738 vals.add(args.remove(i));
739 if (!a.hasOption(Opt.GLOB))
745 public boolean isSet(Arg a)
747 return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
750 public boolean isSet(String linkedId, Arg a)
752 ArgValuesMap avm = linkedArgs.get(linkedId);
753 return avm == null ? false : avm.containsArg(a);
756 public boolean getBool(Arg a)
758 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
760 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
763 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
766 public boolean getBool(String linkedId, Arg a)
768 ArgValuesMap avm = linkedArgs.get(linkedId);
770 return a.getDefaultBoolValue();
771 ArgValues avs = avm.getArgValues(a);
772 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
775 public List<String> linkedIds()
780 public ArgValuesMap linkedArgs(String id)
782 return linkedArgs.get(id);
786 public String toString()
788 StringBuilder sb = new StringBuilder();
789 sb.append("UNLINKED\n");
790 sb.append(argValuesMapToString(linkedArgs.get(null)));
791 if (linkedIds() != null)
793 sb.append("LINKED\n");
794 for (String id : linkedIds())
796 // already listed these as UNLINKED args
800 ArgValuesMap avm = linkedArgs(id);
801 sb.append("ID: '").append(id).append("'\n");
802 sb.append(argValuesMapToString(avm));
805 return sb.toString();
808 private static String argValuesMapToString(ArgValuesMap avm)
812 StringBuilder sb = new StringBuilder();
813 for (Arg a : avm.getArgKeys())
815 ArgValues v = avm.getArgValues(a);
816 sb.append(v.toString());
819 return sb.toString();
822 public static SubVals getSubVals(String item)
824 return new SubVals(item);
828 * A helper class to keep an index of argument position with argument values
830 public static class ArgValue
832 private int argIndex;
834 private String value;
838 protected ArgValue(String value, int argIndex)
841 this.argIndex = argIndex;
844 protected String getValue()
849 protected int getArgIndex()
854 protected void setId(String i)
859 protected String getId()
866 * A helper class to parse a string of the possible forms "content"
867 * "[index]content", "[keyName=keyValue]content" and return the integer index,
868 * the strings keyName and keyValue, and the content after the square brackets
869 * (if present). Values not set `will be -1 or null.
871 public static class SubVals
873 private static int NOTSET = -1;
875 private int index = NOTSET;
877 private Map<String, String> subVals = null;
879 private static char SEPARATOR = ';';
881 private String content = null;
883 public SubVals(String item)
885 this.parseVals(item);
888 public void parseVals(String item)
892 if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
894 int openBracket = item.indexOf('[');
895 int closeBracket = item.indexOf(']');
896 String subvalsString = item.substring(openBracket + 1,
898 this.content = item.substring(closeBracket + 1);
899 boolean setIndex = false;
900 for (String subvalString : subvalsString
901 .split(Character.toString(SEPARATOR)))
903 int equals = subvalString.indexOf('=');
907 subVals = new HashMap<>();
908 subVals.put(subvalString.substring(0, equals),
909 subvalString.substring(equals + 1));
915 this.index = Integer.parseInt(subvalString);
917 } catch (NumberFormatException e)
919 Console.warn("Failed to obtain subvalue or index from '"
920 + item + "'. Setting index=0 and using content='"
934 public boolean notSet()
936 // notSet is true if content present but nonsensical
937 return index == NOTSET && subVals == null;
940 public String get(String key)
942 return subVals == null ? null : subVals.get(key);
945 public boolean has(String key)
947 return subVals == null ? false : subVals.containsKey(key);
950 public int getIndex()
955 public String getContent()
962 * Helper class to allow easy extraction of information about specific
963 * argument values (without having to check for null etc all the time)
965 protected static class ArgValuesMap
967 protected Map<Arg, ArgValues> m;
969 protected ArgValuesMap()
974 protected ArgValuesMap(Map<Arg, ArgValues> map)
979 private Map<Arg, ArgValues> getMap()
984 private void newMap()
986 m = new HashMap<Arg, ArgValues>();
989 private void newArg(Arg a)
994 m.put(a, new ArgValues(a));
997 protected void addArgValue(Arg a, ArgValue av)
999 if (getMap() == null)
1000 m = new HashMap<Arg, ArgValues>();
1002 if (!m.containsKey(a))
1003 m.put(a, new ArgValues(a));
1004 ArgValues avs = m.get(a);
1005 avs.addArgValue(av);
1008 protected ArgValues getArgValues(Arg a)
1010 return m == null ? null : m.get(a);
1013 protected ArgValues getOrCreateArgValues(Arg a)
1015 ArgValues avs = m.get(a);
1018 return getArgValues(a);
1021 protected List<ArgValue> getArgValueList(Arg a)
1023 ArgValues avs = getArgValues(a);
1024 return avs == null ? new ArrayList<>() : avs.getArgValueList();
1027 protected ArgValue getArgValue(Arg a)
1029 List<ArgValue> vals = getArgValueList(a);
1030 return (vals == null || vals.size() == 0) ? null : vals.get(0);
1033 protected String getValue(Arg a)
1035 ArgValue av = getArgValue(a);
1036 return av == null ? null : av.getValue();
1039 protected boolean containsArg(Arg a)
1041 if (m == null || !m.containsKey(a))
1043 return a.hasOption(Opt.STRING) ? getArgValue(a) != null
1044 : this.getBoolean(a);
1047 protected boolean hasValue(Arg a, String val)
1049 if (m == null || !m.containsKey(a))
1051 for (ArgValue av : getArgValueList(a))
1053 String avVal = av.getValue();
1054 if ((val == null && avVal == null)
1055 || (val != null && val.equals(avVal)))
1063 protected boolean getBoolean(Arg a)
1065 ArgValues av = getArgValues(a);
1066 return av == null ? false : av.getBoolean();
1069 protected Set<Arg> getArgKeys()
1074 protected ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv,
1077 ArgValue closestAv = null;
1078 int thisArgIndex = thisAv.getArgIndex();
1079 ArgValues compareAvs = this.getArgValues(a);
1080 int closestPreviousIndex = -1;
1081 for (ArgValue av : compareAvs.getArgValueList())
1083 int argIndex = av.getArgIndex();
1084 if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
1086 closestPreviousIndex = argIndex;
1093 protected ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a)
1095 // this looks for the *next* arg that *might* be referring back to
1096 // a thisAv. Such an arg would have no subValues (if it does it should
1097 // specify an id in the subValues so wouldn't need to be guessed).
1098 ArgValue closestAv = null;
1099 int thisArgIndex = thisAv.getArgIndex();
1100 ArgValues compareAvs = this.getArgValues(a);
1101 int closestNextIndex = Integer.MAX_VALUE;
1102 for (ArgValue av : compareAvs.getArgValueList())
1104 int argIndex = av.getArgIndex();
1105 if (argIndex > thisArgIndex && argIndex < closestNextIndex)
1107 closestNextIndex = argIndex;
1114 protected ArgValue[] getArgValuesReferringTo(String key, String value,
1117 // this looks for the *next* arg that *might* be referring back to
1118 // a thisAv. Such an arg would have no subValues (if it does it should
1119 // specify an id in the subValues so wouldn't need to be guessed).
1120 List<ArgValue> avList = new ArrayList<>();
1121 Arg[] args = a == null ? (Arg[]) this.getMap().keySet().toArray()
1124 for (Arg keyArg : args)
1126 for (ArgValue av : this.getArgValueList(keyArg))
1131 return (ArgValue[]) avList.toArray();
1134 protected boolean hasId(Arg a, String id)
1136 ArgValues avs = this.getArgValues(a);
1137 return avs == null ? false : avs.hasId(id);
1140 protected ArgValue getId(Arg a, String id)
1142 ArgValues avs = this.getArgValues(a);
1143 return avs == null ? null : avs.getId(id);
1147 public static ArgParser parseArgFiles(List<String> argFilenameGlobs)
1149 List<File> argFiles = new ArrayList<>();
1151 for (String pattern : argFilenameGlobs)
1153 // I don't think we want to dedup files, making life easier
1154 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
1157 return parseArgFileList(argFiles);
1160 public static ArgParser parseArgFileList(List<File> argFiles)
1162 List<String> argsList = new ArrayList<>();
1163 for (File argFile : argFiles)
1165 if (!argFile.exists())
1167 System.err.println(DOUBLEDASH
1168 + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
1169 + argFile.getPath() + "\": File does not exist.");
1174 argsList.addAll(Files.readAllLines(Paths.get(argFile.getPath())));
1175 } catch (IOException e)
1177 System.err.println(DOUBLEDASH
1178 + Arg.ARGFILE.name().toLowerCase(Locale.ROOT) + "=\""
1179 + argFile.getPath() + "\": File could not be read.");
1183 return new ArgParser(argsList);
1186 public static class BootstrapArgs
1189 private static Map<Arg, List<String>> bootstrapArgMap = new HashMap<>();
1191 public static BootstrapArgs getBootstrapArgs(String[] args)
1193 List<String> argList = new ArrayList<>(Arrays.asList(args));
1194 return new BootstrapArgs(argList);
1197 private BootstrapArgs(List<String> args)
1202 private void init(List<String> args)
1206 for (int i = 0; i < args.size(); i++)
1208 String arg = args.get(i);
1209 String argName = null;
1211 if (arg.startsWith(ArgParser.DOUBLEDASH))
1213 int equalPos = arg.indexOf('=');
1216 argName = arg.substring(ArgParser.DOUBLEDASH.length(),
1218 val = arg.substring(equalPos + 1);
1222 argName = arg.substring(ArgParser.DOUBLEDASH.length());
1226 Arg a = argMap.get(argName);
1228 if (a == null || !a.hasOption(Opt.BOOTSTRAP))
1230 // not a valid bootstrap arg
1234 if (a.hasOption(Opt.STRING))
1238 addAll(a, ArgParser.getShellGlobbedFilenameValues(a, args,
1243 if (a.hasOption(Opt.GLOB))
1244 addAll(a, FileUtils.getFilenamesFromGlob(val));
1257 public boolean contains(Arg a)
1259 return bootstrapArgMap.containsKey(a);
1262 public List<String> getList(Arg a)
1264 return bootstrapArgMap.get(a);
1267 private List<String> getOrCreateList(Arg a)
1269 List<String> l = getList(a);
1272 l = new ArrayList<>();
1278 private void putList(Arg a, List<String> l)
1280 bootstrapArgMap.put(a, l);
1284 * Creates a new list if not used before,
1285 * adds the value unless the existing list is non-empty
1286 * and the arg is not MULTI (so first expressed value is
1289 private void add(Arg a, String s)
1291 List<String> l = getOrCreateList(a);
1292 if (a.hasOption(Opt.MULTI) || l.size() == 0)
1298 private void addAll(Arg a, List<String> al)
1300 List<String> l = getOrCreateList(a);
1301 if (a.hasOption(Opt.MULTI))
1308 * Retrieves the first value even if MULTI.
1309 * A convenience for non-MULTI args.
1311 public String get(Arg a)
1313 if (!bootstrapArgMap.containsKey(a))
1315 List<String> aL = bootstrapArgMap.get(a);
1316 return (aL == null || aL.size() == 0) ? null : aL.get(0);