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