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