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