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