diff --git a/ice/pom.xml b/ice/pom.xml index 2556c56..b200694 100644 --- a/ice/pom.xml +++ b/ice/pom.xml @@ -512,6 +512,16 @@ picocli ${picocli.version} + + info.picocli + picocli-shell-jline3 + ${picocli.version} + + + org.jline + jline + ${jline.version} + com.fasterxml.jackson.core diff --git a/ice/src/main/java/com/altinity/ice/cli/Main.java b/ice/src/main/java/com/altinity/ice/cli/Main.java index b8a8b30..7856c98 100644 --- a/ice/src/main/java/com/altinity/ice/cli/Main.java +++ b/ice/src/main/java/com/altinity/ice/cli/Main.java @@ -37,12 +37,14 @@ import io.prometheus.metrics.instrumentation.jvm.JvmMetrics; import java.io.IOException; import java.net.URISyntaxException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.HashSet; import java.util.List; import java.util.Scanner; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.curator.shaded.com.google.common.net.HostAndPort; import org.apache.iceberg.CatalogProperties; @@ -50,10 +52,21 @@ import org.apache.iceberg.catalog.TableIdentifier; import org.apache.iceberg.rest.RESTCatalog; import org.eclipse.jetty.server.Server; +import org.jline.console.SystemRegistry; +import org.jline.console.impl.SystemRegistryImpl; +import org.jline.reader.EndOfFileException; +import org.jline.reader.LineReader; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.Parser; +import org.jline.reader.UserInterruptException; +import org.jline.reader.impl.DefaultParser; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import picocli.AutoComplete; import picocli.CommandLine; +import picocli.shell.jline3.PicocliCommands; @CommandLine.Command( name = "ice", @@ -66,6 +79,8 @@ public final class Main { private static final Logger logger = LoggerFactory.getLogger(Main.class); + private boolean inShellMode = false; + @CommandLine.Option( names = {"-c", "--config"}, description = "/path/to/config.yaml ($CWD/.ice.yaml by default)", @@ -835,6 +850,83 @@ private RESTCatalog loadCatalog(String configFile) throws IOException { return catalog; } + @CommandLine.Command( + name = "shell", + description = "Start interactive shell with tab completion.", + mixinStandardHelpOptions = true) + void shell() throws IOException { + if (inShellMode) { + logger.warn("Already in shell mode"); + return; + } + inShellMode = true; + + final String savedConfigFile = this.configFile; + final String savedLogLevel = this.logLevel; + final boolean savedInsecure = this.insecure; + + Supplier workDir = () -> Path.of(System.getProperty("user.dir")); + + CommandLine cmd = new CommandLine(this); + cmd.getSubcommands().remove("shell"); + + cmd.setExecutionStrategy( + parseResult -> { + Main main = (Main) parseResult.commandSpec().root().userObject(); + if (!parseResult.hasMatchedOption("--config")) { + main.configFile = savedConfigFile; + } + if (!parseResult.hasMatchedOption("--log-level")) { + main.logLevel = savedLogLevel; + } + if (!parseResult.hasMatchedOption("--insecure")) { + main.insecure = savedInsecure; + } + ch.qos.logback.classic.Logger rootLogger = + (ch.qos.logback.classic.Logger) + LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME); + rootLogger.setLevel(Level.toLevel(main.logLevel.toUpperCase(), Level.INFO)); + return new CommandLine.RunLast().execute(parseResult); + }); + cmd.setExecutionExceptionHandler( + (Exception ex, CommandLine self, CommandLine.ParseResult res) -> { + logger.error("Error", ex); + return 1; + }); + + PicocliCommands picocliCommands = new PicocliCommands(cmd); + + try (Terminal terminal = TerminalBuilder.builder().build()) { + Parser parser = new DefaultParser(); + SystemRegistry systemRegistry = new SystemRegistryImpl(parser, terminal, workDir, null); + systemRegistry.setCommandRegistries(picocliCommands); + + LineReader reader = + LineReaderBuilder.builder() + .terminal(terminal) + .completer(systemRegistry.completer()) + .parser(parser) + .variable(LineReader.LIST_MAX, 50) + .build(); + + String prompt = "ice> "; + + while (true) { + try { + systemRegistry.cleanUp(); + String line = reader.readLine(prompt); + systemRegistry.execute(line); + } catch (UserInterruptException e) { + System.exit(0); + } catch (EndOfFileException e) { + return; + } catch (Exception e) { + systemRegistry.trace(e); + } + } + } + } + public static void main(String[] args) { CommandLine cmd = new CommandLine(new Main()); CommandLine.IExecutionStrategy defaultExecutionStrategy = cmd.getExecutionStrategy(); diff --git a/pom.xml b/pom.xml index cbcab05..e84e636 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,7 @@ 3.27.2 3.2.0 + 3.26.1