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