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