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