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