<groupId>org.argeo.tp.apache.commons</groupId>
<artifactId>org.apache.commons.io</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.argeo.tp.apache.commons</groupId>
+ <artifactId>org.apache.commons.cli</artifactId>
+ </dependency>
<dependency>
<groupId>org.argeo.tp.apache.commons</groupId>
<artifactId>org.apache.commons.codec</artifactId>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.argeo.commons</groupId>
<artifactId>org.apache.xerces</artifactId>
</dependency>
+ <!-- Legacy but still used by SLC runtime, to be removed -->
+ <dependency>
+ <groupId>org.argeo.tp.apache.commons</groupId>
+ <artifactId>org.apache.commons.exec</artifactId>
+ </dependency>
+
</dependencies>
<profiles>
--- /dev/null
+package org.argeo.cms.cli;
+
+public class CommandArgsException extends IllegalArgumentException {
+ private static final long serialVersionUID = -7271050747105253935L;
+ private String commandName;
+ private volatile CommandsCli commandsCli;
+
+ public CommandArgsException(Exception cause) {
+ super(cause.getMessage(), cause);
+ }
+
+ public CommandArgsException(String message) {
+ super(message);
+ }
+
+ public String getCommandName() {
+ return commandName;
+ }
+
+ public void setCommandName(String commandName) {
+ this.commandName = commandName;
+ }
+
+ public CommandsCli getCommandsCli() {
+ return commandsCli;
+ }
+
+ public void setCommandsCli(CommandsCli commandsCli) {
+ this.commandsCli = commandsCli;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.cli;
+
+import java.util.List;
+
+/** {@link RuntimeException} referring during a command run. */
+public class CommandRuntimeException extends RuntimeException {
+ private static final long serialVersionUID = 5595999301269377128L;
+
+ private final DescribedCommand<?> command;
+ private final List<String> arguments;
+
+ public CommandRuntimeException(Throwable e, DescribedCommand<?> command, List<String> arguments) {
+ this(null, e, command, arguments);
+ }
+
+ public CommandRuntimeException(String message, DescribedCommand<?> command, List<String> arguments) {
+ this(message, null, command, arguments);
+ }
+
+ public CommandRuntimeException(String message, Throwable e, DescribedCommand<?> command, List<String> arguments) {
+ super(message == null ? "(" + command.getClass().getName() + " " + arguments.toString() + ")"
+ : message + " (" + command.getClass().getName() + " " + arguments.toString() + ")", e);
+ this.command = command;
+ this.arguments = arguments;
+ }
+
+ public DescribedCommand<?> getCommand() {
+ return command;
+ }
+
+ public List<String> getArguments() {
+ return arguments;
+ }
+
+}
--- /dev/null
+package org.argeo.cms.cli;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Function;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/** Base class for a CLI managing sub commands. */
+public abstract class CommandsCli implements DescribedCommand<Object> {
+ public final static String HELP = "help";
+
+ private final String commandName;
+ private Map<String, Function<List<String>, ?>> commands = new TreeMap<>();
+
+ protected final Options options = new Options();
+
+ public CommandsCli(String commandName) {
+ this.commandName = commandName;
+ }
+
+ @Override
+ public Object apply(List<String> args) {
+ String cmd = null;
+ List<String> newArgs = new ArrayList<>();
+ try {
+ CommandLineParser clParser = new DefaultParser();
+ CommandLine commonCl = clParser.parse(getOptions(), args.toArray(new String[args.size()]), true);
+ List<String> leftOvers = commonCl.getArgList();
+ for (String arg : leftOvers) {
+ if (!arg.startsWith("-") && cmd == null) {
+ cmd = arg;
+ } else {
+ newArgs.add(arg);
+ }
+ }
+ } catch (ParseException e) {
+ CommandArgsException cae = new CommandArgsException(e);
+ throw cae;
+ }
+
+ Function<List<String>, ?> function = cmd != null ? getCommand(cmd) : getDefaultCommand();
+ if (function == null)
+ throw new IllegalArgumentException("Uknown command " + cmd);
+ try {
+ return function.apply(newArgs).toString();
+ } catch (CommandArgsException e) {
+ if (e.getCommandName() == null) {
+ e.setCommandName(cmd);
+ e.setCommandsCli(this);
+ }
+ throw e;
+ } catch (IllegalArgumentException e) {
+ CommandArgsException cae = new CommandArgsException(e);
+ cae.setCommandName(cmd);
+ throw cae;
+ }
+ }
+
+ @Override
+ public Options getOptions() {
+ return options;
+ }
+
+ protected void addCommand(String cmd, Function<List<String>, ?> function) {
+ commands.put(cmd, function);
+
+ }
+
+ @Override
+ public String getUsage() {
+ return "[command]";
+ }
+
+ protected void addCommandsCli(CommandsCli commandsCli) {
+ addCommand(commandsCli.getCommandName(), commandsCli);
+ commandsCli.addCommand(HELP, new HelpCommand(this, commandsCli));
+ }
+
+ public String getCommandName() {
+ return commandName;
+ }
+
+ public Set<String> getSubCommands() {
+ return commands.keySet();
+ }
+
+ public Function<List<String>, ?> getCommand(String command) {
+ return commands.get(command);
+ }
+
+ public HelpCommand getHelpCommand() {
+ return (HelpCommand) getCommand(HELP);
+ }
+
+ public Function<List<String>, String> getDefaultCommand() {
+ return getHelpCommand();
+ }
+
+ /** In order to implement quickly a main method. */
+ public static void mainImpl(CommandsCli cli, String[] args) {
+ try {
+ cli.addCommand(CommandsCli.HELP, new HelpCommand(null, cli));
+ Object output = cli.apply(Arrays.asList(args));
+ System.out.println(output);
+ System.exit(0);
+ } catch (CommandArgsException e) {
+ System.err.println("Wrong arguments " + Arrays.toString(args) + ": " + e.getMessage());
+ if (e.getCommandName() != null) {
+ StringWriter out = new StringWriter();
+ HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
+ System.err.println(out.toString());
+ } else {
+ e.printStackTrace();
+ }
+ System.exit(1);
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+}
--- /dev/null
+package org.argeo.cms.cli;
+
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/** A command that can be described. */
+public interface DescribedCommand<T> extends Function<List<String>, T> {
+ default Options getOptions() {
+ return new Options();
+ }
+
+ String getDescription();
+
+ default String getUsage() {
+ return null;
+ }
+
+ default String getExamples() {
+ return null;
+ }
+
+ default CommandLine toCommandLine(List<String> args) {
+ try {
+ DefaultParser parser = new DefaultParser();
+ return parser.parse(getOptions(), args.toArray(new String[args.size()]));
+ } catch (ParseException e) {
+ throw new CommandArgsException(e);
+ }
+ }
+
+ /** In order to implement quickly a main method. */
+ public static void mainImpl(DescribedCommand<?> command, String[] args) {
+ try {
+ Object output = command.apply(Arrays.asList(args));
+ System.out.println(output);
+ System.exit(0);
+ } catch (IllegalArgumentException e) {
+ StringWriter out = new StringWriter();
+ HelpCommand.printHelp(command, out);
+ System.err.println(out.toString());
+ System.exit(1);
+ } catch (Exception e) {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.cms.cli;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+
+/** A special command that can describe {@link DescribedCommand}. */
+public class HelpCommand implements DescribedCommand<String> {
+ private CommandsCli commandsCli;
+ private CommandsCli parentCommandsCli;
+
+ // Help formatting
+ private static int helpWidth = 80;
+ private static int helpLeftPad = 4;
+ private static int helpDescPad = 20;
+
+ public HelpCommand(CommandsCli parentCommandsCli, CommandsCli commandsCli) {
+ super();
+ this.parentCommandsCli = parentCommandsCli;
+ this.commandsCli = commandsCli;
+ }
+
+ @Override
+ public String apply(List<String> args) {
+ StringWriter out = new StringWriter();
+
+ if (args.size() == 0) {// overview
+ printHelp(commandsCli, out);
+ } else {
+ String cmd = args.get(0);
+ Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+ if (function == null)
+ return "Command " + cmd + " not found.";
+ Options options;
+ String examples;
+ DescribedCommand<?> command = null;
+ if (function instanceof DescribedCommand) {
+ command = (DescribedCommand<?>) function;
+ options = command.getOptions();
+ examples = command.getExamples();
+ } else {
+ options = new Options();
+ examples = null;
+ }
+ String description = getShortDescription(function);
+ String commandCall = getCommandUsage(cmd, command);
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp(new PrintWriter(out), helpWidth, commandCall, description, options, helpLeftPad,
+ helpDescPad, examples, false);
+ }
+ return out.toString();
+ }
+
+ private static String getShortDescription(Function<List<String>, ?> function) {
+ if (function instanceof DescribedCommand) {
+ return ((DescribedCommand<?>) function).getDescription();
+ } else {
+ return function.toString();
+ }
+ }
+
+ public String getCommandUsage(String cmd, DescribedCommand<?> command) {
+ String commandCall = getCommandCall(commandsCli) + " " + cmd;
+ assert command != null;
+ if (command != null && command.getUsage() != null) {
+ commandCall = commandCall + " " + command.getUsage();
+ }
+ return commandCall;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Shows this help or describes a command";
+ }
+
+ @Override
+ public String getUsage() {
+ return "[command]";
+ }
+
+ public CommandsCli getParentCommandsCli() {
+ return parentCommandsCli;
+ }
+
+ protected String getCommandCall(CommandsCli commandsCli) {
+ HelpCommand hc = commandsCli.getHelpCommand();
+ if (hc.getParentCommandsCli() != null) {
+ return getCommandCall(hc.getParentCommandsCli()) + " " + commandsCli.getCommandName();
+ } else {
+ return commandsCli.getCommandName();
+ }
+ }
+
+ public static void printHelp(DescribedCommand<?> command, StringWriter out) {
+ String usage = "java " + command.getClass().getName()
+ + (command.getUsage() != null ? " " + command.getUsage() : "");
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), command.getOptions(),
+ helpLeftPad, helpDescPad, command.getExamples(), false);
+
+ }
+
+ public static void printHelp(CommandsCli commandsCli, String commandName, StringWriter out) {
+ DescribedCommand<?> command = (DescribedCommand<?>) commandsCli.getCommand(commandName);
+ String usage = commandsCli.getHelpCommand().getCommandUsage(commandName, command);
+ HelpFormatter formatter = new HelpFormatter();
+ formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), command.getOptions(),
+ helpLeftPad, helpDescPad, command.getExamples(), false);
+
+ }
+
+ public static void printHelp(CommandsCli commandsCli, StringWriter out) {
+ out.append(commandsCli.getDescription()).append('\n');
+ String leftPad = spaces(helpLeftPad);
+ for (String cmd : commandsCli.getSubCommands()) {
+ Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+ assert function != null;
+ out.append(leftPad);
+ out.append(cmd);
+ // TODO deal with long commands
+ out.append(spaces(helpDescPad - cmd.length()));
+ out.append(getShortDescription(function));
+ out.append('\n');
+ }
+ }
+
+ private static String spaces(int count) {
+ // Java 11
+ // return " ".repeat(count);
+ if (count <= 0)
+ return "";
+ else {
+ StringBuilder sb = new StringBuilder(count);
+ for (int i = 0; i < count; i++)
+ sb.append(' ');
+ return sb.toString();
+ }
+ }
+}
--- /dev/null
+/** Command line API. */
+package org.argeo.cms.cli;
\ No newline at end of file