JAL-629 Clarify sections of arguments with --help-all (or multiple --help-...)
[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:\n"
201                   + "{basename} - the filename-without-extension of the currently --opened file (or first --appended file),\n"
202                   + "{dirname} - the directory (folder) name of the currently --opened file (or first --appended file),\n"
203                   + "{argfilebasename} - the filename-without-extension of the current --argfile,\n"
204                   + "{argfiledirname} - the directory (folder) name of the current --argfile,\n"
205                   + "{n} - the value of the index counter (starting at 0).\n"
206                   + "{++n} - increase and substitute the value of the index counter,\n"
207                   + "{} - the value of the current alignment window default index.",
208           true, Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
209   ARGFILE(Type.FLOW,
210           "Open one or more files filename and read, line-by-line, as arguments to Jalview.\n"
211                   + "Values in an argfile should be given with an equals sign (\"=\") separator with no spaces.\n"
212                   + "Note that if you use one or more --argfile arguments then all other non-initialising arguments will be ignored.",
213           Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB,
214           Opt.ALLOWSUBSTITUTIONS),
215   NPP(Type.FLOW, "n++",
216           "Increase the index counter used in argument value substitutions.",
217           Opt.UNARY, Opt.MULTI, Opt.NOACTION),
218   ALL(Type.FLOW,
219           "Apply the following output arguments to all sets of linked arguments.",
220           Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
221   OPENED(Type.FLOW,
222           "Apply the following output arguments to all of the last --open'ed set of linked arguments.",
223           Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
224   QUIT(Type.FLOW,
225           "After all files have been opened, appended and output, quit Jalview. In ‑‑headless mode this already happens.",
226           Opt.UNARY),
227
228   // secret options
229   TESTOUTPUT(Type.CONFIG,
230           "Allow specific stdout information.  For testing purposes only.",
231           Opt.UNARY, Opt.BOOTSTRAP, Opt.SECRET), // do not show this to the user
232   SETPROP(Type.CONFIG, "Set an individual Java System property.",
233           Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.SECRET), // not in use yet
234   NIL(Type.FLOW,
235           "This argument does nothing on its own, but can be used with linkedIds.",
236           Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.NOACTION, Opt.SECRET),
237
238   // private options (inserted during arg processing)
239   SETARGFILE(Type.FLOW,
240           "Sets the current value of the argfilename.  Inserted before argfilecontents.",
241           Opt.UNARY, Opt.LINKED, Opt.STRING, Opt.MULTI, Opt.PRIVATE,
242           Opt.NOACTION),
243   UNSETARGFILE(Type.FLOW,
244           "Unsets the current value of the argfilename.  Inserted after argfile contents.",
245           Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.PRIVATE, Opt.NOACTION),
246
247   // these last two have no purpose in the normal Jalview application but are
248   // used by jalview.bin.Launcher to set memory settings. They are not used by
249   // argparser but are here for Usage statement reasons.
250   JVMMEMPC(Type.CONFIG,
251           "Limit maximum heap size (memory) to PERCENT% of total physical memory detected. This defaults to 90 if total physical memory can be detected.\n"
252                   + "The equals sign (\"=\") separator must be used with no spaces.",
253           Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING, Opt.LAST),
254   JVMMEMMAX(Type.CONFIG,
255           "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"
256                   + "The equals sign (\"=\") separator must be used with no spaces.",
257           Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING, Opt.LAST),
258
259   ;
260
261   public static enum Opt
262   {
263     BOOLEAN, // This Arg can be specified as --arg or --noarg to give true or
264              // false. A default can be given with setOptions(bool, Opt....).
265              // Use ArgParser.isSet(Arg) to see if this arg was not specified.
266     STRING, // This Arg can accept a value either through --arg=value or --arg
267             // value.
268     UNARY, // This Arg is a boolean value, true if present, false if not. Like
269            // BOOLEAN but without the --noarg option.
270     MULTI, // This Arg can be specified multiple times. Multiple values are
271            // stored in the ArgValuesMap (along with their positional index) for
272            // each linkedId.
273     LINKED, // This Arg can be linked to others through a --arg[linkedId] or
274             // --arg[linkedId]=value. If no linkedId is specified then the
275             // current default linkedId will be used.
276     NODUPLICATEVALUES, // This Arg can only have one value (per linkedId). The
277                        // first value will be used and subsequent values ignored
278                        // with a warning.
279     BOOTSTRAP, // This Arg value(s) can be determined at an earlier stage than
280                // non-BOOTSTRAP Args. Substitutions do not happen in BOOTSTRAP
281                // Args and they cannot be linked or contain SubVals. See
282                // jalview.bin.argparser.BootstrapArgs.
283     GLOB, // This Arg can expand wildcard filename "globs" (e.g.
284           // path/*/filename*). If the Arg value is given as --arg filename*
285           // then the shell will have expanded the glob already, but if
286           // specified as --arg=filename* then the Java glob expansion method
287           // will be used (see FileUtils.getFilenamesFromGlob()). Note that this
288           // might be different from the shell expansion rules.
289     NOACTION, // This Arg does not perform a data task, usually used to control
290               // flow in ArgParser.parse(args).
291     ALLOWSUBSTITUTIONS, // This Arg allows substitutions in its linkedId,
292                         // SubVals and values.
293     PRIVATE, // This Arg is used internally, and cannot be specified by the
294              // user.
295     SECRET, // This Arg is used by development processes and although it can be
296             // set by the user, it is not displayed to the user.
297     ALLOWALL, // This Arg can use the '*' linkedId to apply to all known
298               // linkedIds
299     INCREMENTDEFAULTCOUNTER, // If an Arg has this option and the default
300                              // linkedId is used, the defaultLinkedIdCounter is
301                              // incremented *first*.
302     INPUT, // This Arg counts as an input for REQUIREINPUT
303     REQUIREINPUT, // This Arg can only be applied via --all if there is an
304                   // input (i.e. --open or --append)
305     OUTPUT, // This Arg provides an output filename. With Opt.ALLOWALL *.ext is
306             // shorthand for --all --output={basename}.ext
307     STORED, // This Arg resets and creates a new set of "opened" linkedIds
308     HELP, // This Arg is a --help type arg
309     PRIMARY, // This Arg is the main Arg for its type
310     HASTYPE, // This Arg can have an Arg.Type assigned to it (and no value)
311     FIRST, // Move this arg to the first in usage statement (within type)
312     LAST, // Move this arg to the end in usage statement (within type)
313   }
314
315   public static enum Type
316   {
317     // Type restricts argument to certain usage output
318     HELP, // --help
319     CONFIG("arguments used to configure "
320             + ChannelProperties.getProperty("app_name") + " from startup"),
321     OPENING("arguments used to open and format alignments"),
322     STRUCTURE("arguments used to add and format 3D structure data"),
323     PROCESS("arguments used to process an alignment once opened"),
324     OUTPUT("arguments used to save data from a processed alignment"),
325     IMAGE("arguments used to export an image of an alignment"),
326     FLOW("arguments that control processing of the other arguments"), //
327     ALL("all arguments"), // mostly just a place-holder for --help-all
328     NONE, // mostly a place-holder for --help
329     INVALID;
330
331     private String description;
332
333     private Type()
334     {
335       description = null;
336     }
337
338     private Type(String description)
339     {
340       this.description = description;
341     }
342
343     public String description()
344     {
345       return description;
346     }
347   }
348
349   private final String[] argNames;
350
351   private Opt[] argOptions;
352
353   private boolean defaultBoolValue;
354
355   private String description;
356
357   private Type type;
358
359   private Arg(Type type, String description, Opt... options)
360   {
361     this(type, null, description, false, options);
362   }
363
364   private Arg(Type type, String description, boolean defaultBoolean,
365           Opt... options)
366   {
367     this(type, null, description, defaultBoolean, options);
368   }
369
370   private Arg(Type type, String alternativeName, String description,
371           Opt... options)
372   {
373     this(type, alternativeName, description, false, options);
374   }
375
376   private Arg(Type type, String alternativeName, String description,
377           boolean defaultBoolean, Opt... options)
378   {
379     this.argNames = alternativeName != null
380             ? new String[]
381             { this.getName(), alternativeName }
382             : new String[]
383             { this.getName() };
384     this.type = type;
385     this.description = description;
386     this.defaultBoolValue = defaultBoolean;
387     this.setOptions(options);
388   }
389
390   public String argString()
391   {
392     return argString(false);
393   }
394
395   public String negateArgString()
396   {
397     return argString(true);
398   }
399
400   private String argString(boolean negate)
401   {
402     StringBuilder sb = new StringBuilder(ArgParser.DOUBLEDASH);
403     if (negate && hasOption(Opt.BOOLEAN))
404       sb.append(ArgParser.NEGATESTRING);
405     sb.append(getName());
406     return sb.toString();
407   }
408
409   public String toLongString()
410   {
411     StringBuilder sb = new StringBuilder();
412     sb.append(this.getClass().getName()).append('.').append(this.name());
413     sb.append('(');
414     if (getNames().length > 0)
415       sb.append('"');
416     sb.append(String.join("\", \"", getNames()));
417     if (getNames().length > 0)
418       sb.append('"');
419     sb.append(")\n");
420     sb.append("\nType: " + type.name());
421     sb.append("\nOpt: ");
422     // map List<Opt> to List<String> for the String.join
423     List<String> optList = Arrays.asList(argOptions).stream()
424             .map(opt -> opt.name()).collect(Collectors.toList());
425     sb.append(String.join(", ", optList));
426     sb.append("\n");
427     return sb.toString();
428   }
429
430   public String[] getNames()
431   {
432     return argNames;
433   }
434
435   public String getName()
436   {
437     return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
438   }
439
440   @Override
441   public final String toString()
442   {
443     return getName();
444   }
445
446   public boolean hasOption(Opt o)
447   {
448     if (argOptions == null)
449       return false;
450     for (Opt option : argOptions)
451     {
452       if (o == option)
453         return true;
454     }
455     return false;
456   }
457
458   public boolean hasAllOptions(Opt... opts)
459   {
460     for (Opt o : opts)
461     {
462       if (!this.hasOption(o))
463         return false;
464     }
465     return true;
466   }
467
468   protected void setOptions(Opt... options)
469   {
470     this.argOptions = options;
471   }
472
473   protected boolean getDefaultBoolValue()
474   {
475     return defaultBoolValue;
476   }
477
478   public Type getType()
479   {
480     return this.type;
481   }
482
483   protected String getDescription()
484   {
485     return description;
486   }
487
488   public static String booleanArgString(Arg a)
489   {
490     StringBuilder sb = new StringBuilder(a.argString());
491     if (a.hasOption(Opt.BOOLEAN))
492     {
493       sb.append('/');
494       sb.append(a.negateArgString());
495     }
496     return sb.toString();
497   }
498
499   public static final String usage()
500   {
501     return usage(null);
502   }
503
504   public static final void appendUsageGeneral(StringBuilder sb,
505           int maxArgLength)
506   {
507     for (Type t : EnumSet.allOf(Type.class))
508     {
509       if (t.description() != null)
510       {
511         StringBuilder argSb = new StringBuilder();
512         argSb.append(Arg.HELP.argString()).append(ArgParser.SINGLEDASH)
513                 .append(t.name().toLowerCase(Locale.ROOT));
514         appendArgAndDescription(sb, argSb.toString(),
515                 "Help for " + t.description(), null, maxArgLength);
516         sb.append(System.lineSeparator());
517       }
518     }
519   }
520
521   public static final String usage(List<Type> types)
522   {
523     StringBuilder sb = new StringBuilder();
524
525     sb.append("usage: jalview [" + Arg.HEADLESS.argString() + "] [["
526             + Arg.OPEN.argString() + "/" + Arg.APPEND.argString()
527             + "] file(s)] [args]");
528     sb.append(System.lineSeparator());
529     sb.append(System.lineSeparator());
530
531     if (types == null || types.contains(null))
532     {
533       // always show --help
534       appendArgAndDescription(sb, null, "Display this basic help", Arg.HELP,
535               DESCRIPTIONINDENT);
536       sb.append(System.lineSeparator());
537
538       appendUsageGeneral(sb, DESCRIPTIONINDENT);
539     }
540     else
541     {
542       List<Arg> args = argsSortedForDisplay(types);
543
544       /*
545        * just use a set maxArgLength of DESCRIPTIONINDENT
546        
547       int maxArgLength = 0;
548       for (Arg a : args)
549       {
550         if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET))
551           continue;
552       
553         String argS = argDisplayString(a);
554         if (argS.length() > maxArgLength)
555           maxArgLength = argS.length();
556       }
557       */
558       int maxArgLength = DESCRIPTIONINDENT;
559
560       // always show --help
561       appendArgAndDescription(sb, null, null, Arg.HELP, maxArgLength);
562       sb.append(System.lineSeparator());
563
564       if ((args.contains(Arg.HELP) && types.contains(Type.ALL)))
565       {
566         appendUsageGeneral(sb, maxArgLength);
567       }
568
569       Iterator<Arg> argsI = args.iterator();
570       Type typeSection = null;
571       while (argsI.hasNext())
572       {
573         Arg a = argsI.next();
574
575         if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET)
576                 || a == Arg.HELP)
577         {
578           continue;
579         }
580
581         if (a.getType() != typeSection)
582         {
583           typeSection = a.getType();
584           String typeDescription = a.getType().description();
585           if (typeDescription != null && typeDescription.length() > 0)
586           {
587             // typeDescription = typeDescription.substring(0,
588             // 1).toUpperCase(Locale.ROOT) + typeDescription.substring(1);
589             typeDescription = typeDescription.toUpperCase(Locale.ROOT);
590             sb.append(typeDescription);
591             sb.append(System.lineSeparator());
592             sb.append(System.lineSeparator());
593           }
594         }
595
596         appendArgUsage(sb, a, maxArgLength);
597
598         if (argsI.hasNext())
599         {
600           sb.append(System.lineSeparator());
601         }
602       }
603     }
604     return sb.toString();
605   }
606
607   private static void appendArgUsage(StringBuilder sb, Arg a,
608           int maxArgLength)
609   {
610     boolean first = appendArgAndDescription(sb, null, null, a,
611             maxArgLength);
612     List<String> options = new ArrayList<>();
613
614     if (a.hasOption(Opt.BOOLEAN))
615     {
616       options.add("default " + (a.getDefaultBoolValue() ? a.argString()
617               : a.negateArgString()));
618     }
619
620     if (a.hasOption(Opt.MULTI))
621     {
622       options.add("multiple");
623     }
624
625     if (a.hasOption(Opt.LINKED))
626     {
627       options.add("can be linked");
628     }
629
630     if (a.hasOption(Opt.GLOB))
631     {
632       options.add("allows file globs");
633     }
634
635     if (a.hasOption(Opt.ALLOWSUBSTITUTIONS))
636     {
637       options.add("allows substitutions");
638     }
639
640     if (a.hasOption(Opt.ALLOWALL))
641     {
642       options.add("can be applied to all linked arguments");
643     }
644
645     if (a.hasOption(Opt.PRIVATE))
646     {
647       options.add("for internal use only");
648     }
649
650     if (a.hasOption(Opt.SECRET))
651     {
652       options.add("for development use only");
653     }
654
655     if (options.size() > 0)
656     {
657       if (first)
658       {
659         sb.append(ARGDESCRIPTIONSEPARATOR);
660       }
661       else
662       {
663         sb.append(String.format("%-"
664                 + (maxArgLength + ARGDESCRIPTIONSEPARATOR.length()) + "s",
665                 ""));
666       }
667       sb.append("(");
668       sb.append(String.join("; ", options));
669       sb.append(')');
670       sb.append(System.lineSeparator());
671     }
672   }
673
674   public static String argDisplayString(Arg a)
675   {
676     StringBuilder argSb = new StringBuilder();
677     argSb.append(
678             a.hasOption(Opt.BOOLEAN) ? booleanArgString(a) : a.argString());
679     if (a.hasOption(Opt.STRING))
680       argSb.append("=value");
681     return argSb.toString();
682   }
683
684   public static boolean appendArgAndDescription(StringBuilder sb,
685           String aString, String description, Arg a, int maxArgLength)
686   {
687     return appendArgAndDescription(sb, aString, description, a,
688             maxArgLength, Platform.consoleWidth());
689   }
690
691   public static boolean appendArgAndDescription(StringBuilder sb,
692           String aString, String description, Arg a, int maxArgLength,
693           int maxLength)
694   {
695     if (aString == null && a != null)
696     {
697       aString = argDisplayString(a);
698     }
699     if (description == null && a != null)
700     {
701       description = a.getDescription();
702     }
703     sb.append(String.format("%-" + maxArgLength + "s", aString));
704     if (aString.length() > maxArgLength)
705     {
706       sb.append(System.lineSeparator());
707       sb.append(String.format("%-" + maxArgLength + "s", ""));
708     }
709
710     int descLength = maxLength - maxArgLength
711             - ARGDESCRIPTIONSEPARATOR.length();
712     // reformat the descriptions lines to the right width
713     Iterator<String> descLines = null;
714     if (description != null)
715     {
716       descLines = Arrays.stream(description.split("\\n")).iterator();
717     }
718     List<String> splitDescLinesList = new ArrayList<>();
719     while (descLines != null && descLines.hasNext())
720     {
721       String line = descLines.next();
722       while (line.length() > descLength)
723       {
724         int splitIndex = line.lastIndexOf(" ", descLength);
725         splitDescLinesList.add(line.substring(0, splitIndex));
726         line = line.substring(splitIndex + 1);
727       }
728       splitDescLinesList.add(line);
729     }
730
731     Iterator<String> splitDescLines = splitDescLinesList.iterator();
732     boolean first = true;
733     if (splitDescLines != null)
734     {
735       while (splitDescLines.hasNext())
736       {
737         if (first)
738         {
739           sb.append(ARGDESCRIPTIONSEPARATOR);
740         }
741         else
742         {
743           sb.append(String.format("%-"
744                   + (maxArgLength + ARGDESCRIPTIONSEPARATOR.length()) + "s",
745                   ""));
746         }
747         sb.append(splitDescLines.next());
748         sb.append(System.lineSeparator());
749         first = false;
750       }
751     }
752     return first;
753   }
754
755   protected static Iterator<Arg> getAllOfType(Type type)
756   {
757     return getAllOfType(type, new Opt[] {});
758   }
759
760   protected static Iterator<Arg> getAllOfType(Type type, Opt... options)
761   {
762     Opt[] opts = options == null ? new Opt[] {} : options;
763     return EnumSet.allOf(Arg.class).stream().filter(a -> {
764       if (a.getType() != type)
765         return false;
766       for (Opt o : opts)
767       {
768         if (!a.hasOption(o))
769           return false;
770       }
771       return true;
772     }).iterator();
773   }
774
775   private static List<Arg> argsSortedForDisplay(List<Type> types)
776   {
777     List<Arg> argsToSort;
778     // if no types provided, do all
779     if (types == null || types.size() == 0 || types.contains(Type.ALL))
780     {
781       argsToSort = Arrays
782               .asList(EnumSet.allOf(Arg.class).toArray(new Arg[] {}));
783     }
784     else
785     {
786       argsToSort = new ArrayList<>();
787       for (Type type : types)
788       {
789         if (type == null)
790           continue;
791         Arg.getAllOfType(type).forEachRemaining(a -> argsToSort.add(a));
792       }
793     }
794
795     Collections.sort(argsToSort, new ArgDisplayComparator());
796     return argsToSort;
797   }
798
799   private static final String ARGDESCRIPTIONSEPARATOR = " - ";
800
801   private static final int DESCRIPTIONINDENT = 20;
802
803 }
804
805 class ArgDisplayComparator implements Comparator<Arg>
806 {
807   private int compareArgOpts(Arg a, Arg b, Opt o)
808   {
809     int i = a.hasOption(o) ? (b.hasOption(o) ? 0 : -1)
810             : (b.hasOption(o) ? 1 : 0);
811     return i;
812   }
813
814   private int compareForDisplay(Arg a, Arg b)
815   {
816     if (b == null)
817       return -1;
818     // first compare types (in enum order)
819     int i = a.getType().compareTo(b.getType());
820     if (i != 0)
821       return i;
822     // do Opt.LAST next (oddly). Reversed args important!
823     i = compareArgOpts(b, a, Opt.LAST);
824     if (i != 0)
825       return i;
826     // priority order
827     Opt[] optOrder = { Opt.HELP, Opt.FIRST, Opt.PRIMARY, Opt.STRING,
828         Opt.BOOLEAN };
829     for (Opt o : optOrder)
830     {
831       i = compareArgOpts(a, b, o);
832       if (i != 0)
833         return i;
834     }
835     // finally order of appearance in enum declarations
836     return a.compareTo(b);
837   }
838
839   @Override
840   public int compare(Arg a, Arg b)
841   {
842     return compareForDisplay(a, b);
843   }
844 }