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