JAL-629 Added Arg.Types and additional --help-type args to be --help with a type...
[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.Collections;
6 import java.util.Comparator;
7 import java.util.EnumSet;
8 import java.util.Iterator;
9 import java.util.List;
10 import java.util.Locale;
11 import java.util.stream.Collectors;
12
13 import jalview.bin.argparser.Arg.Opt;
14 import jalview.util.ChannelProperties;
15 import jalview.util.Platform;
16
17 public enum Arg
18 {
19
20   // Initialising arguments (BOOTSTRAP)
21   HELP(Type.HELP, "h", "Display basic help", Opt.UNARY, Opt.BOOTSTRAP,
22           Opt.HASTYPE, Opt.MULTI),
23   /*
24    * Other --help-type Args will be added by the static block.
25    */
26   VERSION(Type.CONFIG, "v",
27           "Display the version of "
28                   + ChannelProperties.getProperty("app_name"),
29           Opt.UNARY, Opt.BOOTSTRAP),
30   HEADLESS(Type.CONFIG,
31           "Run Jalview in headless mode. No GUI interface will be created and Jalview will quit after all arguments have been processed.",
32           Opt.UNARY, Opt.BOOTSTRAP),
33   JABAWS(Type.CONFIG, "Set a different URL to connect to a JABAWS server.",
34           Opt.STRING, Opt.BOOTSTRAP),
35   NEWS(Type.CONFIG, "Show (or don't show) the news feed.", true,
36           Opt.BOOLEAN, Opt.BOOTSTRAP),
37   SPLASH(Type.CONFIG,
38           "Show (or don't show) the About Jalview splash screen.", true,
39           Opt.BOOLEAN, Opt.BOOTSTRAP),
40   QUESTIONNAIRE(Type.CONFIG,
41           "Show (or don't show) the questionnaire if one is available.",
42           true, Opt.BOOLEAN, Opt.BOOTSTRAP),
43   USAGESTATS(Type.CONFIG,
44           "Send (or don't send) initial launch usage stats.", true,
45           Opt.BOOLEAN, Opt.BOOTSTRAP),
46   WEBSERVICEDISCOVERY(Type.CONFIG,
47           "Attempt (or don't attempt) to connect to JABAWS web services.",
48           true, Opt.BOOLEAN, Opt.BOOTSTRAP),
49   PROPS(Type.CONFIG,
50           "Use a file as the preferences file instead of the usual ~/"
51                   + ChannelProperties.getProperty("preferences.filename")
52                   + " file.",
53           Opt.STRING, Opt.BOOTSTRAP),
54   DEBUG(Type.CONFIG, "d", "Start Jalview in debug log level.", Opt.BOOLEAN,
55           Opt.BOOTSTRAP),
56   TRACE(Type.CONFIG, "Start Jalview in trace log level.", Opt.BOOLEAN,
57           Opt.BOOTSTRAP, Opt.SECRET),
58   QUIET(Type.CONFIG, "q",
59           "Stop all output to STDOUT (after the Java Virtual Machine has started). Use ‑‑quiet a second time to stop all output to STDERR.",
60           Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP),
61   INITSUBSTITUTIONS(Type.CONFIG,
62           "Set ‑‑substitutions to be initially enabled (or initially disabled).",
63           true, Opt.BOOLEAN, Opt.BOOTSTRAP, Opt.NOACTION, Opt.SECRET),
64
65   // Opening an alignment
66   OPEN(Type.OPENING,
67           "Opens one or more alignment files or URLs in new alignment windows.",
68           Opt.STRING, Opt.LINKED, Opt.INCREMENTDEFAULTCOUNTER, Opt.MULTI,
69           Opt.GLOB, Opt.ALLOWSUBSTITUTIONS, Opt.INPUT, Opt.STORED,
70           Opt.PRIMARY),
71   APPEND(Type.OPENING,
72           "Appends one or more alignment files or URLs to the open alignment window (or opens a new alignment if none already open).",
73           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB,
74           Opt.ALLOWSUBSTITUTIONS, Opt.INPUT, Opt.PRIMARY),
75   TITLE(Type.OPENING,
76           "Specifies the title for the open alignment window as string.",
77           Opt.STRING, Opt.LINKED),
78   COLOUR(Type.OPENING, "color", // being a bit soft on the Americans!
79           "Applies the colour scheme to the open alignment window. Valid values are:\n"
80                   + "clustal,\n" + "blosum62,\n" + "pc-identity,\n"
81                   + "zappo,\n" + "taylor,\n" + "gecos-flower,\n"
82                   + "gecos-blossom,\n" + "gecos-sunset,\n"
83                   + "gecos-ocean,\n" + "hydrophobic,\n"
84                   + "helix-propensity,\n" + "strand-propensity,\n"
85                   + "turn-propensity,\n" + "buried-index,\n"
86                   + "nucleotide,\n" + "nucleotide-ambiguity,\n"
87                   + "purine-pyrimidine,\n" + "rna-helices,\n"
88                   + "t-coffee-scores,\n" + "sequence-id.",
89           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
90   FEATURES(Type.OPENING, "Add a feature file or URL to the open alignment.",
91           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
92   TREE(Type.OPENING, "Add a tree file or URL to the open alignment.",
93           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
94   SORTBYTREE(Type.OPENING,
95           "Enforces sorting (or not sorting) the open alignment in the order of an attached phylogenetic tree.",
96           true, Opt.LINKED, Opt.BOOLEAN, Opt.ALLOWALL),
97   ANNOTATIONS(Type.OPENING,
98           "Add an annotations file or URL to the open alignment.",
99           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
100   SHOWANNOTATIONS(Type.OPENING,
101           "Enforces showing (or not showing) alignment annotations.",
102           Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
103   WRAP(Type.OPENING,
104           "Enforces wrapped (or not wrapped) alignment formatting.",
105           Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
106   NOSTRUCTURE(Type.OPENING,
107           "Do not open or process any 3D structure in the ‑‑open or ‑‑append files.",
108           Opt.UNARY, Opt.LINKED, Opt.ALLOWALL),
109
110   // Adding a 3D structure
111   STRUCTURE(Type.STRUCTURE,
112           "Load a structure file or URL associated with a sequence in the open alignment.\n"
113                   + "The sequence to be associated with can be specified with a following --seqid argument, or the subval modifier seqid=ID can be used. A subval INDEX can also be used to specify the INDEX-th sequence in the open alignment.",
114           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS,
115           Opt.PRIMARY),
116   SEQID(Type.STRUCTURE,
117           "Specify the sequence name for the preceding --structure to be associated with.",
118           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
119   PAEMATRIX(Type.STRUCTURE,
120           "Add a PAE json matrix file to the preceding --structure.",
121           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
122   TEMPFAC(Type.STRUCTURE,
123           "Set the type of temperature factor. Possible values are:\n"
124                   + "default,\n" + "plddt.",
125           Opt.STRING, Opt.LINKED),
126   STRUCTUREVIEWER(Type.STRUCTURE,
127           "Set the structure viewer to use to open the 3D structure file specified in previous --structure to name. Possible values of name are:\n"
128                   + "none,\n" + "jmol,\n" + "chimera,\n" + "chimerax,\n"
129                   + "pymol.",
130           Opt.STRING, Opt.LINKED, Opt.MULTI),
131   STRUCTUREIMAGE(Type.STRUCTURE,
132           "Export an image of a 3D structure opened in JMOL", Opt.STRING,
133           Opt.LINKED, Opt.MULTI),
134   NOTEMPFAC(Type.STRUCTURE,
135           "Do not show the temperature factor annotation for the preceding --structure.",
136           Opt.UNARY, Opt.LINKED, Opt.ALLOWALL, Opt.SECRET), // keep this secret
137                                                             // until it
138   // works!
139   SHOWSSANNOTATIONS(Type.STRUCTURE, null, Opt.BOOLEAN, Opt.LINKED,
140           Opt.ALLOWALL),
141
142   // Outputting files
143   IMAGE(Type.IMAGE,
144           "Output an image of the open alignment window. Format is specified by the subval modifier, a following --type argument or guessed from the file extension. Valid formats/extensions are:\n"
145                   + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
146           Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL,
147           Opt.REQUIREINPUT, Opt.OUTPUT, Opt.PRIMARY),
148   TYPE(Type.IMAGE,
149           "Set the image format for the preceding --image to name. Valid values for name are: svg,\n"
150                   + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
151           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
152   TEXTRENDERER(Type.IMAGE,
153           "Sets whether text in a vector image format (SVG, HTML, EPS) should be rendered as text or vector line-art. Possible values for name are:\n"
154                   + "text,\n" + "lineart.",
155           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
156   SCALE(Type.IMAGE,
157           "Sets a scaling for bitmap image format (PNG). Should be given as a floating point number. If used in conjunction with --width and --height then the smallest scaling will be used (scale, width and height provide bounds for the image).",
158           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
159   WIDTH(Type.IMAGE,
160           "Sets a width for bitmap image format (PNG) with the height maintaining the aspect ratio. Should be given as a positive integer. If used in conjunction with --scale and --height then the smallest scaling will be used (scale, width and height provide bounds for the image).",
161           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
162   HEIGHT(Type.IMAGE,
163           "Sets a height for bitmap image format (PNG) with the width maintaining the aspect ratio. Should be given as a positive integer. If used in conjunction with --scale and --width then the smallest scaling will be used (scale, width and height provide bounds for the image).",
164           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
165   OUTPUT(Type.OUTPUT,
166           "Export the open alignment to file filename. The format name is specified by the subval modifier format=name, a following --format name argument or guessed from the file extension. Valid format names (and file extensions) are:\n"
167                   + "fasta (fa, fasta, mfa, fastq),\n" + "pfam (pfam),\n"
168                   + "stockholm (sto, stk),\n" + "pir (pir),\n"
169                   + "blc (blc),\n" + "amsa (amsa),\n" + "json (json),\n"
170                   + "pileup (pileup),\n" + "msf (msf),\n"
171                   + "clustal (aln),\n" + "phylip (phy),\n"
172                   + "jalview (jvp, jar).",
173           Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL,
174           Opt.REQUIREINPUT, Opt.OUTPUT, Opt.PRIMARY),
175   FORMAT(Type.OUTPUT,
176           "Sets the format for the preceding --output file. Valid formats are:\n"
177                   + "fasta,\n" + "pfam,\n" + "stockholm,\n" + "pir,\n"
178                   + "blc,\n" + "amsa,\n" + "json,\n" + "pileup,\n"
179                   + "msf,\n" + "clustal,\n" + "phylip,\n" + "jalview.",
180           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
181   GROOVY(Type.PROCESS,
182           "Process a groovy script in the file for the open alignment.",
183           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS,
184           Opt.ALLOWALL),
185   BACKUPS(Type.OUTPUT,
186           "Enable (or disable) writing backup files when saving an ‑‑output file. This applies to the current open alignment.  To apply to all ‑‑output and ‑‑image files, use after ‑‑all.",
187           true, Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
188   OVERWRITE(Type.OUTPUT,
189           "Enable (or disable) overwriting of output files without backups enabled. This applies to the current open alignment.  To apply to all ‑‑output and ‑‑image files, use after ‑‑all.",
190           Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
191   CLOSE(Type.OPENING,
192           "Close the current open alignment window. This occurs after other output arguments. This applies to the current open alignment.  To apply to all ‑‑output and ‑‑image files, use after ‑‑all.",
193           Opt.UNARY, Opt.LINKED, Opt.ALLOWALL),
194
195   // controlling flow of arguments
196   NEW(Type.FLOW,
197           "Move on to a new alignment window. This will ensure --append will start a new alignment window and other linked arguments will apply to the new alignment window.",
198           Opt.UNARY, Opt.MULTI, Opt.NOACTION, Opt.INCREMENTDEFAULTCOUNTER),
199   SUBSTITUTIONS(Type.FLOW,
200           "The following argument values allow (or don't allow) subsituting filename parts. This is initially true. Valid substitutions are {basename} - the filename-without-extension of the currently --opened file (or first --appended file),\n"
201                   + "{dirname}, - the directory (folder) name of the currently --opened file (or first --appended file),\n"
202                   + "{argfilebasename} - the filename-without-extension of the current --argfile,\n"
203                   + "{argfiledirname} - the directory (folder) name of the current --argfile,\n"
204                   + "{n} - the value of the index counter (starting at 0).\n"
205                   + "{++n} - increase and substitute the value of the index counter,\n"
206                   + "{} - the value of the current alignment window default index.",
207           true, Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
208   ARGFILE(Type.FLOW,
209           "Open one or more files filename and read, line-by-line, as arguments to Jalview.\n"
210                   + "Values in an argfile should be given with an equals sign (\"=\") separator with no spaces.\n"
211                   + "Note that if you use one or more --argfile arguments then all other non-initialising arguments will be ignored.",
212           Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB,
213           Opt.ALLOWSUBSTITUTIONS),
214   NPP(Type.FLOW, "n++",
215           "Increase the index counter used in argument value substitutions.",
216           Opt.UNARY, Opt.MULTI, Opt.NOACTION),
217   ALL(Type.FLOW,
218           "Apply the following output arguments to all sets of linked arguments.",
219           Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
220   OPENED(Type.FLOW,
221           "Apply the following output arguments to all of the last --open'ed set of linked arguments.",
222           Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
223   QUIT(Type.FLOW,
224           "After all files have been opened, appended and output, quit Jalview. In ‑‑headless mode this already happens.",
225           Opt.UNARY),
226
227   // secret options
228   TESTOUTPUT(Type.CONFIG,
229           "Allow specific stdout information.  For testing purposes only.",
230           Opt.UNARY, Opt.BOOTSTRAP, Opt.SECRET), // do not show this to the user
231   SETPROP(Type.CONFIG, "Set an individual Java System property.",
232           Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.SECRET), // not in use yet
233   NIL(Type.FLOW,
234           "This argument does nothing on its own, but can be used with linkedIds.",
235           Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.NOACTION, Opt.SECRET),
236
237   // private options (inserted during arg processing)
238   SETARGFILE(Type.FLOW,
239           "Sets the current value of the argfilename.  Inserted before argfilecontents.",
240           Opt.UNARY, Opt.LINKED, Opt.STRING, Opt.MULTI, Opt.PRIVATE,
241           Opt.NOACTION),
242   UNSETARGFILE(Type.FLOW,
243           "Unsets the current value of the argfilename.  Inserted after argfile contents.",
244           Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.PRIVATE, Opt.NOACTION),
245
246   // these last two have no purpose in the normal Jalview application but are
247   // used by jalview.bin.Launcher to set memory settings. They are not used by
248   // argparser but are here for Usage statement reasons.
249   JVMMEMPC(Type.CONFIG,
250           "Limit maximum heap size (memory) to PERCENT% of total physical memory detected. This defaults to 90 if total physical memory can be detected.\n"
251                   + "The equals sign (\"=\") separator must be used with no spaces.",
252           Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING, Opt.LAST),
253   JVMMEMMAX(Type.CONFIG,
254           "Limit maximum heap size (memory) to MAXMEMORY. MAXMEMORY can be specified in bytes, kilobytes(k), megabytes(m), gigabytes(g) or if you're lucky enough, terabytes(t). This defaults to 32g if total physical memory can be detected, or to 8g if total physical memory cannot be detected.\n"
255                   + "The equals sign (\"=\") separator must be used with no spaces.",
256           Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING, Opt.LAST),
257
258   ;
259
260   public static enum Opt
261   {
262     BOOLEAN, // This Arg can be specified as --arg or --noarg to give true or
263              // false. A default can be given with setOptions(bool, Opt....).
264              // Use ArgParser.isSet(Arg) to see if this arg was not specified.
265     STRING, // This Arg can accept a value either through --arg=value or --arg
266             // value.
267     UNARY, // This Arg is a boolean value, true if present, false if not. Like
268            // BOOLEAN but without the --noarg option.
269     MULTI, // This Arg can be specified multiple times. Multiple values are
270            // stored in the ArgValuesMap (along with their positional index) for
271            // each linkedId.
272     LINKED, // This Arg can be linked to others through a --arg[linkedId] or
273             // --arg[linkedId]=value. If no linkedId is specified then the
274             // current default linkedId will be used.
275     NODUPLICATEVALUES, // This Arg can only have one value (per linkedId). The
276                        // first value will be used and subsequent values ignored
277                        // with a warning.
278     BOOTSTRAP, // This Arg value(s) can be determined at an earlier stage than
279                // non-BOOTSTRAP Args. Substitutions do not happen in BOOTSTRAP
280                // Args and they cannot be linked or contain SubVals. See
281                // jalview.bin.argparser.BootstrapArgs.
282     GLOB, // This Arg can expand wildcard filename "globs" (e.g.
283           // path/*/filename*). If the Arg value is given as --arg filename*
284           // then the shell will have expanded the glob already, but if
285           // specified as --arg=filename* then the Java glob expansion method
286           // will be used (see FileUtils.getFilenamesFromGlob()). Note that this
287           // might be different from the shell expansion rules.
288     NOACTION, // This Arg does not perform a data task, usually used to control
289               // flow in ArgParser.parse(args).
290     ALLOWSUBSTITUTIONS, // This Arg allows substitutions in its linkedId,
291                         // SubVals and values.
292     PRIVATE, // This Arg is used internally, and cannot be specified by the
293              // user.
294     SECRET, // This Arg is used by development processes and although it can be
295             // set by the user, it is not displayed to the user.
296     ALLOWALL, // This Arg can use the '*' linkedId to apply to all known
297               // linkedIds
298     INCREMENTDEFAULTCOUNTER, // If an Arg has this option and the default
299                              // linkedId is used, the defaultLinkedIdCounter is
300                              // incremented *first*.
301     INPUT, // This Arg counts as an input for REQUIREINPUT
302     REQUIREINPUT, // This Arg can only be applied via --all if there is an
303                   // input (i.e. --open or --append)
304     OUTPUT, // This Arg provides an output filename. With Opt.ALLOWALL *.ext is
305             // shorthand for --all --output={basename}.ext
306     STORED, // This Arg resets and creates a new set of "opened" linkedIds
307     HELP, // This Arg is a --help type arg
308     PRIMARY, // This Arg is the main Arg for its type
309     HASTYPE, // This Arg can have an Arg.Type assigned to it (and no value)
310     FIRST, // Move this arg to the first in usage statement (within type)
311     LAST, // Move this arg to the end in usage statement (within type)
312   }
313
314   public static enum Type
315   {
316     // Type restricts argument to certain usage output
317     HELP, // --help
318     CONFIG("arguments used to configure "
319             + ChannelProperties.getProperty("app_name") + " from startup"),
320     OPENING("arguments used to open and format alignments"),
321     STRUCTURE("arguments used to add and format 3D structure data"),
322     PROCESS("arguments used to process an alignment once opened"),
323     OUTPUT("arguments used to save data from a processed alignment"),
324     IMAGE("arguments used to export an image of an alignment"),
325     FLOW("arguments that control processing of the other arguments"), //
326     ALL("all arguments"), // mostly just a place-holder for --help-all
327     NONE, // mostly a place-holder for --help
328     INVALID;
329
330     private String description;
331
332     private Type()
333     {
334       description = null;
335     }
336
337     private Type(String description)
338     {
339       this.description = description;
340     }
341
342     public String description()
343     {
344       return description;
345     }
346   }
347
348   private final String[] argNames;
349
350   private Opt[] argOptions;
351
352   private boolean defaultBoolValue;
353
354   private String description;
355
356   private Type type;
357
358   private Arg(Type type, String description, Opt... options)
359   {
360     this(type, null, description, false, options);
361   }
362
363   private Arg(Type type, String description, boolean defaultBoolean,
364           Opt... options)
365   {
366     this(type, null, description, defaultBoolean, options);
367   }
368
369   private Arg(Type type, String alternativeName, String description,
370           Opt... options)
371   {
372     this(type, alternativeName, description, false, options);
373   }
374
375   private Arg(Type type, String alternativeName, String description,
376           boolean defaultBoolean, Opt... options)
377   {
378     this.argNames = alternativeName != null
379             ? new String[]
380             { this.getName(), alternativeName }
381             : new String[]
382             { this.getName() };
383     this.type = type;
384     this.description = description;
385     this.defaultBoolValue = defaultBoolean;
386     this.setOptions(options);
387   }
388
389   public String argString()
390   {
391     return argString(false);
392   }
393
394   public String negateArgString()
395   {
396     return argString(true);
397   }
398
399   private String argString(boolean negate)
400   {
401     StringBuilder sb = new StringBuilder(ArgParser.DOUBLEDASH);
402     if (negate && hasOption(Opt.BOOLEAN))
403       sb.append(ArgParser.NEGATESTRING);
404     sb.append(getName());
405     return sb.toString();
406   }
407
408   public String toLongString()
409   {
410     StringBuilder sb = new StringBuilder();
411     sb.append(this.getClass().getName()).append('.').append(this.name());
412     sb.append('(');
413     if (getNames().length > 0)
414       sb.append('"');
415     sb.append(String.join("\", \"", getNames()));
416     if (getNames().length > 0)
417       sb.append('"');
418     sb.append(")\n");
419     sb.append("\nType: " + type.name());
420     sb.append("\nOpt: ");
421     // map List<Opt> to List<String> for the String.join
422     List<String> optList = Arrays.asList(argOptions).stream()
423             .map(opt -> opt.name()).collect(Collectors.toList());
424     sb.append(String.join(", ", optList));
425     sb.append("\n");
426     return sb.toString();
427   }
428
429   public String[] getNames()
430   {
431     return argNames;
432   }
433
434   public String getName()
435   {
436     return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
437   }
438
439   @Override
440   public final String toString()
441   {
442     return getName();
443   }
444
445   public boolean hasOption(Opt o)
446   {
447     if (argOptions == null)
448       return false;
449     for (Opt option : argOptions)
450     {
451       if (o == option)
452         return true;
453     }
454     return false;
455   }
456
457   public boolean hasAllOptions(Opt... opts)
458   {
459     for (Opt o : opts)
460     {
461       if (!this.hasOption(o))
462         return false;
463     }
464     return true;
465   }
466
467   protected void setOptions(Opt... options)
468   {
469     this.argOptions = options;
470   }
471
472   protected boolean getDefaultBoolValue()
473   {
474     return defaultBoolValue;
475   }
476
477   public Type getType()
478   {
479     return this.type;
480   }
481
482   protected String getDescription()
483   {
484     return description;
485   }
486
487   public static String booleanArgString(Arg a)
488   {
489     StringBuilder sb = new StringBuilder(a.argString());
490     if (a.hasOption(Opt.BOOLEAN))
491     {
492       sb.append('/');
493       sb.append(a.negateArgString());
494     }
495     return sb.toString();
496   }
497
498   public static final String usage()
499   {
500     return usage(null);
501   }
502
503   public static final void appendUsageGeneral(StringBuilder sb,
504           int maxArgLength)
505   {
506     for (Type t : EnumSet.allOf(Type.class))
507     {
508       if (t.description() != null)
509       {
510         StringBuilder argSb = new StringBuilder();
511         argSb.append(Arg.HELP.argString()).append(ArgParser.SINGLEDASH)
512                 .append(t.name().toLowerCase(Locale.ROOT));
513         appendArgAndDescription(sb, argSb.toString(),
514                 "Help for " + t.description(), null, maxArgLength);
515         sb.append(System.lineSeparator());
516       }
517     }
518   }
519
520   public static final String usage(List<Type> types)
521   {
522     StringBuilder sb = new StringBuilder();
523
524     sb.append("usage: jalview [" + Arg.HEADLESS.argString() + "] ["
525             + Arg.OPEN.argString() + "/" + Arg.APPEND.argString()
526             + " file(s)] [args]");
527     sb.append(System.lineSeparator());
528     sb.append(System.lineSeparator());
529
530     if (types == null || types.contains(null))
531     {
532       // always show --help
533       appendArgAndDescription(sb, null, "Display this basic help", Arg.HELP,
534               DESCRIPTIONINDENT);
535       sb.append(System.lineSeparator());
536
537       appendUsageGeneral(sb, DESCRIPTIONINDENT);
538     }
539     else
540     {
541       List<Arg> args = argsSortedForDisplay(types);
542
543       /*
544        * just use a set maxArgLength of DESCRIPTIONINDENT
545        
546       int maxArgLength = 0;
547       for (Arg a : args)
548       {
549         if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET))
550           continue;
551       
552         String argS = argDisplayString(a);
553         if (argS.length() > maxArgLength)
554           maxArgLength = argS.length();
555       }
556       */
557       int maxArgLength = DESCRIPTIONINDENT;
558
559       // always show --help
560       appendArgAndDescription(sb, null, null, Arg.HELP, maxArgLength);
561       sb.append(System.lineSeparator());
562
563       if ((args.contains(Arg.HELP) && types.contains(Type.ALL)))
564       {
565         appendUsageGeneral(sb, maxArgLength);
566       }
567
568       Iterator<Arg> argsI = args.iterator();
569       while (argsI.hasNext())
570       {
571         Arg a = argsI.next();
572
573         if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET)
574                 || a == Arg.HELP)
575         {
576           continue;
577         }
578
579         appendArgUsage(sb, a, maxArgLength);
580
581         if (argsI.hasNext())
582           sb.append(System.lineSeparator());
583       }
584     }
585     return sb.toString();
586   }
587
588   private static void appendArgUsage(StringBuilder sb, Arg a,
589           int maxArgLength)
590   {
591     boolean first = appendArgAndDescription(sb, null, null, a,
592             maxArgLength);
593     List<String> options = new ArrayList<>();
594
595     if (a.hasOption(Opt.BOOLEAN))
596     {
597       options.add("default " + (a.getDefaultBoolValue() ? a.argString()
598               : a.negateArgString()));
599     }
600
601     if (a.hasOption(Opt.MULTI))
602     {
603       options.add("multiple");
604     }
605
606     if (a.hasOption(Opt.LINKED))
607     {
608       options.add("can be linked");
609     }
610
611     if (a.hasOption(Opt.GLOB))
612     {
613       options.add("allows file globs");
614     }
615
616     if (a.hasOption(Opt.ALLOWSUBSTITUTIONS))
617     {
618       options.add("allows substitutions");
619     }
620
621     if (a.hasOption(Opt.ALLOWALL))
622     {
623       options.add("can be applied to all linked arguments");
624     }
625
626     if (a.hasOption(Opt.PRIVATE))
627     {
628       options.add("for internal use only");
629     }
630
631     if (a.hasOption(Opt.SECRET))
632     {
633       options.add("for development use only");
634     }
635
636     if (options.size() > 0)
637     {
638       if (first)
639       {
640         sb.append(ARGDESCRIPTIONSEPARATOR);
641       }
642       else
643       {
644         sb.append(String.format("%-"
645                 + (maxArgLength + ARGDESCRIPTIONSEPARATOR.length()) + "s",
646                 ""));
647       }
648       sb.append("(");
649       sb.append(String.join("; ", options));
650       sb.append(')');
651       sb.append(System.lineSeparator());
652     }
653   }
654
655   public static String argDisplayString(Arg a)
656   {
657     StringBuilder argSb = new StringBuilder();
658     argSb.append(
659             a.hasOption(Opt.BOOLEAN) ? booleanArgString(a) : a.argString());
660     if (a.hasOption(Opt.STRING))
661       argSb.append("=value");
662     return argSb.toString();
663   }
664
665   public static boolean appendArgAndDescription(StringBuilder sb,
666           String aString, String description, Arg a, int maxArgLength)
667   {
668     return appendArgAndDescription(sb, aString, description, a,
669             maxArgLength, Platform.consoleWidth());
670   }
671
672   public static boolean appendArgAndDescription(StringBuilder sb,
673           String aString, String description, Arg a, int maxArgLength,
674           int maxLength)
675   {
676     if (aString == null && a != null)
677     {
678       aString = argDisplayString(a);
679     }
680     if (description == null && a != null)
681     {
682       description = a.getDescription();
683     }
684     sb.append(String.format("%-" + maxArgLength + "s", aString));
685     if (aString.length() > maxArgLength)
686     {
687       sb.append(System.lineSeparator());
688       sb.append(String.format("%-" + maxArgLength + "s", ""));
689     }
690
691     int descLength = maxLength - maxArgLength
692             - ARGDESCRIPTIONSEPARATOR.length();
693     // reformat the descriptions lines to the right width
694     Iterator<String> descLines = null;
695     if (description != null)
696     {
697       descLines = Arrays.stream(description.split("\\n")).iterator();
698     }
699     List<String> splitDescLinesList = new ArrayList<>();
700     while (descLines != null && descLines.hasNext())
701     {
702       String line = descLines.next();
703       while (line.length() > descLength)
704       {
705         int splitIndex = line.lastIndexOf(" ", descLength);
706         if (splitIndex > descLength)
707         {
708           break;
709         }
710         else
711         {
712           splitDescLinesList.add(line.substring(0, splitIndex));
713           line = line.substring(splitIndex + 1);
714         }
715       }
716       splitDescLinesList.add(line);
717     }
718
719     Iterator<String> splitDescLines = splitDescLinesList.iterator();
720     boolean first = true;
721     if (splitDescLines != null)
722     {
723       while (splitDescLines.hasNext())
724       {
725         if (first)
726           sb.append(ARGDESCRIPTIONSEPARATOR);
727         else
728           sb.append(String.format("%-"
729                   + (maxArgLength + ARGDESCRIPTIONSEPARATOR.length()) + "s",
730                   ""));
731         sb.append(splitDescLines.next());
732         sb.append(System.lineSeparator());
733         first = false;
734       }
735     }
736     return first;
737   }
738
739   protected static Iterator<Arg> getAllOfType(Type type)
740   {
741     return getAllOfType(type, new Opt[] {});
742   }
743
744   protected static Iterator<Arg> getAllOfType(Type type, Opt... options)
745   {
746     Opt[] opts = options == null ? new Opt[] {} : options;
747     return EnumSet.allOf(Arg.class).stream().filter(a -> {
748       if (a.getType() != type)
749         return false;
750       for (Opt o : opts)
751       {
752         if (!a.hasOption(o))
753           return false;
754       }
755       return true;
756     }).iterator();
757   }
758
759   private static List<Arg> argsSortedForDisplay(List<Type> types)
760   {
761     List<Arg> argsToSort;
762     // if no types provided, do all
763     if (types == null || types.size() == 0 || types.contains(Type.ALL))
764     {
765       argsToSort = Arrays
766               .asList(EnumSet.allOf(Arg.class).toArray(new Arg[] {}));
767     }
768     else
769     {
770       argsToSort = new ArrayList<>();
771       for (Type type : types)
772       {
773         if (type == null)
774           continue;
775         Arg.getAllOfType(type).forEachRemaining(a -> argsToSort.add(a));
776       }
777     }
778
779     Collections.sort(argsToSort, new ArgDisplayComparator());
780     return argsToSort;
781   }
782
783   private static final String ARGDESCRIPTIONSEPARATOR = " - ";
784
785   private static final int DESCRIPTIONINDENT = 20;
786
787 }
788
789 class ArgDisplayComparator implements Comparator<Arg>
790 {
791   private int compareArgOpts(Arg a, Arg b, Opt o)
792   {
793     int i = a.hasOption(o) ? (b.hasOption(o) ? 0 : -1)
794             : (b.hasOption(o) ? 1 : 0);
795     return i;
796   }
797
798   private int compareForDisplay(Arg a, Arg b)
799   {
800     if (b == null)
801       return -1;
802     // first compare types (in enum order)
803     int i = a.getType().compareTo(b.getType());
804     if (i != 0)
805       return i;
806     // do Opt.LAST next (oddly). Reversed args important!
807     i = compareArgOpts(b, a, Opt.LAST);
808     if (i != 0)
809       return i;
810     // priority order
811     Opt[] optOrder = { Opt.HELP, Opt.FIRST, Opt.PRIMARY, Opt.STRING,
812         Opt.BOOLEAN };
813     for (Opt o : optOrder)
814     {
815       i = compareArgOpts(a, b, o);
816       if (i != 0)
817         return i;
818     }
819     // finally order of appearance in enum declarations
820     return a.compareTo(b);
821   }
822
823   @Override
824   public int compare(Arg a, Arg b)
825   {
826     return compareForDisplay(a, b);
827   }
828 }