JAL-629 Change behaviour of --open GLOB to increment defaultLinkedId to allow --allfr...
[jalview.git] / src / jalview / bin / argparser / Arg.java
1 package jalview.bin.argparser;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.EnumSet;
6 import java.util.List;
7 import java.util.Locale;
8 import java.util.stream.Collectors;
9
10 public enum Arg
11 {
12   HELP("h"), CALCULATION, MENUBAR, STATUS, SHOWOVERVIEW, ANNOTATIONS,
13   COLOUR, FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, DISPLAY, GUI, NEWS,
14   SORTBYTREE, USAGESTATS, APPEND, OPEN, PROPS, QUESTIONNAIRE, SETPROP, TREE,
15   VDOC, VSESS, OUTPUT, SSANNOTATIONS, NOTEMPFAC, TEMPFAC, TITLE, PAEMATRIX,
16   WRAP, NOSTRUCTURE, STRUCTURE, STRUCTUREVIEWER, IMAGE, QUIT, CLOSE,
17   DEBUG("d"), QUIET("q"), ARGFILE, NEWFRAME, NPP("n++"), SUBSTITUTIONS,
18   INITSUBSTITUTIONS, NIL, SPLASH, SETARGFILE, UNSETARGFILE,
19   WEBSERVICEDISCOVERY, ALLFRAMES;
20
21   protected static enum Opt
22   {
23     BOOLEAN, // This Arg can be specified as --arg or --noarg to give true or
24              // false. A default can be given with setOptions(bool, Opt....).
25              // Use ArgParser.isSet(Arg) to see if this arg was not specified.
26     STRING, // This Arg can accept a value either through --arg=value or --arg
27             // value.
28     UNARY, // This Arg is a boolean value, true if present, false if not. Like
29            // BOOLEAN but without the --noarg option.
30     MULTI, // This Arg can be specified multiple times. Multiple values are
31            // stored in the ArgValuesMap (along with their positional index) for
32            // each linkedId.
33     LINKED, // This Arg can be linked to others through a --arg[linkedId] or
34             // --arg[linkedId]=value. If no linkedId is specified then the
35             // current default linkedId will be used.
36     NODUPLICATEVALUES, // This Arg can only have one value (per linkedId). The
37                        // first value will be used and subsequent values ignored
38                        // with a warning.
39     BOOTSTRAP, // This Arg value(s) can be determined at an earlier stage than
40                // non-BOOTSTRAP Args. Substitutions do not happen in BOOTSTRAP
41                // Args and they cannot be linked or contain SubVals. See
42                // jalview.bin.argparser.BootstrapArgs.
43     GLOB, // This Arg can expand wildcard filename "globs" (e.g.
44           // path/*/filename*). If the Arg value is given as --arg filename*
45           // then the shell will have expanded the glob already, but if
46           // specified as --arg=filename* then the Java glob expansion method
47           // will be used (see FileUtils.getFilenamesFromGlob()). Note that this
48           // might be different from the shell expansion rules.
49     NOACTION, // This Arg does not perform a data task, usually used to control
50               // flow in ArgParser.parse(args).
51     ALLOWSUBSTITUTIONS, // This Arg allows substitutions in its linkedId,
52                         // SubVals and values.
53     PRIVATE, // This Arg is used internally, and cannot be specified by the
54              // user.
55     ALLOWALL, // This Arg can use the '*' linkedId to apply to all known
56               // linkedIds
57     INCREMENTDEFAULTCOUNTER, // If an Arg has this option and the default
58                              // linkedId is used, the defaultLinkedIdCounter is
59                              // incremented *first*.
60     INPUT, // This Arg counts as an input for REQUIREINPUT
61     REQUIREINPUT, // This Arg can only be applied via --allframes if there is an
62                   // input (i.e. --open or --append)
63   }
64
65   static
66   {
67     HELP.setOptions("Display this help message", Opt.UNARY, Opt.BOOTSTRAP);
68     CALCULATION.setOptions(true, Opt.BOOLEAN); // default "true" implies only
69                                                // expecting "--nocalculation"
70     MENUBAR.setOptions(true, Opt.BOOLEAN);
71     STATUS.setOptions(true, Opt.BOOLEAN);
72     SHOWOVERVIEW.setOptions(Opt.UNARY, Opt.LINKED);
73     ANNOTATIONS.setOptions(Opt.BOOLEAN, Opt.LINKED);
74     COLOUR.setOptions(Opt.STRING, Opt.LINKED);
75     FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
76             Opt.ALLOWSUBSTITUTIONS);
77     GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
78             Opt.ALLOWSUBSTITUTIONS);
79     GROUPS.setOptions(Opt.STRING, Opt.LINKED);
80     HEADLESS.setOptions(Opt.UNARY, Opt.BOOTSTRAP);
81     JABAWS.setOptions(Opt.STRING);
82     DISPLAY.setOptions(true, Opt.BOOLEAN);
83     GUI.setOptions(true, Opt.BOOLEAN);
84     NEWS.setOptions(true, Opt.BOOLEAN, Opt.BOOTSTRAP);
85     SPLASH.setOptions(true, Opt.BOOLEAN, Opt.BOOTSTRAP);
86     // expects a string value
87     SORTBYTREE.setOptions(true, Opt.BOOLEAN);
88     USAGESTATS.setOptions(true, Opt.BOOLEAN);
89     APPEND.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB,
90             Opt.ALLOWSUBSTITUTIONS, Opt.INPUT);
91     OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.INCREMENTDEFAULTCOUNTER,
92             Opt.MULTI, Opt.GLOB, Opt.ALLOWSUBSTITUTIONS, Opt.INPUT);
93     PROPS.setOptions(Opt.STRING, Opt.BOOTSTRAP);
94     QUESTIONNAIRE.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP);
95     SETPROP.setOptions(Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP);
96     TREE.setOptions(Opt.STRING);
97
98     VDOC.setOptions(Opt.UNARY);
99     VSESS.setOptions(Opt.UNARY);
100
101     OUTPUT.setOptions(Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS,
102             Opt.ALLOWALL, Opt.REQUIREINPUT);
103
104     SSANNOTATIONS.setOptions(Opt.BOOLEAN, Opt.LINKED);
105     NOTEMPFAC.setOptions(Opt.UNARY, Opt.LINKED);
106     TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
107     TITLE.setOptions(Opt.STRING, Opt.LINKED);
108     PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
109             Opt.ALLOWSUBSTITUTIONS);
110     NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
111     STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI,
112             Opt.ALLOWSUBSTITUTIONS);
113     STRUCTUREVIEWER.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
114     WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
115     IMAGE.setOptions(Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS,
116             Opt.ALLOWALL, Opt.REQUIREINPUT);
117     QUIT.setOptions(Opt.UNARY);
118     CLOSE.setOptions(Opt.UNARY, Opt.LINKED, Opt.ALLOWALL);
119     DEBUG.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP);
120     QUIET.setOptions(Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP);
121     ARGFILE.setOptions(Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB,
122             Opt.ALLOWSUBSTITUTIONS);
123     NEWFRAME.setOptions(Opt.UNARY, Opt.MULTI, Opt.NOACTION);
124     NPP.setOptions(Opt.UNARY, Opt.MULTI, Opt.NOACTION);
125     SUBSTITUTIONS.setOptions(Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION);
126     INITSUBSTITUTIONS.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP, Opt.NOACTION);
127     NIL.setOptions(Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.NOACTION);
128     SETARGFILE.setOptions(Opt.STRING, Opt.MULTI, Opt.PRIVATE, Opt.NOACTION);
129     UNSETARGFILE.setOptions(Opt.MULTI, Opt.PRIVATE, Opt.NOACTION);
130     WEBSERVICEDISCOVERY.setOptions(Opt.BOOLEAN, Opt.BOOTSTRAP);
131     ALLFRAMES.setOptions(Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION);
132
133   }
134
135   private final String[] argNames;
136
137   private Opt[] argOptions;
138
139   private boolean defaultBoolValue = false;
140
141   private String description = null;
142
143   private Arg()
144   {
145     this(new String[0]);
146   }
147
148   private Arg(String... names)
149   {
150     int length = (names == null || names.length == 0
151             || (names.length == 1 && names[0] == null)) ? 1
152                     : names.length + 1;
153     this.argNames = new String[length];
154     this.argNames[0] = this.getName();
155     if (length > 1)
156       System.arraycopy(names, 0, this.argNames, 1, names.length);
157   }
158
159   public String argString()
160   {
161     return argString(false);
162   }
163
164   public String negateArgString()
165   {
166     return argString(true);
167   }
168
169   private String argString(boolean negate)
170   {
171     StringBuilder sb = new StringBuilder(ArgParser.DOUBLEDASH);
172     if (negate && hasOption(Opt.BOOLEAN))
173       sb.append(ArgParser.NEGATESTRING);
174     sb.append(getName());
175     return sb.toString();
176   }
177
178   public String toLongString()
179   {
180     StringBuilder sb = new StringBuilder();
181     sb.append(this.getClass().getName()).append('.').append(this.name());
182     sb.append('(');
183     if (getNames().length > 0)
184       sb.append('"');
185     sb.append(String.join("\", \"", getNames()));
186     if (getNames().length > 0)
187       sb.append('"');
188     sb.append(")\n");
189     sb.append("\nOpt: ");
190     // map List<Opt> to List<String> for the String.join
191     List<String> optList = Arrays.asList(argOptions).stream()
192             .map(opt -> opt.name()).collect(Collectors.toList());
193     sb.append(String.join(", ", optList));
194     sb.append("\n");
195     return sb.toString();
196   }
197
198   public String[] getNames()
199   {
200     return argNames;
201   }
202
203   public String getName()
204   {
205     return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
206   }
207
208   @Override
209   public final String toString()
210   {
211     return getName();
212   }
213
214   public boolean hasOption(Opt o)
215   {
216     if (argOptions == null)
217       return false;
218     for (Opt option : argOptions)
219     {
220       if (o == option)
221         return true;
222     }
223     return false;
224   }
225
226   protected void setOptions(Opt... options)
227   {
228     setOptions("", false, options);
229   }
230
231   protected void setOptions(String desc, Opt... options)
232   {
233     setOptions(desc, false, options);
234   }
235
236   protected void setOptions(boolean defaultBoolValue, Opt... options)
237   {
238     setOptions("", defaultBoolValue, options);
239   }
240
241   protected void setOptions(String desc, boolean defaultBoolValue,
242           Opt... options)
243   {
244     this.description = desc;
245     this.defaultBoolValue = defaultBoolValue;
246     this.argOptions = options;
247   }
248
249   protected boolean getDefaultBoolValue()
250   {
251     return defaultBoolValue;
252   }
253
254   private void setDescription(String d)
255   {
256     description = d;
257   }
258
259   protected String getDescription()
260   {
261     return description;
262   }
263
264   public static String booleanArgString(Arg a)
265   {
266     StringBuilder sb = new StringBuilder(a.argString());
267     if (a.hasOption(Opt.BOOLEAN))
268     {
269       sb.append('/');
270       sb.append(a.negateArgString());
271     }
272     return sb.toString();
273   }
274
275   public static final String usage()
276   {
277     StringBuilder sb = new StringBuilder();
278
279     sb.append("Usage: jalview [args]");
280     sb.append(System.lineSeparator());
281
282     int maxArgLength = 0;
283     for (Arg a : EnumSet.allOf(Arg.class))
284     {
285       if (a.hasOption(Opt.PRIVATE))
286         continue;
287       StringBuilder argSb = new StringBuilder();
288       argSb.append(a.hasOption(Opt.BOOLEAN) ? booleanArgString(a)
289               : a.argString());
290       if (a.hasOption(Opt.STRING))
291         argSb.append("=value");
292       if (argSb.length() > maxArgLength)
293         maxArgLength = argSb.length();
294     }
295
296     // might want to sort these
297     for (Arg a : EnumSet.allOf(Arg.class))
298     {
299       if (a.hasOption(Opt.PRIVATE))
300         continue;
301       StringBuilder argSb = new StringBuilder();
302       argSb.append(a.hasOption(Opt.BOOLEAN) ? booleanArgString(a)
303               : a.argString());
304       if (a.hasOption(Opt.STRING))
305         argSb.append("=value");
306       sb.append(String.format("%-" + maxArgLength + "s  - %s",
307               argSb.toString(), a.getDescription()));
308
309       List<String> options = new ArrayList<>();
310
311       if (a.hasOption(Opt.BOOLEAN))
312       {
313         options.add("default " + (a.getDefaultBoolValue() ? a.argString()
314                 : a.negateArgString()));
315       }
316
317       if (a.hasOption(Opt.MULTI))
318       {
319         options.add("multiple");
320       }
321
322       if (a.hasOption(Opt.LINKED))
323       {
324         options.add("can be linked");
325       }
326
327       if (a.hasOption(Opt.GLOB))
328       {
329         options.add("allows file globs");
330       }
331
332       if (a.hasOption(Opt.ALLOWSUBSTITUTIONS))
333       {
334         options.add("allows substitutions");
335       }
336
337       if (options.size() > 0)
338       {
339         sb.append(" (");
340         sb.append(String.join("; ", options));
341         sb.append(')');
342       }
343       sb.append(System.lineSeparator());
344     }
345     return sb.toString();
346   }
347 }