From: Ben Soares Date: Fri, 19 May 2023 14:46:46 +0000 (+0100) Subject: JAL-629 Added Arg.Types and additional --help-type args to be --help with a type... X-Git-Tag: Release_2_11_3_0~14^2~37 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=ef4d508e2a165ee0656ae6071d4a2bace94a516a;p=jalview.git JAL-629 Added Arg.Types and additional --help-type args to be --help with a type. Better formatting for console. --- diff --git a/src/jalview/bin/Jalview.java b/src/jalview/bin/Jalview.java index 3d20d3c..fb31189 100755 --- a/src/jalview/bin/Jalview.java +++ b/src/jalview/bin/Jalview.java @@ -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 helpArgs = bootstrapArgs.getArgsOfType(Type.HELP); - System.out.println(Arg.usage(helpArgs)); + List> 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); + } } /* diff --git a/src/jalview/bin/argparser/Arg.java b/src/jalview/bin/argparser/Arg.java index a9cacbd..958569e 100644 --- a/src/jalview/bin/argparser/Arg.java +++ b/src/jalview/bin/argparser/Arg.java @@ -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 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 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 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 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 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 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 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 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 descLines = null; + if (description != null) + { + descLines = Arrays.stream(description.split("\\n")).iterator(); + } + List 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 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 getAllOfType(Type type) @@ -647,11 +756,11 @@ public enum Arg }).iterator(); } - private static List sortForDisplay(Type... types) + private static List argsSortedForDisplay(List types) { List 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 +{ + 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 -{ @Override public int compare(Arg a, Arg b) { - return a.compareForDisplay(b); + return compareForDisplay(a, b); } } \ No newline at end of file diff --git a/src/jalview/util/Platform.java b/src/jalview/util/Platform.java index 573e2d5..f5953bf 100644 --- a/src/jalview/util/Platform.java +++ b/src/jalview/util/Platform.java @@ -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; + } } diff --git a/src/jalview/util/StringUtils.java b/src/jalview/util/StringUtils.java index efaf3e2..1c67c92 100644 --- a/src/jalview/util/StringUtils.java +++ b/src/jalview/util/StringUtils.java @@ -617,6 +617,22 @@ public class StringUtils { return string; } + } + /* + * return the maximum length of a List of Strings + */ + public static int maxLength(List l) + { + int max = 0; + for (String s : l) + { + if (s == null) + continue; + if (s.length() > max) + max = s.length(); + } + return max; + } }