JAL-629 Fix --tempfac. Hide non-working --notempfac. Add --scale, --width, --height...
[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.EnumSet;
6 import java.util.Iterator;
7 import java.util.List;
8 import java.util.Locale;
9 import java.util.stream.Collectors;
10
11 import jalview.bin.Cache;
12 import jalview.util.ChannelProperties;
13
14 public enum Arg
15 {
16
17   // Initialising arguments (BOOTSTRAP)
18   HELP("Display this help statement", Opt.UNARY, Opt.BOOTSTRAP),
19   VERSION("Display the version of "
20           + ChannelProperties.getProperty("app_name"), Opt.UNARY,
21           Opt.BOOTSTRAP),
22   HEADLESS(
23           "Run Jalview in headless mode. No GUI interface will be created and Jalview will quit after all arguments have been processed.",
24           Opt.UNARY, Opt.BOOTSTRAP),
25   JABAWS("Set a different URL to connect to a JABAWS server.", Opt.STRING,
26           Opt.BOOTSTRAP),
27   NEWS("Show (or don't show) the news feed.", true, Opt.BOOLEAN,
28           Opt.BOOTSTRAP),
29   SPLASH("Show (or don't show) the About Jalview splash screen.", true,
30           Opt.BOOLEAN, Opt.BOOTSTRAP),
31   QUESTIONNAIRE(
32           "Show (or don't show) the questionnaire if one is available.",
33           true, Opt.BOOLEAN, Opt.BOOTSTRAP),
34   USAGESTATS("Send (or don't send) initial launch usage stats.", true,
35           Opt.BOOLEAN, Opt.BOOTSTRAP),
36   WEBSERVICEDISCOVERY(
37           "Attempt (or don't attempt) to connect to JABAWS web services.",
38           true, Opt.BOOLEAN, Opt.BOOTSTRAP),
39   PROPS("Use a file as the preferences file instead of the usual ~/"
40           + ChannelProperties.getProperty("preferences.filename")
41           + " file.", Opt.STRING, Opt.BOOTSTRAP),
42   DEBUG("Start Jalview in debug log level.", Opt.BOOLEAN, Opt.BOOTSTRAP),
43   TRACE("Start Jalview in trace log level.", Opt.BOOLEAN, Opt.BOOTSTRAP,
44           Opt.SECRET),
45   QUIET("Stop all output to STDOUT (after the Java Virtual Machine has started). Use ‑‑quiet a second time to stop all output to STDERR.",
46           Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP),
47   INITSUBSTITUTIONS(
48           "Set ‑‑substitutions to be initially enabled (or initially disabled).",
49           true, Opt.BOOLEAN, Opt.BOOTSTRAP, Opt.NOACTION),
50
51   // Opening an alignment
52   OPEN("Opens one or more alignment files or URLs in new alignment windows.",
53           Opt.STRING, Opt.LINKED, Opt.INCREMENTDEFAULTCOUNTER, Opt.MULTI,
54           Opt.GLOB, Opt.ALLOWSUBSTITUTIONS, Opt.INPUT),
55   APPEND("Appends one or more alignment files or URLs to the open alignment window (or opens a new alignment if none already open).",
56           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB,
57           Opt.ALLOWSUBSTITUTIONS, Opt.INPUT),
58   TITLE("Specifies the title for the open alignment window as string.",
59           Opt.STRING, Opt.LINKED),
60   COLOUR("Applies the colour scheme to the open alignment window. Valid values are:\n"
61           + "clustal,\n" + "blosum62,\n" + "pc-identity,\n" + "zappo,\n"
62           + "taylor,\n" + "gecos-flower,\n" + "gecos-blossom,\n"
63           + "gecos-sunset,\n" + "gecos-ocean,\n" + "hydrophobic,\n"
64           + "helix-propensity,\n" + "strand-propensity,\n"
65           + "turn-propensity,\n" + "buried-index,\n" + "nucleotide,\n"
66           + "nucleotide-ambiguity,\n" + "purine-pyrimidine,\n"
67           + "rna-helices,\n" + "t-coffee-scores,\n" + "sequence-id.",
68           Opt.STRING, Opt.LINKED),
69   FEATURES("Add a feature file or URL to the open alignment.", Opt.STRING,
70           Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
71   TREE("Add a tree file or URL to the open alignment.", Opt.STRING,
72           Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
73   SORTBYTREE(
74           "Enforces sorting (or not sorting) the open alignment in the order of an attached phylogenetic tree.",
75           true, Opt.LINKED, Opt.BOOLEAN),
76   ANNOTATIONS("Add an annotations file or URL to the open alignment.",
77           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
78   SHOWANNOTATIONS(
79           "Enforces showing (or not showing) alignment annotations.",
80           Opt.BOOLEAN, Opt.LINKED),
81   WRAP("Enforces wrapped (or not wrapped) alignment formatting.",
82           Opt.BOOLEAN, Opt.LINKED),
83   NOSTRUCTURE(
84           "Do not open or process any 3D structure in the ‑‑open or ‑‑append files.",
85           Opt.UNARY, Opt.LINKED),
86
87   // Adding a 3D structure
88   STRUCTURE(
89           "Load a structure file or URL associated with a sequence in the open alignment.\n"
90                   + "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.",
91           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
92   SEQID("Specify the sequence name for the preceding --structure to be associated with.",
93           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
94   PAEMATRIX("Add a PAE json matrix file to the preceding --structure.",
95           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
96   TEMPFAC("Set the type of temperature factor. Possible values are:\n"
97           + "default,\n" + "plddt.", Opt.STRING, Opt.LINKED),
98   STRUCTUREVIEWER(
99           "Set the structure viewer to use to open the 3d structure file specified in previous --structure to name. Possible values of name are:\n"
100                   + "none,\n" + "jmol,\n" + "chimera,\n" + "chimerax,\n"
101                   + "pymol.",
102           Opt.STRING, Opt.LINKED, Opt.MULTI),
103   NOTEMPFAC(
104           "Do not show the temperature factor annotation for the preceding --structure.",
105           Opt.UNARY, Opt.LINKED, Opt.SECRET), // keep this secret until it
106                                               // works!
107   SHOWSSANNOTATIONS(null, Opt.BOOLEAN, Opt.LINKED),
108
109   // Outputting files
110   IMAGE("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"
111           + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
112           Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL,
113           Opt.REQUIREINPUT),
114   TYPE("Set the image format for the preceding --image to name. Valid values for name are: svg,\n"
115           + "png,\n" + "eps,\n" + "html,\n" + "biojs.", Opt.STRING,
116           Opt.LINKED, Opt.ALLOWALL),
117   TEXTRENDERER(
118           "Sets whether text in a vector image format (SVG, HTML, EPS) should be rendered as text or vector line-art. Possible values for name are:\n"
119                   + "text,\n" + "lineart.",
120           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
121   SCALE("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).",
122           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
123   WIDTH("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).",
124           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
125   HEIGHT("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).",
126           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
127   OUTPUT("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"
128           + "fasta (fa, fasta, mfa, fastq),\n" + "pfam (pfam),\n"
129           + "stockholm (sto, stk),\n" + "pir (pir),\n" + "blc (blc),\n"
130           + "amsa (amsa),\n" + "json (json),\n" + "pileup (pileup),\n"
131           + "msf (msf),\n" + "clustal (aln),\n" + "phylip (phy),\n"
132           + "jalview (jvp, jar).", Opt.STRING, Opt.LINKED,
133           Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL, Opt.REQUIREINPUT),
134   FORMAT("Sets the format for the preceding --output file. Valid formats are:\n"
135           + "fasta,\n" + "pfam,\n" + "stockholm,\n" + "pir,\n" + "blc,\n"
136           + "amsa,\n" + "json,\n" + "pileup,\n" + "msf,\n" + "clustal,\n"
137           + "phylip,\n" + "jalview.", Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
138   GROOVY("Process a groovy script in the file for the open alignment.",
139           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
140   BACKUPS("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.",
141           true, Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
142   OVERWRITE(
143           "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.",
144           Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
145   CLOSE("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.",
146           Opt.UNARY, Opt.LINKED, Opt.ALLOWALL),
147
148   // controlling flow of arguments
149   NEW("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.",
150           Opt.UNARY, Opt.MULTI, Opt.NOACTION, Opt.INCREMENTDEFAULTCOUNTER),
151   SUBSTITUTIONS(
152           "The following argument values allow (or don't allow) subsituting filename parts. This is initially true. Valid substitutions are {basename} - the filename-without-extension of the currently --opened file (or first --appended file),\n"
153                   + "{dirname}, - the directory (folder) name of the currently --opened file (or first --appended file),\n"
154                   + "{argfilebasename} - the filename-without-extension of the current --argfile,\n"
155                   + "{argfiledirname} - the directory (folder) name of the current --argfile,\n"
156                   + "{n} - the value of the index counter (starting at 0).\n"
157                   + "{++n} - increase and substitute the value of the index counter,\n"
158                   + "{} - the value of the current alignment window default index.",
159           true, Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
160   ARGFILE("Open one or more files filename and read, line-by-line, as arguments to Jalview.\n"
161           + "Values in an argfile should be given with an equals sign (\"=\") separator with no spaces.\n"
162           + "Note that if you use one or more --argfile arguments then all other non-initialising arguments will be ignored.",
163           Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB,
164           Opt.ALLOWSUBSTITUTIONS),
165   NPP("Increase the index counter used in argument value substitutions.",
166           Opt.UNARY, Opt.MULTI, Opt.NOACTION),
167   ALL("Apply the following output arguments to all sets of linked arguments.",
168           Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
169   QUIT("After all files have been opened, appended and output, quit Jalview. In ‑‑headless mode this already happens.",
170           Opt.UNARY),
171
172   // secret options
173   TESTOUTPUT(
174           "Allow specific stdout information.  For testing purposes only.",
175           Opt.UNARY, Opt.BOOTSTRAP, Opt.SECRET), // do not show this to the user
176   SETPROP("Set an individual Java System property.", Opt.STRING, Opt.MULTI,
177           Opt.BOOTSTRAP, Opt.SECRET), // not in use yet
178   NIL("This argument does nothing on its own, but can be used with linkedIds.",
179           Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.NOACTION, Opt.SECRET),
180
181   // private options (inserted during arg processing)
182   SETARGFILE(
183           "Sets the current value of the argfilename.  Inserted before argfilecontents.",
184           Opt.UNARY, Opt.LINKED, Opt.STRING, Opt.MULTI, Opt.PRIVATE,
185           Opt.NOACTION),
186   UNSETARGFILE(
187           "Unsets the current value of the argfilename.  Inserted after argfile contents.",
188           Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.PRIVATE, Opt.NOACTION),
189
190   // these last two have no purpose in the normal Jalview application but are
191   // used by jalview.bin.Launcher to set memory settings. They are not used by
192   // argparser but are here for Usage statement reasons.
193   JVMMEMPC(
194           "Only available with standalone executable jar or jalview.bin.Launcher.\n"
195                   + "Limit maximum heap size (memory) to PERCENT% of total physical memory detected. This defaults to 90 if total physical memory can be detected.\n"
196                   + "The equals sign (\"=\") separator must be used with no spaces.",
197           Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING),
198   JVMMEMMAX(
199           "Only available with standalone executable jar or jalview.bin.Launcher.\n"
200                   + "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"
201                   + "The equals sign (\"=\") separator must be used with no spaces.",
202           Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING),
203
204   ;
205
206   public static enum Opt
207   {
208     BOOLEAN, // This Arg can be specified as --arg or --noarg to give true or
209              // false. A default can be given with setOptions(bool, Opt....).
210              // Use ArgParser.isSet(Arg) to see if this arg was not specified.
211     STRING, // This Arg can accept a value either through --arg=value or --arg
212             // value.
213     UNARY, // This Arg is a boolean value, true if present, false if not. Like
214            // BOOLEAN but without the --noarg option.
215     MULTI, // This Arg can be specified multiple times. Multiple values are
216            // stored in the ArgValuesMap (along with their positional index) for
217            // each linkedId.
218     LINKED, // This Arg can be linked to others through a --arg[linkedId] or
219             // --arg[linkedId]=value. If no linkedId is specified then the
220             // current default linkedId will be used.
221     NODUPLICATEVALUES, // This Arg can only have one value (per linkedId). The
222                        // first value will be used and subsequent values ignored
223                        // with a warning.
224     BOOTSTRAP, // This Arg value(s) can be determined at an earlier stage than
225                // non-BOOTSTRAP Args. Substitutions do not happen in BOOTSTRAP
226                // Args and they cannot be linked or contain SubVals. See
227                // jalview.bin.argparser.BootstrapArgs.
228     GLOB, // This Arg can expand wildcard filename "globs" (e.g.
229           // path/*/filename*). If the Arg value is given as --arg filename*
230           // then the shell will have expanded the glob already, but if
231           // specified as --arg=filename* then the Java glob expansion method
232           // will be used (see FileUtils.getFilenamesFromGlob()). Note that this
233           // might be different from the shell expansion rules.
234     NOACTION, // This Arg does not perform a data task, usually used to control
235               // flow in ArgParser.parse(args).
236     ALLOWSUBSTITUTIONS, // This Arg allows substitutions in its linkedId,
237                         // SubVals and values.
238     PRIVATE, // This Arg is used internally, and cannot be specified by the
239              // user.
240     SECRET, // This Arg is used by development processes and although it can be
241             // set by the user, it is not displayed to the user.
242     ALLOWALL, // This Arg can use the '*' linkedId to apply to all known
243               // linkedIds
244     INCREMENTDEFAULTCOUNTER, // If an Arg has this option and the default
245                              // linkedId is used, the defaultLinkedIdCounter is
246                              // incremented *first*.
247     INPUT, // This Arg counts as an input for REQUIREINPUT
248     REQUIREINPUT, // This Arg can only be applied via --all if there is an
249                   // input (i.e. --open or --append)
250   }
251
252   private final String[] argNames;
253
254   private Opt[] argOptions;
255
256   private boolean defaultBoolValue = false;
257
258   private String description = null;
259
260   private Arg(String description, Opt... options)
261   {
262     this(null, description, false, options);
263   }
264
265   private Arg(String description, boolean defaultBoolean, Opt... options)
266   {
267     this(null, description, defaultBoolean, options);
268   }
269
270   private Arg(String alternativeName, String description, Opt... options)
271   {
272     this(alternativeName, description, false, options);
273   }
274
275   private Arg(String alternativeName, String description,
276           boolean defaultBoolean, Opt... options)
277   {
278     this.argNames = alternativeName != null
279             ? new String[]
280             { this.getName(), alternativeName }
281             : new String[]
282             { this.getName() };
283     this.description = description;
284     this.defaultBoolValue = defaultBoolean;
285     this.setOptions(options);
286   }
287
288   public String argString()
289   {
290     return argString(false);
291   }
292
293   public String negateArgString()
294   {
295     return argString(true);
296   }
297
298   private String argString(boolean negate)
299   {
300     StringBuilder sb = new StringBuilder(ArgParser.DOUBLEDASH);
301     if (negate && hasOption(Opt.BOOLEAN))
302       sb.append(ArgParser.NEGATESTRING);
303     sb.append(getName());
304     return sb.toString();
305   }
306
307   public String toLongString()
308   {
309     StringBuilder sb = new StringBuilder();
310     sb.append(this.getClass().getName()).append('.').append(this.name());
311     sb.append('(');
312     if (getNames().length > 0)
313       sb.append('"');
314     sb.append(String.join("\", \"", getNames()));
315     if (getNames().length > 0)
316       sb.append('"');
317     sb.append(")\n");
318     sb.append("\nOpt: ");
319     // map List<Opt> to List<String> for the String.join
320     List<String> optList = Arrays.asList(argOptions).stream()
321             .map(opt -> opt.name()).collect(Collectors.toList());
322     sb.append(String.join(", ", optList));
323     sb.append("\n");
324     return sb.toString();
325   }
326
327   public String[] getNames()
328   {
329     return argNames;
330   }
331
332   public String getName()
333   {
334     return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
335   }
336
337   @Override
338   public final String toString()
339   {
340     return getName();
341   }
342
343   public boolean hasOption(Opt o)
344   {
345     if (argOptions == null)
346       return false;
347     for (Opt option : argOptions)
348     {
349       if (o == option)
350         return true;
351     }
352     return false;
353   }
354
355   protected void setOptions(Opt... options)
356   {
357     this.argOptions = options;
358   }
359
360   protected boolean getDefaultBoolValue()
361   {
362     return defaultBoolValue;
363   }
364
365   protected String getDescription()
366   {
367     return description;
368   }
369
370   public static String booleanArgString(Arg a)
371   {
372     StringBuilder sb = new StringBuilder(a.argString());
373     if (a.hasOption(Opt.BOOLEAN))
374     {
375       sb.append('/');
376       sb.append(a.negateArgString());
377     }
378     return sb.toString();
379   }
380
381   public static final String usage()
382   {
383     StringBuilder sb = new StringBuilder();
384
385     sb.append(ChannelProperties.getProperty("app_name"));
386     String version = Cache.getDefault("VERSION", null);
387     if (version != null)
388     {
389       sb.append(" version ");
390       sb.append(Cache.getDefault("VERSION", "unknown"));
391     }
392     sb.append(System.lineSeparator());
393     sb.append("Usage: jalview [files...] [args]");
394     sb.append(System.lineSeparator());
395     sb.append(System.lineSeparator());
396
397     int maxArgLength = 0;
398     for (Arg a : EnumSet.allOf(Arg.class))
399     {
400       if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET))
401         continue;
402
403       StringBuilder argSb = new StringBuilder();
404       argSb.append(a.hasOption(Opt.BOOLEAN) ? booleanArgString(a)
405               : a.argString());
406       if (a.hasOption(Opt.STRING))
407         argSb.append("=value");
408       if (argSb.length() > maxArgLength)
409         maxArgLength = argSb.length();
410     }
411
412     // might want to sort these
413     for (Arg a : EnumSet.allOf(Arg.class))
414     {
415       if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET))
416         continue;
417       StringBuilder argSb = new StringBuilder();
418       argSb.append(a.hasOption(Opt.BOOLEAN) ? booleanArgString(a)
419               : a.argString());
420       if (a.hasOption(Opt.STRING))
421         argSb.append("=value");
422       Iterator<String> descLines = null;
423       if (a.getDescription() != null)
424       {
425         descLines = Arrays.stream(a.getDescription().split("\\n"))
426                 .iterator();
427       }
428       sb.append(String.format("%-" + maxArgLength + "s", argSb.toString()));
429       boolean first = true;
430       if (descLines != null)
431       {
432         while (descLines.hasNext())
433         {
434           if (first)
435             sb.append(" - ");
436           else
437             sb.append(" ".repeat(maxArgLength + 3));
438           sb.append(descLines.next());
439           sb.append(System.lineSeparator());
440           first = false;
441         }
442       }
443
444       List<String> options = new ArrayList<>();
445
446       if (a.hasOption(Opt.BOOLEAN))
447       {
448         options.add("default " + (a.getDefaultBoolValue() ? a.argString()
449                 : a.negateArgString()));
450       }
451
452       if (a.hasOption(Opt.MULTI))
453       {
454         options.add("multiple");
455       }
456
457       if (a.hasOption(Opt.LINKED))
458       {
459         options.add("can be linked");
460       }
461
462       if (a.hasOption(Opt.GLOB))
463       {
464         options.add("allows file globs");
465       }
466
467       if (a.hasOption(Opt.ALLOWSUBSTITUTIONS))
468       {
469         options.add("allows substitutions");
470       }
471
472       if (a.hasOption(Opt.ALLOWALL))
473       {
474         options.add("can be applied to all linked arguments");
475       }
476
477       if (a.hasOption(Opt.PRIVATE))
478       {
479         options.add("for internal use only");
480       }
481
482       if (a.hasOption(Opt.SECRET))
483       {
484         options.add("for development use only");
485       }
486
487       if (options.size() > 0)
488       {
489         if (first)
490           sb.append(" - ");
491         else
492           sb.append(" ".repeat(maxArgLength + 3));
493         sb.append("(");
494         sb.append(String.join("; ", options));
495         sb.append(')');
496         sb.append(System.lineSeparator());
497       }
498       sb.append(System.lineSeparator());
499     }
500     return sb.toString();
501   }
502 }