JAL-629 Better formatting of Arg.Opt options in usage statement
authorBen Soares <b.soares@dundee.ac.uk>
Tue, 30 May 2023 09:53:40 +0000 (10:53 +0100)
committerBen Soares <b.soares@dundee.ac.uk>
Tue, 30 May 2023 09:53:40 +0000 (10:53 +0100)
src/jalview/bin/argparser/Arg.java

index c502eec..6c97ebc 100644 (file)
@@ -287,57 +287,138 @@ public enum Arg
 
   public static enum Opt
   {
-    BOOLEAN, // This Arg can be specified as --arg or --noarg to give true or
-             // false. A default can be given with setOptions(bool, Opt....).
-             // Use ArgParser.isSet(Arg) to see if this arg was not specified.
-    STRING, // This Arg can accept a value either through --arg=value or --arg
-            // value.
-    UNARY, // This Arg is a boolean value, true if present, false if not. Like
-           // BOOLEAN but without the --noarg option.
-    MULTI, // This Arg can be specified multiple times. Multiple values are
-           // stored in the ArgValuesMap (along with their positional index) for
-           // each linkedId.
-    LINKED, // This Arg can be linked to others through a --arg[linkedId] or
-            // --arg[linkedId]=value. If no linkedId is specified then the
-            // current default linkedId will be used.
-    NODUPLICATEVALUES, // This Arg can only have one value (per linkedId). The
-                       // first value will be used and subsequent values ignored
-                       // with a warning.
-    BOOTSTRAP, // This Arg value(s) can be determined at an earlier stage than
-               // non-BOOTSTRAP Args. Substitutions do not happen in BOOTSTRAP
-               // Args and they cannot be linked or contain SubVals. See
-               // jalview.bin.argparser.BootstrapArgs.
-    GLOB, // This Arg can expand wildcard filename "globs" (e.g.
-          // path/*/filename*). If the Arg value is given as --arg filename*
-          // then the shell will have expanded the glob already, but if
-          // specified as --arg=filename* then the Java glob expansion method
-          // will be used (see FileUtils.getFilenamesFromGlob()). Note that this
-          // might be different from the shell expansion rules.
-    NOACTION, // This Arg does not perform a data task, usually used to control
-              // flow in ArgParser.parse(args).
-    ALLOWSUBSTITUTIONS, // This Arg allows substitutions in its linkedId,
-                        // SubVals and values.
-    PRIVATE, // This Arg is used internally, and cannot be specified by the
-             // user.
-    SECRET, // This Arg is used by development processes and although it can be
-            // set by the user, it is not displayed to the user.
-    ALLOWALL, // This Arg can use the '*' linkedId to apply to all known
-              // linkedIds
-    INCREMENTDEFAULTCOUNTER, // If an Arg has this option and the default
-                             // linkedId is used, the defaultLinkedIdCounter is
-                             // incremented *first*.
-    INPUT, // This Arg counts as an input for REQUIREINPUT
-    REQUIREINPUT, // This Arg can only be applied via --all if there is an
-                  // input (i.e. --open or --append)
-    OUTPUTFILE, // This Arg provides an output filename. With Opt.ALLOWALL *.ext
-                // is
-    // shorthand for --all --output={basename}.ext
-    STORED, // This Arg resets and creates a new set of "opened" linkedIds
-    HELP, // This Arg is a --help type arg
-    PRIMARY, // This Arg is the main Arg for its type
-    HASTYPE, // This Arg can have an Arg.Type assigned to it (and no value)
-    FIRST, // Move this arg to the first in usage statement (within type)
-    LAST, // Move this arg to the end in usage statement (within type)
+    /*
+     * A BOOLEAN Arg can be specified as --arg or --noarg to give true or false.
+     * A default can be given with setOptions(bool, Opt....).
+     * Use ArgParser.isSet(Arg) to see if this arg was not specified.
+     */
+    BOOLEAN("can be negated with " + ArgParser.DOUBLEDASH
+            + ArgParser.NEGATESTRING + "..."),
+
+    /*
+     * A STRING Arg will take a value either through --arg=value or --arg value.
+     */
+    STRING("expects a value"),
+    /*
+     * A UNARY Arg is a boolean value, true if present, false if not.
+     * Like BOOLEAN but without the --noarg option.
+     */
+    UNARY(null),
+    /*
+     * A MULTI Arg can be specified multiple times.
+     * Multiple values are stored in the ArgValuesMap (along with their positional index) for each linkedId.
+     */
+    MULTI("can be specified multiple times"),
+    /*
+     * A Linked Arg can be linked to others through a --arg[linkedId] or --arg[linkedId]=value.
+     * If no linkedId is specified then the current default linkedId will be used.
+     */
+    LINKED("is linked to an alignment"),
+    /*
+     * A NODUPLICATES Arg can only have one value (per linkedId).
+     * The first value will be used and subsequent values ignored with a warning.
+     */
+    NODUPLICATEVALUES("cannot have the same value more than once"),
+    /*
+     * A BOOTSTRAP Arg value(s) can be determined at an earlier stage than non-BOOTSTRAP Args.
+     * Substitutions do not happen in BOOTSTRAP Args and they cannot be linked or contain SubVals.
+     * See jalview.bin.argparser.BootstrapArgs.
+     */
+    BOOTSTRAP("a configuration argument"),
+    /*
+     * A GLOB Arg can expand wildcard filename "globs" (e.g. path/* /filename*).
+     * If the Arg value is given as --arg filename* then the shell will have expanded the glob already,
+     * but if specified as --arg=filename* then the Java glob expansion method will be used
+     * (see FileUtils.getFilenamesFromGlob()).
+     * Note that this might be different from the shell expansion rules.
+     */
+    GLOB("can take multiple filenames with wildcards"),
+    /*
+     * A NOACTION Arg does not perform a data task,
+     * usually used to control flow in ArgParser.parse(args).
+     */
+    NOACTION(null),
+    /*
+     * An ALLOWSUBSTITUTIONS Arg allows substitutions in its linkedId,
+     * SubVals and values.
+     */
+    ALLOWSUBSTITUTIONS("values can use substitutions"),
+    /*
+     * A PRIVATE Arg is used internally, and cannot be specified by the user.
+     */
+    PRIVATE(null),
+    /*
+     * A SECRET Arg is used by development processes and although it can be set by the user,
+     * it is not displayed to the user.
+     */
+    SECRET(null),
+    /*
+     * An ALLOWALL Arg can use the '*' linkedId to apply to all known linkedIds
+     */
+    ALLOWALL("can be used with " + ArgParser.DOUBLEDASH + "all"),
+    /*
+     * If an Arg has the INCREMENTDEFAULTCOUNTER option and the default linkedId is used,
+     * the defaultLinkedIdCounter is incremented *first*.
+     */
+    INCREMENTDEFAULTCOUNTER("starts a new default alignment"),
+    /*
+     * An INPUT Arg counts as an input for REQUIREINPUT
+     */
+    INPUT(null),
+    /*
+     * A REQUIREINPUT Arg can only be applied via --all if there is an input
+     * (i.e. --open or --append)
+     */
+    REQUIREINPUT(null),
+    /*
+     * An OUTPUTFILE Arg provides an output filename. With Opt.ALLOWALL *.ext is shorthand for
+     * --all --output={basename}.ext
+     */
+    OUTPUTFILE("value is an output file"),
+    /*
+     * A STORED Arg resets and creates a new set of "opened" linkedIds
+     */
+    STORED(null),
+    /*
+     * A HELP Arg is a --help type arg
+     */
+    HELP("provides a help statement"),
+    /*
+     * A PRIMARY Arg is the main Arg for its type
+     */
+    PRIMARY("is a primary argument for its type"),
+    /*
+     * A HASTYPE Arg can have an Arg.Type assigned to its ArgValue
+     */
+    HASTYPE(null),
+    /*
+     * A FIRST arg gets moved to appear first in the usage statement (within type)
+     */
+    FIRST(null),
+    /*
+     * A LAST arg gets moved to appear last in the usage statement (within type)
+     */
+    LAST(null),
+    //
+    ;
+
+    private String description;
+
+    private Opt()
+    {
+      description = null;
+    }
+
+    private Opt(String description)
+    {
+      this.description = description;
+    }
+
+    public String description()
+    {
+      return description;
+    }
+
   }
 
   public static enum Type
@@ -627,7 +708,7 @@ public enum Arg
           }
         }
 
-        appendArgUsage(sb, a, maxArgLength);
+        appendArgUsage(sb, a, maxArgLength, Platform.consoleWidth());
 
         if (argsI.hasNext())
         {
@@ -639,69 +720,71 @@ public enum Arg
   }
 
   private static void appendArgUsage(StringBuilder sb, Arg a,
-          int maxArgLength)
+          int maxArgLength, int maxWidth)
   {
     boolean first = appendArgAndDescription(sb, null, null, a,
             maxArgLength);
     List<String> options = new ArrayList<>();
 
-    if (a.hasOption(Opt.BOOLEAN))
-    {
-      options.add("default " + (a.getDefaultBoolValue() ? a.argString()
-              : a.negateArgString()));
-    }
-
-    if (a.hasOption(Opt.MULTI))
-    {
-      options.add("multiple");
-    }
-
-    if (a.hasOption(Opt.LINKED))
-    {
-      options.add("can be linked");
-    }
-
-    if (a.hasOption(Opt.GLOB))
+    for (Opt o : EnumSet.allOf(Opt.class))
     {
-      options.add("allows file globs");
-    }
-
-    if (a.hasOption(Opt.ALLOWSUBSTITUTIONS))
-    {
-      options.add("allows substitutions");
-    }
-
-    if (a.hasOption(Opt.ALLOWALL))
-    {
-      options.add("can be applied to all linked arguments");
-    }
-
-    if (a.hasOption(Opt.PRIVATE))
-    {
-      options.add("for internal use only");
-    }
-
-    if (a.hasOption(Opt.SECRET))
-    {
-      options.add("for development use only");
+      if (a.hasOption(o) && o.description() != null)
+      {
+        options.add(o.description());
+      }
     }
 
+    final String optDisplaySeparator = "; ";
     if (options.size() > 0)
     {
+      int linelength = 0;
+      String spacing = String.format("%-"
+              + (maxArgLength + ARGDESCRIPTIONSEPARATOR.length()) + "s",
+              "");
       if (first)
       {
         sb.append(ARGDESCRIPTIONSEPARATOR);
+        linelength += maxArgLength + ARGDESCRIPTIONSEPARATOR.length();
       }
       else
       {
-        sb.append(String.format("%-"
-                + (maxArgLength + ARGDESCRIPTIONSEPARATOR.length()) + "s",
-                ""));
+        sb.append(spacing);
+        linelength += spacing.length();
+      }
+      if (options.size() > 0)
+      {
+        boolean optFirst = true;
+        Iterator<String> optionsI = options.listIterator();
+        while (optionsI.hasNext())
+        {
+          String desc = optionsI.next();
+          if (optFirst)
+          {
+            sb.append("(");
+            linelength += 1;
+          }
+          int descLength = desc.length()
+                  + (optionsI.hasNext() ? optDisplaySeparator.length() : 0);
+          if (linelength + descLength > maxWidth)
+          {
+            sb.append(System.lineSeparator());
+            linelength = 0;
+            sb.append(spacing);
+            linelength += spacing.length();
+          }
+          // sb.append(linelength + "+" + desc.length() + " ");
+          sb.append(desc);
+          linelength += desc.length();
+          if (optionsI.hasNext())
+          {
+            sb.append(optDisplaySeparator);
+            linelength += optDisplaySeparator.length();
+          }
+          optFirst = false;
+        }
+        sb.append(')');
+        sb.append(System.lineSeparator());
       }
-      sb.append("(");
-      sb.append(String.join("; ", options));
-      sb.append(')');
-      sb.append(System.lineSeparator());
     }
   }