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