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