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