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