From 867d36daf15d299f8d55eb58c65879e87011c5cd Mon Sep 17 00:00:00 2001 From: Loris Sauter Date: Sun, 15 May 2022 15:46:02 +0200 Subject: [PATCH] Standalone commands / CLI respect -h / --help for help (#307) * Fixed #306 by adding the help option and subsequent minor refactoring Former-commit-id: dcdc208595d72b865faa3ec650fd4e99874a4269 --- build.gradle | 2 +- .../org/vitrivr/cineast/standalone/Main.java | 33 +++++++++++-- .../cli/AbstractCineastCommand.java | 35 +++++++++++++ .../cineast/standalone/cli/CineastCli.java | 6 ++- .../standalone/cli/CodebookCommand.java | 4 +- .../standalone/cli/DatabaseSetupCommand.java | 4 +- .../cli/DistinctColumnApiCommand.java | 4 +- .../standalone/cli/ExtractionCommand.java | 4 +- .../cineast/standalone/cli/ImportCommand.java | 4 +- .../cli/OptimizeEntitiesCommand.java | 4 +- .../standalone/cli/RetrieveCommand.java | 5 +- .../cli/SingleObjRetrievalCommand.java | 5 +- .../standalone/cli/TagRetrievalCommand.java | 5 +- .../standalone/cli/TextRetrievalCommand.java | 5 +- .../standalone/cli/ThreeDeeTestCommand.java | 4 +- .../standalone/cli/db/DropTableCommand.java | 5 +- .../cli/db/PolyphenyBenchmarkCommand.java | 5 +- .../vitrivr/cineast/standalone/util/CLI.java | 49 ++++++++++++++----- 18 files changed, 140 insertions(+), 43 deletions(-) create mode 100644 cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/AbstractCineastCommand.java diff --git a/build.gradle b/build.gradle index fa056df7e..1c4347f2a 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ allprojects { group = 'org.vitrivr' /* Our current version, on dev branch this should always be release+1-SNAPSHOT */ - version = '3.11.3' + version = '3.11.4' apply plugin: 'java-library' apply plugin: 'maven-publish' diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/Main.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/Main.java index b573d83a7..768865e52 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/Main.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/Main.java @@ -1,5 +1,8 @@ package org.vitrivr.cineast.standalone; +import com.github.rvesse.airline.parser.ParseResult; +import com.github.rvesse.airline.parser.errors.ParseException; +import java.io.IOException; import java.util.Arrays; import org.vitrivr.cineast.standalone.cli.CineastCli; import org.vitrivr.cineast.standalone.config.Config; @@ -35,9 +38,33 @@ public static void main(String[] args) { CLI.start(CineastCli.class); } else { com.github.rvesse.airline.Cli cli = new com.github.rvesse.airline.Cli<>(CineastCli.class); - final Runnable command = cli.parse(Arrays.copyOfRange(args, 1, args.length)); - command.run(); + final String[] theArgs = Arrays.copyOfRange(args, 1, args.length); + // Adopted from https://rvesse.github.io/airline/guide/help/index.html + // Parse with a result to allow us to inspect the results of parsing + ParseResult result = cli.parseWithResult(theArgs); + if (result.wasSuccessful()) { + // Parsed successfully, so just run the command and exit + result.getCommand().run(); + } else { + // Parsing failed + // Display errors and then the help information + System.err.println(String.format("%d errors encountered:", result.getErrors().size())); + int i = 1; + for (ParseException e : result.getErrors()) { + System.err.println(String.format("Error %d: %s", i, e.getMessage())); + i++; + } + + System.err.println(); + + try { + com.github.rvesse.airline.help.Help.help(cli.getMetadata(), Arrays.asList(theArgs), System.out); // Is it appropriate to use System.out here? + } catch (IOException e) { + // Something seriously went wrong, as we could not display the help message. + e.printStackTrace(); + } + } + PrometheusServer.stopServer(); } - PrometheusServer.stopServer(); } } diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/AbstractCineastCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/AbstractCineastCommand.java new file mode 100644 index 000000000..2f8c9ae84 --- /dev/null +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/AbstractCineastCommand.java @@ -0,0 +1,35 @@ +package org.vitrivr.cineast.standalone.cli; + +import com.github.rvesse.airline.HelpOption; +import javax.inject.Inject; + +/** + * The base command for al Cineast CLI commands. + * + * @author loris.sauter + * @version 1.0 + */ +public abstract class AbstractCineastCommand implements Runnable { + + /** + * The help option available to derived classes. + */ + @Inject + protected HelpOption help; + + /** + * The main method of all commands which will be executed by the CLI. + */ + @Override + public void run() { + if (!help.showHelpIfRequested()) { + this.execute(); + } + } + + /** + * The actual command logic to be executed. + */ + protected abstract void execute(); + +} diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/CineastCli.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/CineastCli.java index a33a83605..7a7b51716 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/CineastCli.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/CineastCli.java @@ -1,16 +1,20 @@ package org.vitrivr.cineast.standalone.cli; import com.github.rvesse.airline.annotations.Cli; +import com.github.rvesse.airline.annotations.Parser; import com.github.rvesse.airline.help.Help; +import com.github.rvesse.airline.parser.errors.handlers.CollectAll; import org.vitrivr.cineast.standalone.cli.db.DropTableCommand; import org.vitrivr.cineast.standalone.cli.db.PolyphenyBenchmarkCommand; + @Cli(name = "cineast-api", description = "The CLI provided by the Cineast API.", commands = { DropTableCommand.class, TagRetrievalCommand.class, OptimizeEntitiesCommand.class, CodebookCommand.class, DatabaseSetupCommand.class, ExtractionCommand.class, ImportCommand.class, ThreeDeeTestCommand.class, RetrieveCommand.class, Help.class, SingleObjRetrievalCommand.class, TextRetrievalCommand.class, DistinctColumnApiCommand.class, - PolyphenyBenchmarkCommand.class}, defaultCommand = Help.class) + PolyphenyBenchmarkCommand.class}, defaultCommand = Help.class, parserConfiguration = @Parser(errorHandler = CollectAll.class) +) public class CineastCli { } diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/CodebookCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/CodebookCommand.java index 92b24bbbf..c6ced0008 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/CodebookCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/CodebookCommand.java @@ -12,7 +12,7 @@ * A CLI command that can be used to generate SURF or HOG codebooks based on a set of images. */ @Command(name = "codebook", description = "Generates a codebook of defined size based on a set of images using some specified features.") -public class CodebookCommand implements Runnable { +public class CodebookCommand extends AbstractCineastCommand { @Option(name = {"-n", "--name"}, description = "The fully qualified name of the codebook generator. Supported values are HOGCodebookGenerator and SURFCodebookGenerator.") private String name; @@ -27,7 +27,7 @@ public class CodebookCommand implements Runnable { private int words; @Override - public void run() { + public void execute() { final CodebookGenerator generator = ReflectionHelper.newCodebookGenerator(name); final Path input = Paths.get(this.input); final Path output = Paths.get(this.output); diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/DatabaseSetupCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/DatabaseSetupCommand.java index f2f601668..7ea4d9aef 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/DatabaseSetupCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/DatabaseSetupCommand.java @@ -19,7 +19,7 @@ * A CLI command that can be used to setup all the database entities required by Cineast. */ @Command(name = "setup", description = "Makes the necessary database setup for Cineast and creates all the required entities and inidices.") -public class DatabaseSetupCommand implements Runnable { +public class DatabaseSetupCommand extends AbstractCineastCommand { private static final Logger LOGGER = LogManager.getLogger(DatabaseSetupCommand.class); /** @@ -72,7 +72,7 @@ private HashSet extractionConfigPersistentOperators(String e } @Override - public void run() { + public void execute() { doSetup(); } diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/DistinctColumnApiCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/DistinctColumnApiCommand.java index f26d199ce..b6f16c1de 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/DistinctColumnApiCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/DistinctColumnApiCommand.java @@ -12,7 +12,7 @@ import org.vitrivr.cineast.standalone.config.Config; @Command(name = "distinct-column", description = "Retrieves all distinct elements from the database for a given table and a given column") -public class DistinctColumnApiCommand implements Runnable { +public class DistinctColumnApiCommand extends AbstractCineastCommand { @Option(name = {"--table"}, title = "Table", description = "Table in the underlying database") @Required @@ -26,7 +26,7 @@ public class DistinctColumnApiCommand implements Runnable { private int limit = -1; @Override - public void run() { + public void execute() { DBSelector selector = Config.sharedConfig().getDatabase().getSelectorSupplier().get(); selector.open(table); long start = System.currentTimeMillis(); diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/ExtractionCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/ExtractionCommand.java index 1ae9b1f83..50d54975c 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/ExtractionCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/ExtractionCommand.java @@ -31,7 +31,7 @@ * A CLI command that can be used to start a media extraction based on an extraction definition file. */ @Command(name = "extract", description = "Starts a media extracting using the specified settings.") -public class ExtractionCommand implements Runnable { +public class ExtractionCommand extends AbstractCineastCommand { private static final Logger LOGGER = LogManager.getLogger(); @@ -48,7 +48,7 @@ public class ExtractionCommand implements Runnable { private Runnable postExtractionIIIFCleanup; @Override - public void run() { + public void execute() { final ExtractionDispatcher dispatcher = new ExtractionDispatcher(); final File file = new File(this.extractionConfig); if (file.exists()) { diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/ImportCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/ImportCommand.java index 99b66ce76..0014e19e3 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/ImportCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/ImportCommand.java @@ -15,7 +15,7 @@ * A CLI command that can be used to start import of pre-extracted data. */ @Command(name = "import", description = "Starts import of pre-extracted data.") -public class ImportCommand implements Runnable { +public class ImportCommand extends AbstractCineastCommand { @Required @Option(name = {"-t", "--type"}, description = "Type of data import that should be started.") @@ -41,7 +41,7 @@ public class ImportCommand implements Runnable { private boolean noTransactions = false; @Override - public void run() { + public void execute() { System.out.printf("Starting import of type %s for '%s'. Batchsize %d, %d threads. Clean %b, no-finalize %b .%n", this.type, this.input, this.batchsize, this.threads, this.clean, this.doNotFinalize); final Path path = Paths.get(this.input); if (noTransactions) { diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/OptimizeEntitiesCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/OptimizeEntitiesCommand.java index c2c5023ec..657a6c02e 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/OptimizeEntitiesCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/OptimizeEntitiesCommand.java @@ -8,7 +8,7 @@ import org.vitrivr.cottontail.client.language.ddl.OptimizeEntity; @Command(name = "optimize", description = "Optimize all entities for the Cineast schema. This command is only compatible with the Cottontail DB database.") -public class OptimizeEntitiesCommand implements Runnable { +public class OptimizeEntitiesCommand extends AbstractCineastCommand { public static void optimizeAllCottontailEntities() { if (Config.sharedConfig().getDatabase().getSelector() != DataSource.COTTONTAIL || Config.sharedConfig().getDatabase().getWriter() != DataSource.COTTONTAIL) { @@ -27,7 +27,7 @@ public static void optimizeAllCottontailEntities() { } @Override - public void run() { + public void execute() { optimizeAllCottontailEntities(); } } diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/RetrieveCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/RetrieveCommand.java index b659e07e5..ec874bdbc 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/RetrieveCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/RetrieveCommand.java @@ -12,7 +12,7 @@ @Command(name = "retrieve-mlt", description = "Retrieves objects from the database using an example segment. Equivalent to an MLT lookup.") -public class RetrieveCommand implements Runnable { +public class RetrieveCommand extends AbstractCineastCommand { @Option(name = {"-s", "--segmentid"}, title = "Segment ID", description = "The ID of the segment to use an example for retrieval.") private String segmentId; @@ -23,7 +23,8 @@ public class RetrieveCommand implements Runnable { @Option(name = {"-r", "--relevantSegments"}, title = "Relevant segments", description = "Comma separated list of segment IDs to which the query is to be limited.") private String relevantSegments; - public void run() { + @Override + public void execute() { final ContinuousRetrievalLogic retrieval = new ContinuousRetrievalLogic(Config.sharedConfig().getDatabase()); QueryConfig qc = QueryConfig.newQueryConfigFromOther(new ConstrainedQueryConfig("cli-query", new ArrayList<>())); diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/SingleObjRetrievalCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/SingleObjRetrievalCommand.java index d8287f7ef..5626b26ac 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/SingleObjRetrievalCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/SingleObjRetrievalCommand.java @@ -6,7 +6,7 @@ import org.vitrivr.cineast.standalone.config.Config; @Command(name = "retrieve-single", description = "Retrieves all information from the database for a given segment / object.") -public class SingleObjRetrievalCommand implements Runnable { +public class SingleObjRetrievalCommand extends AbstractCineastCommand { @Option(name = {"--segmentid"}, title = "Segment ID", description = "The ID of the segment for which to retrieve detailed information.") private String segmentId; @@ -23,7 +23,8 @@ public class SingleObjRetrievalCommand implements Runnable { @Option(name = {"-c", "--category"}, title = "Category", description = "Name of the feature category to retrieve. By default, all categories are retrieved for a segment.") private String category; - public void run() { + @Override + public void execute() { DBSelector selector = Config.sharedConfig().getDatabase().getSelectorSupplier().get(); if (segmentId != null) { CliUtils.printInfoForSegment(segmentId, selector, category, true); diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/TagRetrievalCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/TagRetrievalCommand.java index 5a74167bd..b6e4d1ea0 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/TagRetrievalCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/TagRetrievalCommand.java @@ -11,7 +11,7 @@ import org.vitrivr.cineast.standalone.util.ContinuousRetrievalLogic; @Command(name = "retrieve-tags", description = "Retrieves objects from the database using text as query input.") -public class TagRetrievalCommand implements Runnable { +public class TagRetrievalCommand extends AbstractCineastCommand { private String data = "data:application/json;base64,W3siaWQiOiJRODUwMiIsIm5hbWUiOiJtb3VudGFpbiIsImRlc2NyaXB0aW9uIjoiIn0seyJpZCI6IlE2OTMwMTcwNSIsIm5hbWUiOiJzaGVlcCIsImRlc2NyaXB0aW9uIjoiIn0seyJpZCI6IlE3MzY4IiwibmFtZSI6InNoZWVwIiwiZGVzY3JpcHRpb24iOiIifV0="; @@ -21,7 +21,8 @@ public class TagRetrievalCommand implements Runnable { @Option(name = {"--detail"}, title = "detailed results", description = "also list detailed results for retrieved segments.") private Boolean printDetail = false; - public void run() { + @Override + public void execute() { final ContinuousRetrievalLogic retrieval = new ContinuousRetrievalLogic(Config.sharedConfig().getDatabase()); TagQueryTermContainer qc = new TagQueryTermContainer(data); List retrievers = new ArrayList<>(); diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/TextRetrievalCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/TextRetrievalCommand.java index 4004d6fe0..d8d605506 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/TextRetrievalCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/TextRetrievalCommand.java @@ -13,7 +13,7 @@ import org.vitrivr.cineast.standalone.util.ContinuousRetrievalLogic; @Command(name = "retrieve-text", description = "Retrieves objects from the database using text as query input.") -public class TextRetrievalCommand implements Runnable { +public class TextRetrievalCommand extends AbstractCineastCommand { @Option(name = {"--text"}, title = "text input", description = "query to be used for retrieval.") private String text; @@ -24,7 +24,8 @@ public class TextRetrievalCommand implements Runnable { @Option(name = {"--detail"}, title = "detailed results", description = "also list detailed results for retrieved segments.") private Boolean printDetail = false; - public void run() { + @Override + public void execute() { final ContinuousRetrievalLogic retrieval = new ContinuousRetrievalLogic(Config.sharedConfig().getDatabase()); System.out.println("Querying for text " + text); TextQueryTermContainer qc = new TextQueryTermContainer(text); diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/ThreeDeeTestCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/ThreeDeeTestCommand.java index cce972386..b5cfaf9d4 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/ThreeDeeTestCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/ThreeDeeTestCommand.java @@ -11,10 +11,10 @@ import org.vitrivr.cineast.core.render.JOGLOffscreenRenderer; @Command(name = "3dtest", description = "Starts a 3D rendering test to check availability of an OpenGL renderer.") -public class ThreeDeeTestCommand implements Runnable { +public class ThreeDeeTestCommand extends AbstractCineastCommand { @Override - public void run() { + public void execute() { System.out.println("Performing 3D test..."); Mesh mesh = new Mesh(2, 6); diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/db/DropTableCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/db/DropTableCommand.java index c86542106..99a82122b 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/db/DropTableCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/db/DropTableCommand.java @@ -5,13 +5,14 @@ import com.github.rvesse.airline.annotations.Option; import com.github.rvesse.airline.annotations.restrictions.Required; import org.vitrivr.cineast.core.db.setup.EntityCreator; +import org.vitrivr.cineast.standalone.cli.AbstractCineastCommand; import org.vitrivr.cineast.standalone.config.Config; /** * A CLI command that can be used to drop a table */ @Command(name = "drop-table", description = "Drop a specific table") -public class DropTableCommand implements Runnable { +public class DropTableCommand extends AbstractCineastCommand { @Option(name = {"--table"}, description = "Name of the table to drop.") @Required @@ -30,7 +31,7 @@ public static void dropTable(String tableName) { } @Override - public void run() { + protected void execute() { dropTable(tableName); } } diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/db/PolyphenyBenchmarkCommand.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/db/PolyphenyBenchmarkCommand.java index 7b6a0eab2..920a466de 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/db/PolyphenyBenchmarkCommand.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/cli/db/PolyphenyBenchmarkCommand.java @@ -19,6 +19,7 @@ import java.util.Properties; import java.util.SplittableRandom; import org.vitrivr.cineast.core.config.ReadableQueryConfig.Distance; +import org.vitrivr.cineast.standalone.cli.AbstractCineastCommand; /** * Runs a Polypheny DB benchmark based on a feature category. @@ -27,7 +28,7 @@ * @version 1.0.0 */ @Command(name = "polypheny-benchmark", description = "Drop a specific table") -public class PolyphenyBenchmarkCommand implements Runnable { +public class PolyphenyBenchmarkCommand extends AbstractCineastCommand { /** @@ -127,7 +128,7 @@ private boolean prepare() { } @Override - public void run() { + protected void execute() { try { /* Run preparation. */ diff --git a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/util/CLI.java b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/util/CLI.java index 296bda868..20b6da3df 100644 --- a/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/util/CLI.java +++ b/cineast-runtime/src/main/java/org/vitrivr/cineast/standalone/util/CLI.java @@ -1,9 +1,14 @@ package org.vitrivr.cineast.standalone.util; import com.github.rvesse.airline.model.CommandMetadata; -import com.github.rvesse.airline.parser.errors.ParseRestrictionViolatedException; +import com.github.rvesse.airline.parser.ParseResult; +import com.github.rvesse.airline.parser.errors.ParseException; import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; import java.util.regex.Matcher; @@ -20,6 +25,7 @@ import org.jline.terminal.TerminalBuilder; import org.jline.utils.AttributedStringBuilder; import org.jline.utils.AttributedStyle; +import org.jline.utils.WriterOutputStream; /** * Helper class that can be used to start an interactive CLI. @@ -67,6 +73,8 @@ public static void start(Class cliClass) { terminal.writer().println(LOGO.replaceAll("\u0085", "\r\n")); terminal.writer().println("Welcome to the interactive Cineast CLI."); + final OutputStream terminalOutput = new WriterOutputStream(terminal.writer(), Charset.defaultCharset()); + try { while (true) { @@ -84,18 +92,24 @@ public static void start(Class cliClass) { /* Try to parse user input. */ try { - final Runnable command = cli.parse(CLI.splitLine(line)); - command.run(); - } catch (ParseRestrictionViolatedException e) { - terminal.writer().println( - new AttributedStringBuilder().style(AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)) - .append("Error: ").append(e.getMessage()).toAnsi() - ); + final String[] args = CLI.splitLine(line); + + ParseResult result = cli.parseWithResult(args); + if (result.wasSuccessful()) { + result.getCommand().run(); + } else { + printlnRed(terminal.writer(), String.format("%d errors ecountered:", result.getErrors().size())); + int i = 1; + for (ParseException e : result.getErrors()) { + printlnRed(terminal.writer(), String.format("%d. %s", i++, e.getMessage())); + } + terminal.writer().println(); + + com.github.rvesse.airline.help.Help.help(cli.getMetadata(), Arrays.asList(args), terminalOutput); + } + } catch (Exception e) { - terminal.writer().println( - new AttributedStringBuilder().style(AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)) - .append("Error: ").append(e.getMessage()).toAnsi() - ); + printlnRed(terminal.writer(), "Error: ", e.getMessage()); } } } catch (IllegalStateException | NoSuchElementException e) { @@ -103,6 +117,17 @@ public static void start(Class cliClass) { } } + private static void printlnRed(PrintWriter pw, String... msg) { + + final AttributedStringBuilder asb = new AttributedStringBuilder(); + asb.style(AttributedStyle.DEFAULT.foreground(AttributedStyle.RED)); + + for (String s : msg) { + asb.append(s); + } + pw.println(asb.toAnsi()); + } + //based on https://stackoverflow.com/questions/366202/regex-for-splitting-a-string-using-space-when-not-surrounded-by-single-or-double/366532 private static String[] splitLine(String line) { if (line == null || line.isEmpty()) {