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