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