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