Merge branch 'patch/JAL-4195_cli_eps_export_npe' into develop
[jalview.git] / src / jalview / bin / argparser / Arg.java
index 8bac623..2f25978 100644 (file)
@@ -28,7 +28,11 @@ public enum Arg
                   + ChannelProperties.getProperty("app_name"),
           Opt.UNARY, Opt.BOOTSTRAP),
   HEADLESS(Type.CONFIG,
                   + ChannelProperties.getProperty("app_name"),
           Opt.UNARY, Opt.BOOTSTRAP),
   HEADLESS(Type.CONFIG,
-          "Run Jalview in headless mode. No GUI interface will be created and Jalview will quit after all arguments have been processed.",
+          "Run Jalview in headless mode. No GUI interface will be created and Jalview will quit after all arguments have been processed. "
+                  + "Headless mode is assumed if an output file is to be generated, this can be overridden with --noheadless or --gui.",
+          Opt.BOOLEAN, Opt.BOOTSTRAP),
+  GUI(Type.CONFIG,
+          "Do not run Jalview in headless mode.  This overrides the assumption of headless mode when an output file is to be generated.",
           Opt.UNARY, Opt.BOOTSTRAP),
   JABAWS(Type.CONFIG, "Set a different URL to connect to a JABAWS server.",
           Opt.STRING, Opt.BOOTSTRAP),
           Opt.UNARY, Opt.BOOTSTRAP),
   JABAWS(Type.CONFIG, "Set a different URL to connect to a JABAWS server.",
           Opt.STRING, Opt.BOOTSTRAP),
@@ -40,9 +44,10 @@ public enum Arg
   QUESTIONNAIRE(Type.CONFIG,
           "Show (or don't show) the questionnaire if one is available.",
           true, Opt.BOOLEAN, Opt.BOOTSTRAP),
   QUESTIONNAIRE(Type.CONFIG,
           "Show (or don't show) the questionnaire if one is available.",
           true, Opt.BOOLEAN, Opt.BOOTSTRAP),
-  USAGESTATS(Type.CONFIG,
-          "Send (or don't send) initial launch usage stats.", true,
-          Opt.BOOLEAN, Opt.BOOTSTRAP),
+  NOUSAGESTATS(Type.CONFIG, "Don't send initial launch usage stats.",
+          Opt.UNARY, Opt.BOOTSTRAP),
+  NOSTARTUPFILE(Type.CONFIG, "Don't show the default startup file.",
+          Opt.UNARY, Opt.BOOTSTRAP),
   WEBSERVICEDISCOVERY(Type.CONFIG,
           "Attempt (or don't attempt) to connect to JABAWS web services.",
           true, Opt.BOOLEAN, Opt.BOOTSTRAP),
   WEBSERVICEDISCOVERY(Type.CONFIG,
           "Attempt (or don't attempt) to connect to JABAWS web services.",
           true, Opt.BOOLEAN, Opt.BOOTSTRAP),
@@ -61,6 +66,9 @@ public enum Arg
   INITSUBSTITUTIONS(Type.CONFIG,
           "Set ‑‑substitutions to be initially enabled (or initially disabled).",
           true, Opt.BOOLEAN, Opt.BOOTSTRAP, Opt.NOACTION, Opt.SECRET),
   INITSUBSTITUTIONS(Type.CONFIG,
           "Set ‑‑substitutions to be initially enabled (or initially disabled).",
           true, Opt.BOOLEAN, Opt.BOOTSTRAP, Opt.NOACTION, Opt.SECRET),
+  P(Type.CONFIG, "Set a Jalview preference value for this session.",
+          Opt.PREFIXKEV, Opt.PRESERVECASE, Opt.STRING, Opt.BOOTSTRAP,
+          Opt.MULTI, Opt.NOACTION, Opt.SECRET), // keep this secret for now.
 
   // Opening an alignment
   OPEN(Type.OPENING,
 
   // Opening an alignment
   OPEN(Type.OPENING,
@@ -76,7 +84,7 @@ public enum Arg
           "Specifies the title for the open alignment window as string.",
           Opt.STRING, Opt.LINKED),
   COLOUR(Type.OPENING, "color", // being a bit soft on the Americans!
           "Specifies the title for the open alignment window as string.",
           Opt.STRING, Opt.LINKED),
   COLOUR(Type.OPENING, "color", // being a bit soft on the Americans!
-          "Applies the colour scheme to the open alignment window. Valid values are:\n"
+          "Applies the colour scheme to the open alignment window. Valid values include:\n"
                   + "clustal,\n" + "blosum62,\n" + "pc-identity,\n"
                   + "zappo,\n" + "taylor,\n" + "gecos-flower,\n"
                   + "gecos-blossom,\n" + "gecos-sunset,\n"
                   + "clustal,\n" + "blosum62,\n" + "pc-identity,\n"
                   + "zappo,\n" + "taylor,\n" + "gecos-flower,\n"
                   + "gecos-blossom,\n" + "gecos-sunset,\n"
@@ -85,7 +93,11 @@ public enum Arg
                   + "turn-propensity,\n" + "buried-index,\n"
                   + "nucleotide,\n" + "nucleotide-ambiguity,\n"
                   + "purine-pyrimidine,\n" + "rna-helices,\n"
                   + "turn-propensity,\n" + "buried-index,\n"
                   + "nucleotide,\n" + "nucleotide-ambiguity,\n"
                   + "purine-pyrimidine,\n" + "rna-helices,\n"
-                  + "t-coffee-scores,\n" + "sequence-id.",
+                  + "t-coffee-scores,\n" + "sequence-id.\n"
+                  +"\n"
+                  + "Names of user defined colourschemes will also work,\n"
+                 +"and jalview colourscheme specifications like\n"
+                  +"--colour=\"D,E=red; K,R,H=0022FF; C,c=yellow\"",
           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
   FEATURES(Type.OPENING, "Add a feature file or URL to the open alignment.",
           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
   FEATURES(Type.OPENING, "Add a feature file or URL to the open alignment.",
           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
@@ -140,7 +152,7 @@ public enum Arg
           "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"
                   + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
           Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL,
           "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"
                   + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
           Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL,
-          Opt.REQUIREINPUT, Opt.OUTPUT, Opt.PRIMARY),
+          Opt.REQUIREINPUT, Opt.OUTPUTFILE, Opt.PRIMARY),
   TYPE(Type.IMAGE,
           "Set the image format for the preceding --image. Valid values are:\n"
                   + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
   TYPE(Type.IMAGE,
           "Set the image format for the preceding --image. Valid values are:\n"
                   + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
@@ -160,7 +172,7 @@ public enum Arg
           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
   STRUCTUREIMAGE(Type.STRUCTUREIMAGE,
           "Export an image of a 3D structure opened in JMOL", Opt.STRING,
           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
   STRUCTUREIMAGE(Type.STRUCTUREIMAGE,
           "Export an image of a 3D structure opened in JMOL", Opt.STRING,
-          Opt.LINKED, Opt.MULTI),
+          Opt.LINKED, Opt.MULTI, Opt.OUTPUTFILE),
   STRUCTUREIMAGETYPE(Type.STRUCTUREIMAGE,
           "Set the structure image format for the preceding --structureimage. Valid values are:\n"
                   + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
   STRUCTUREIMAGETYPE(Type.STRUCTUREIMAGE,
           "Set the structure image format for the preceding --structureimage. Valid values are:\n"
                   + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
@@ -188,7 +200,7 @@ public enum Arg
                   + "clustal (aln),\n" + "phylip (phy),\n"
                   + "jalview (jvp, jar).",
           Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL,
                   + "clustal (aln),\n" + "phylip (phy),\n"
                   + "jalview (jvp, jar).",
           Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL,
-          Opt.REQUIREINPUT, Opt.OUTPUT, Opt.PRIMARY),
+          Opt.REQUIREINPUT, Opt.OUTPUTFILE, Opt.PRIMARY),
   FORMAT(Type.OUTPUT,
           "Sets the format for the preceding --output file. Valid formats are:\n"
                   + "fasta,\n" + "pfam,\n" + "stockholm,\n" + "pir,\n"
   FORMAT(Type.OUTPUT,
           "Sets the format for the preceding --output file. Valid formats are:\n"
                   + "fasta,\n" + "pfam,\n" + "stockholm,\n" + "pir,\n"
@@ -283,56 +295,146 @@ public enum Arg
 
   public static enum Opt
   {
 
   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)
-    OUTPUT, // 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("output file --headless will be assumed unless --gui used"),
+    /*
+     * 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),
+    /*
+     * After other args are checked, the following args can prefix a KEY=VALUE argument
+     */
+    PREFIXKEV("prefixes key=value"),
+    /*
+     * do not lowercase the name when getting the arg name or arg string
+     */
+    PRESERVECASE(null),
+    //
+    ;
+
+    private String description;
+
+    private Opt()
+    {
+      description = null;
+    }
+
+    private Opt(String description)
+    {
+      this.description = description;
+    }
+
+    public String description()
+    {
+      return description;
+    }
+
   }
 
   public static enum Type
   }
 
   public static enum Type
@@ -400,15 +502,15 @@ public enum Arg
   private Arg(Type type, String alternativeName, String description,
           boolean defaultBoolean, Opt... options)
   {
   private Arg(Type type, String alternativeName, String description,
           boolean defaultBoolean, Opt... options)
   {
+    this.type = type;
+    this.description = description;
+    this.defaultBoolValue = defaultBoolean;
+    this.setOptions(options);
     this.argNames = alternativeName != null
             ? new String[]
             { this.getName(), alternativeName }
             : new String[]
             { this.getName() };
     this.argNames = alternativeName != null
             ? new String[]
             { this.getName(), alternativeName }
             : new String[]
             { this.getName() };
-    this.type = type;
-    this.description = description;
-    this.defaultBoolValue = defaultBoolean;
-    this.setOptions(options);
   }
 
   public String argString()
   }
 
   public String argString()
@@ -458,7 +560,9 @@ public enum Arg
 
   public String getName()
   {
 
   public String getName()
   {
-    return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
+    String name = hasOption(Opt.PRESERVECASE) ? this.name()
+            : this.name().toLowerCase(Locale.ROOT);
+    return name.replace('_', '-');
   }
 
   @Override
   }
 
   @Override
@@ -489,6 +593,11 @@ public enum Arg
     return true;
   }
 
     return true;
   }
 
+  protected Opt[] getOptions()
+  {
+    return argOptions;
+  }
+
   protected void setOptions(Opt... options)
   {
     this.argOptions = options;
   protected void setOptions(Opt... options)
   {
     this.argOptions = options;
@@ -617,7 +726,7 @@ public enum Arg
           }
         }
 
           }
         }
 
-        appendArgUsage(sb, a, maxArgLength);
+        appendArgUsage(sb, a, maxArgLength, Platform.consoleWidth());
 
         if (argsI.hasNext())
         {
 
         if (argsI.hasNext())
         {
@@ -629,69 +738,71 @@ public enum Arg
   }
 
   private static void appendArgUsage(StringBuilder sb, Arg a,
   }
 
   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<>();
 
   {
     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))
+    for (Opt o : EnumSet.allOf(Opt.class))
     {
     {
-      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)
     {
     if (options.size() > 0)
     {
+      int linelength = 0;
+      String spacing = String.format("%-"
+              + (maxArgLength + ARGDESCRIPTIONSEPARATOR.length()) + "s",
+              "");
       if (first)
       {
         sb.append(ARGDESCRIPTIONSEPARATOR);
       if (first)
       {
         sb.append(ARGDESCRIPTIONSEPARATOR);
+        linelength += maxArgLength + ARGDESCRIPTIONSEPARATOR.length();
       }
       else
       {
       }
       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());
     }
   }
 
     }
   }
 
@@ -701,7 +812,16 @@ public enum Arg
     argSb.append(
             a.hasOption(Opt.BOOLEAN) ? booleanArgString(a) : a.argString());
     if (a.hasOption(Opt.STRING))
     argSb.append(
             a.hasOption(Opt.BOOLEAN) ? booleanArgString(a) : a.argString());
     if (a.hasOption(Opt.STRING))
-      argSb.append("=value");
+    {
+      if (a.hasOption(Opt.PREFIXKEV))
+      {
+        argSb.append("key=value");
+      }
+      else
+      {
+        argSb.append("=value");
+      }
+    }
     return argSb.toString();
   }
 
     return argSb.toString();
   }