JAL-629 Added Arg.Types and additional --help-type args to be --help with a type...
authorBen Soares <b.soares@dundee.ac.uk>
Fri, 19 May 2023 14:46:46 +0000 (15:46 +0100)
committerBen Soares <b.soares@dundee.ac.uk>
Fri, 19 May 2023 14:46:46 +0000 (15:46 +0100)
src/jalview/bin/Jalview.java
src/jalview/bin/argparser/Arg.java
src/jalview/util/Platform.java
src/jalview/util/StringUtils.java

index 3d20d3c..fb31189 100755 (executable)
@@ -49,6 +49,7 @@ import java.util.Vector;
 import java.util.logging.ConsoleHandler;
 import java.util.logging.Level;
 import java.util.logging.Logger;
+import java.util.stream.Collectors;
 
 import javax.swing.JDialog;
 import javax.swing.JFrame;
@@ -345,6 +346,7 @@ public class Jalview
       final File appdir = new File(appdirString);
       new Thread()
       {
+
         @Override
         public void run()
         {
@@ -492,10 +494,12 @@ public class Jalview
      * @j2sIgnore
      */
     {
-      if (bootstrapArgs.containsType(Type.HELP))
+      if (bootstrapArgs.contains(Arg.HELP))
       {
-        List<Arg> helpArgs = bootstrapArgs.getArgsOfType(Type.HELP);
-        System.out.println(Arg.usage(helpArgs));
+        List<Map.Entry<Type, String>> helpArgs = bootstrapArgs
+                .getList(Arg.HELP);
+        System.out.println(Arg.usage(helpArgs.stream().map(e -> e.getKey())
+                .collect(Collectors.toList())));
         Jalview.exit(null, 0);
       }
       if (aparser.contains("help") || aparser.contains("h"))
@@ -1698,14 +1702,40 @@ public class Jalview
 
   public static void exit(String message, int exitcode)
   {
-    Console.debug("Using Jalview.exit");
-    if (message != null)
-      if (exitcode == 0)
-        Console.info(message);
-      else
-        Console.error(message);
+    if (Console.log == null)
+    {
+      // Don't start the logger just to exit!
+      if (message != null)
+      {
+        if (exitcode == 0)
+        {
+          System.out.println(message);
+        }
+        else
+        {
+          System.err.println(message);
+        }
+      }
+    }
+    else
+    {
+      Console.debug("Using Jalview.exit");
+      if (message != null)
+      {
+        if (exitcode == 0)
+        {
+          Console.info(message);
+        }
+        else
+        {
+          Console.error(message);
+        }
+      }
+    }
     if (exitcode > -1)
+    {
       System.exit(exitcode);
+    }
   }
 
   /*
index a9cacbd..958569e 100644 (file)
@@ -10,15 +10,16 @@ import java.util.List;
 import java.util.Locale;
 import java.util.stream.Collectors;
 
-import jalview.bin.Cache;
+import jalview.bin.argparser.Arg.Opt;
 import jalview.util.ChannelProperties;
+import jalview.util.Platform;
 
 public enum Arg
 {
 
   // Initialising arguments (BOOTSTRAP)
-  HELP(Type.HELP, "h", "Display this help statement", Opt.UNARY,
-          Opt.BOOTSTRAP, Opt.HASTYPE, Opt.MULTI),
+  HELP(Type.HELP, "h", "Display basic help", Opt.UNARY, Opt.BOOTSTRAP,
+          Opt.HASTYPE, Opt.MULTI),
   /*
    * Other --help-type Args will be added by the static block.
    */
@@ -248,11 +249,11 @@ public enum Arg
   JVMMEMPC(Type.CONFIG,
           "Limit maximum heap size (memory) to PERCENT% of total physical memory detected. This defaults to 90 if total physical memory can be detected.\n"
                   + "The equals sign (\"=\") separator must be used with no spaces.",
-          Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING),
+          Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING, Opt.LAST),
   JVMMEMMAX(Type.CONFIG,
           "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"
                   + "The equals sign (\"=\") separator must be used with no spaces.",
-          Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING),
+          Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING, Opt.LAST),
 
   ;
 
@@ -306,26 +307,33 @@ public enum Arg
     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)
   }
 
   public static enum Type
   {
     // Type restricts argument to certain usage output
-    CONFIG("Arguments user to configure "
+    HELP, // --help
+    CONFIG("arguments used to configure "
             + ChannelProperties.getProperty("app_name") + " from startup"),
-    OPENING("Arguments used to open and format alignments"),
-    STRUCTURE("Arguments used to add and format 3D structure data"),
-    PROCESS("Arguments used to process an alignment once opened"),
-    OUTPUT("Arguments used to save data from a processed alignment"),
-    IMAGE("Arguments used to export an image of an alignment"),
-    FLOW("Arguments that control processing of the other arguments"),
-    HELP("Arguments to provide help text"), // --help
-    ALL("All arguments"), // mostly just a place-holder for --help-all
-    NONE("No specific arguments"), // mostly a place-holder for --help
-    INVALID("This type of argument doesn't exist");
+    OPENING("arguments used to open and format alignments"),
+    STRUCTURE("arguments used to add and format 3D structure data"),
+    PROCESS("arguments used to process an alignment once opened"),
+    OUTPUT("arguments used to save data from a processed alignment"),
+    IMAGE("arguments used to export an image of an alignment"),
+    FLOW("arguments that control processing of the other arguments"), //
+    ALL("all arguments"), // mostly just a place-holder for --help-all
+    NONE, // mostly a place-holder for --help
+    INVALID;
 
     private String description;
 
+    private Type()
+    {
+      description = null;
+    }
+
     private Type(String description)
     {
       this.description = description;
@@ -337,15 +345,6 @@ public enum Arg
     }
   }
 
-  static
-  {
-    for (Type t : EnumSet.allOf(Type.class))
-    {
-      String type = t.name();
-
-    }
-  }
-
   private final String[] argNames;
 
   private Opt[] argOptions;
@@ -501,130 +500,240 @@ public enum Arg
     return usage(null);
   }
 
-  public static final String usage(List<Arg> helpArgs)
+  public static final void appendUsageGeneral(StringBuilder sb,
+          int maxArgLength)
   {
-    StringBuilder sb = new StringBuilder();
-
-    sb.append(ChannelProperties.getProperty("app_name"));
-    String version = Cache.getDefault("VERSION", null);
-    if (version != null)
+    for (Type t : EnumSet.allOf(Type.class))
     {
-      sb.append(" version ");
-      sb.append(Cache.getDefault("VERSION", "unknown"));
+      if (t.description() != null)
+      {
+        StringBuilder argSb = new StringBuilder();
+        argSb.append(Arg.HELP.argString()).append(ArgParser.SINGLEDASH)
+                .append(t.name().toLowerCase(Locale.ROOT));
+        appendArgAndDescription(sb, argSb.toString(),
+                "Help for " + t.description(), null, maxArgLength);
+        sb.append(System.lineSeparator());
+      }
     }
-    sb.append(System.lineSeparator());
-    sb.append("Usage: jalview [files...] [args]");
+  }
+
+  public static final String usage(List<Type> types)
+  {
+    StringBuilder sb = new StringBuilder();
+
+    sb.append("usage: jalview [" + Arg.HEADLESS.argString() + "] ["
+            + Arg.OPEN.argString() + "/" + Arg.APPEND.argString()
+            + " file(s)] [args]");
     sb.append(System.lineSeparator());
     sb.append(System.lineSeparator());
 
-    // PROGRAM THIS!
-    Type type = Type.HELP;
-    Iterator<Arg> typeArgs = Arg.getAllOfType(type);
-
-    int maxArgLength = 0;
-    for (Arg a : EnumSet.allOf(Arg.class))
+    if (types == null || types.contains(null))
     {
-      if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET))
-        continue;
-
-      StringBuilder argSb = new StringBuilder();
-      argSb.append(a.hasOption(Opt.BOOLEAN) ? booleanArgString(a)
-              : a.argString());
-      if (a.hasOption(Opt.STRING))
-        argSb.append("=value");
-      if (argSb.length() > maxArgLength)
-        maxArgLength = argSb.length();
-    }
+      // always show --help
+      appendArgAndDescription(sb, null, "Display this basic help", Arg.HELP,
+              DESCRIPTIONINDENT);
+      sb.append(System.lineSeparator());
 
-    // might want to sort these
-    for (Arg a : EnumSet.allOf(Arg.class))
+      appendUsageGeneral(sb, DESCRIPTIONINDENT);
+    }
+    else
     {
-      if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET))
-        continue;
-      StringBuilder argSb = new StringBuilder();
-      argSb.append(a.hasOption(Opt.BOOLEAN) ? booleanArgString(a)
-              : a.argString());
-      if (a.hasOption(Opt.STRING))
-        argSb.append("=value");
-      Iterator<String> descLines = null;
-      if (a.getDescription() != null)
-      {
-        descLines = Arrays.stream(a.getDescription().split("\\n"))
-                .iterator();
-      }
-      sb.append(String.format("%-" + maxArgLength + "s", argSb.toString()));
-      boolean first = true;
-      if (descLines != null)
+      List<Arg> args = argsSortedForDisplay(types);
+
+      /*
+       * just use a set maxArgLength of DESCRIPTIONINDENT
+       
+      int maxArgLength = 0;
+      for (Arg a : args)
       {
-        while (descLines.hasNext())
-        {
-          if (first)
-            sb.append(" - ");
-          else
-            sb.append(" ".repeat(maxArgLength + 3));
-          sb.append(descLines.next());
-          sb.append(System.lineSeparator());
-          first = false;
-        }
+        if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET))
+          continue;
+      
+        String argS = argDisplayString(a);
+        if (argS.length() > maxArgLength)
+          maxArgLength = argS.length();
       }
+      */
+      int maxArgLength = DESCRIPTIONINDENT;
 
-      List<String> options = new ArrayList<>();
+      // always show --help
+      appendArgAndDescription(sb, null, null, Arg.HELP, maxArgLength);
+      sb.append(System.lineSeparator());
 
-      if (a.hasOption(Opt.BOOLEAN))
+      if ((args.contains(Arg.HELP) && types.contains(Type.ALL)))
       {
-        options.add("default " + (a.getDefaultBoolValue() ? a.argString()
-                : a.negateArgString()));
+        appendUsageGeneral(sb, maxArgLength);
       }
 
-      if (a.hasOption(Opt.MULTI))
+      Iterator<Arg> argsI = args.iterator();
+      while (argsI.hasNext())
       {
-        options.add("multiple");
-      }
+        Arg a = argsI.next();
 
-      if (a.hasOption(Opt.LINKED))
-      {
-        options.add("can be linked");
-      }
+        if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET)
+                || a == Arg.HELP)
+        {
+          continue;
+        }
 
-      if (a.hasOption(Opt.GLOB))
-      {
-        options.add("allows file globs");
-      }
+        appendArgUsage(sb, a, maxArgLength);
 
-      if (a.hasOption(Opt.ALLOWSUBSTITUTIONS))
-      {
-        options.add("allows substitutions");
+        if (argsI.hasNext())
+          sb.append(System.lineSeparator());
       }
+    }
+    return sb.toString();
+  }
+
+  private static void appendArgUsage(StringBuilder sb, Arg a,
+          int maxArgLength)
+  {
+    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))
+    {
+      options.add("allows file globs");
+    }
+
+    if (a.hasOption(Opt.ALLOWSUBSTITUTIONS))
+    {
+      options.add("allows substitutions");
+    }
 
-      if (a.hasOption(Opt.ALLOWALL))
+    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 (options.size() > 0)
+    {
+      if (first)
       {
-        options.add("can be applied to all linked arguments");
+        sb.append(ARGDESCRIPTIONSEPARATOR);
       }
-
-      if (a.hasOption(Opt.PRIVATE))
+      else
       {
-        options.add("for internal use only");
+        sb.append(String.format("%-"
+                + (maxArgLength + ARGDESCRIPTIONSEPARATOR.length()) + "s",
+                ""));
       }
+      sb.append("(");
+      sb.append(String.join("; ", options));
+      sb.append(')');
+      sb.append(System.lineSeparator());
+    }
+  }
+
+  public static String argDisplayString(Arg a)
+  {
+    StringBuilder argSb = new StringBuilder();
+    argSb.append(
+            a.hasOption(Opt.BOOLEAN) ? booleanArgString(a) : a.argString());
+    if (a.hasOption(Opt.STRING))
+      argSb.append("=value");
+    return argSb.toString();
+  }
+
+  public static boolean appendArgAndDescription(StringBuilder sb,
+          String aString, String description, Arg a, int maxArgLength)
+  {
+    return appendArgAndDescription(sb, aString, description, a,
+            maxArgLength, Platform.consoleWidth());
+  }
+
+  public static boolean appendArgAndDescription(StringBuilder sb,
+          String aString, String description, Arg a, int maxArgLength,
+          int maxLength)
+  {
+    if (aString == null && a != null)
+    {
+      aString = argDisplayString(a);
+    }
+    if (description == null && a != null)
+    {
+      description = a.getDescription();
+    }
+    sb.append(String.format("%-" + maxArgLength + "s", aString));
+    if (aString.length() > maxArgLength)
+    {
+      sb.append(System.lineSeparator());
+      sb.append(String.format("%-" + maxArgLength + "s", ""));
+    }
 
-      if (a.hasOption(Opt.SECRET))
+    int descLength = maxLength - maxArgLength
+            - ARGDESCRIPTIONSEPARATOR.length();
+    // reformat the descriptions lines to the right width
+    Iterator<String> descLines = null;
+    if (description != null)
+    {
+      descLines = Arrays.stream(description.split("\\n")).iterator();
+    }
+    List<String> splitDescLinesList = new ArrayList<>();
+    while (descLines != null && descLines.hasNext())
+    {
+      String line = descLines.next();
+      while (line.length() > descLength)
       {
-        options.add("for development use only");
+        int splitIndex = line.lastIndexOf(" ", descLength);
+        if (splitIndex > descLength)
+        {
+          break;
+        }
+        else
+        {
+          splitDescLinesList.add(line.substring(0, splitIndex));
+          line = line.substring(splitIndex + 1);
+        }
       }
+      splitDescLinesList.add(line);
+    }
 
-      if (options.size() > 0)
+    Iterator<String> splitDescLines = splitDescLinesList.iterator();
+    boolean first = true;
+    if (splitDescLines != null)
+    {
+      while (splitDescLines.hasNext())
       {
         if (first)
-          sb.append(" - ");
+          sb.append(ARGDESCRIPTIONSEPARATOR);
         else
-          sb.append(" ".repeat(maxArgLength + 3));
-        sb.append("(");
-        sb.append(String.join("; ", options));
-        sb.append(')');
+          sb.append(String.format("%-"
+                  + (maxArgLength + ARGDESCRIPTIONSEPARATOR.length()) + "s",
+                  ""));
+        sb.append(splitDescLines.next());
         sb.append(System.lineSeparator());
+        first = false;
       }
-      sb.append(System.lineSeparator());
     }
-    return sb.toString();
+    return first;
   }
 
   protected static Iterator<Arg> getAllOfType(Type type)
@@ -647,11 +756,11 @@ public enum Arg
     }).iterator();
   }
 
-  private static List<Arg> sortForDisplay(Type... types)
+  private static List<Arg> argsSortedForDisplay(List<Type> types)
   {
     List<Arg> argsToSort;
     // if no types provided, do all
-    if (types == null || types.length == 0)
+    if (types == null || types.size() == 0 || types.contains(Type.ALL))
     {
       argsToSort = Arrays
               .asList(EnumSet.allOf(Arg.class).toArray(new Arg[] {}));
@@ -661,42 +770,59 @@ public enum Arg
       argsToSort = new ArrayList<>();
       for (Type type : types)
       {
+        if (type == null)
+          continue;
         Arg.getAllOfType(type).forEachRemaining(a -> argsToSort.add(a));
       }
     }
 
-    Collections.sort(argsToSort, new ArgComparator());
+    Collections.sort(argsToSort, new ArgDisplayComparator());
     return argsToSort;
   }
 
-  protected int compareForDisplay(Arg other)
+  private static final String ARGDESCRIPTIONSEPARATOR = " - ";
+
+  private static final int DESCRIPTIONINDENT = 20;
+
+}
+
+class ArgDisplayComparator implements Comparator<Arg>
+{
+  private int compareArgOpts(Arg a, Arg b, Opt o)
   {
-    if (other == null)
-      return 1;
-    // first compare types (in order of appearance)
-    int i = this.getType().compareTo(other.getType());
-    if (i == 0)
-    {
-      // next prioritise primary arguments
-      i = this.hasOption(Opt.PRIMARY)
-              ? (other.hasOption(Opt.PRIMARY) ? 0 : 1)
-              : (other.hasOption(Opt.PRIMARY) ? -1 : 0);
-      if (i == 0)
-      {
-        // finally order of appearance in enum declarations
-        i = this.compareTo(other);
-      }
-    }
+    int i = a.hasOption(o) ? (b.hasOption(o) ? 0 : -1)
+            : (b.hasOption(o) ? 1 : 0);
     return i;
   }
 
-}
+  private int compareForDisplay(Arg a, Arg b)
+  {
+    if (b == null)
+      return -1;
+    // first compare types (in enum order)
+    int i = a.getType().compareTo(b.getType());
+    if (i != 0)
+      return i;
+    // do Opt.LAST next (oddly). Reversed args important!
+    i = compareArgOpts(b, a, Opt.LAST);
+    if (i != 0)
+      return i;
+    // priority order
+    Opt[] optOrder = { Opt.HELP, Opt.FIRST, Opt.PRIMARY, Opt.STRING,
+        Opt.BOOLEAN };
+    for (Opt o : optOrder)
+    {
+      i = compareArgOpts(a, b, o);
+      if (i != 0)
+        return i;
+    }
+    // finally order of appearance in enum declarations
+    return a.compareTo(b);
+  }
 
-class ArgComparator implements Comparator<Arg>
-{
   @Override
   public int compare(Arg a, Arg b)
   {
-    return a.compareForDisplay(b);
+    return compareForDisplay(a, b);
   }
 }
\ No newline at end of file
index 573e2d5..f5953bf 100644 (file)
@@ -20,8 +20,6 @@
  */
 package jalview.util;
 
-import jalview.javascript.json.JSON;
-
 import java.awt.Toolkit;
 import java.awt.event.MouseEvent;
 import java.io.BufferedReader;
@@ -40,6 +38,8 @@ import javax.swing.SwingUtilities;
 import org.json.simple.parser.JSONParser;
 import org.json.simple.parser.ParseException;
 
+import jalview.javascript.json.JSON;
+
 /**
  * System platform information used by Applet and Application
  * 
@@ -56,6 +56,27 @@ public class Platform
 
   private static Boolean isHeadless = null;
 
+  // If launched from CLI with launcher script then -DCOLUMNWIDTH is set
+  private static final int CONSOLEWIDTH;
+
+  private static final String CONSOLEWIDTHPROPERTY = "CONSOLEWIDTH";
+
+  static
+  {
+    int cw = 80;
+    if (System.getProperty(CONSOLEWIDTHPROPERTY) != null
+            && System.getProperty(CONSOLEWIDTHPROPERTY).length() > 0)
+    {
+      try
+      {
+        cw = Integer.parseInt(System.getProperty(CONSOLEWIDTHPROPERTY));
+      } catch (NumberFormatException e)
+      {
+      }
+    }
+    CONSOLEWIDTH = cw;
+  }
+
   /**
    * added to group mouse events into Windows and nonWindows (mac, unix, linux)
    * 
@@ -657,4 +678,12 @@ public class Platform
     String p2 = path2.replace('\\', '/');
     return p1.equals(p2);
   }
+
+  /**
+   * If started on command line using launch script, return the console width
+   */
+  public static int consoleWidth()
+  {
+    return CONSOLEWIDTH;
+  }
 }
index efaf3e2..1c67c92 100644 (file)
@@ -617,6 +617,22 @@ public class StringUtils
     {
       return string;
     }
+
   }
 
+  /* 
+   * return the maximum length of a List of Strings
+   */
+  public static int maxLength(List<String> l)
+  {
+    int max = 0;
+    for (String s : l)
+    {
+      if (s == null)
+        continue;
+      if (s.length() > max)
+        max = s.length();
+    }
+    return max;
+  }
 }