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