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