3737import io .prometheus .metrics .instrumentation .jvm .JvmMetrics ;
3838import java .io .IOException ;
3939import java .net .URISyntaxException ;
40+ import java .nio .file .Path ;
4041import java .util .ArrayList ;
4142import java .util .Arrays ;
4243import java .util .Base64 ;
4344import java .util .HashSet ;
4445import java .util .List ;
4546import java .util .Scanner ;
47+ import java .util .function .Supplier ;
4648import java .util .stream .Collectors ;
4749import org .apache .curator .shaded .com .google .common .net .HostAndPort ;
4850import org .apache .iceberg .CatalogProperties ;
4951import org .apache .iceberg .catalog .Namespace ;
5052import org .apache .iceberg .catalog .TableIdentifier ;
5153import org .apache .iceberg .rest .RESTCatalog ;
5254import org .eclipse .jetty .server .Server ;
55+ import org .jline .console .SystemRegistry ;
56+ import org .jline .console .impl .SystemRegistryImpl ;
57+ import org .jline .reader .EndOfFileException ;
58+ import org .jline .reader .LineReader ;
59+ import org .jline .reader .LineReaderBuilder ;
60+ import org .jline .reader .Parser ;
61+ import org .jline .reader .UserInterruptException ;
62+ import org .jline .reader .impl .DefaultParser ;
63+ import org .jline .terminal .Terminal ;
64+ import org .jline .terminal .TerminalBuilder ;
5365import org .slf4j .Logger ;
5466import org .slf4j .LoggerFactory ;
5567import picocli .AutoComplete ;
5668import picocli .CommandLine ;
69+ import picocli .shell .jline3 .PicocliCommands ;
5770
5871@ CommandLine .Command (
5972 name = "ice" ,
@@ -66,6 +79,8 @@ public final class Main {
6679
6780 private static final Logger logger = LoggerFactory .getLogger (Main .class );
6881
82+ private boolean inShellMode = false ;
83+
6984 @ CommandLine .Option (
7085 names = {"-c" , "--config" },
7186 description = "/path/to/config.yaml ($CWD/.ice.yaml by default)" ,
@@ -835,6 +850,83 @@ private RESTCatalog loadCatalog(String configFile) throws IOException {
835850 return catalog ;
836851 }
837852
853+ @ CommandLine .Command (
854+ name = "shell" ,
855+ description = "Start interactive shell with tab completion." ,
856+ mixinStandardHelpOptions = true )
857+ void shell () throws IOException {
858+ if (inShellMode ) {
859+ logger .warn ("Already in shell mode" );
860+ return ;
861+ }
862+ inShellMode = true ;
863+
864+ final String savedConfigFile = this .configFile ;
865+ final String savedLogLevel = this .logLevel ;
866+ final boolean savedInsecure = this .insecure ;
867+
868+ Supplier <Path > workDir = () -> Path .of (System .getProperty ("user.dir" ));
869+
870+ CommandLine cmd = new CommandLine (this );
871+ cmd .getSubcommands ().remove ("shell" );
872+
873+ cmd .setExecutionStrategy (
874+ parseResult -> {
875+ Main main = (Main ) parseResult .commandSpec ().root ().userObject ();
876+ if (!parseResult .hasMatchedOption ("--config" )) {
877+ main .configFile = savedConfigFile ;
878+ }
879+ if (!parseResult .hasMatchedOption ("--log-level" )) {
880+ main .logLevel = savedLogLevel ;
881+ }
882+ if (!parseResult .hasMatchedOption ("--insecure" )) {
883+ main .insecure = savedInsecure ;
884+ }
885+ ch .qos .logback .classic .Logger rootLogger =
886+ (ch .qos .logback .classic .Logger )
887+ LoggerFactory .getLogger (ch .qos .logback .classic .Logger .ROOT_LOGGER_NAME );
888+ rootLogger .setLevel (Level .toLevel (main .logLevel .toUpperCase (), Level .INFO ));
889+ return new CommandLine .RunLast ().execute (parseResult );
890+ });
891+ cmd .setExecutionExceptionHandler (
892+ (Exception ex , CommandLine self , CommandLine .ParseResult res ) -> {
893+ logger .error ("Error" , ex );
894+ return 1 ;
895+ });
896+
897+ PicocliCommands picocliCommands = new PicocliCommands (cmd );
898+
899+ try (Terminal terminal = TerminalBuilder .builder ().build ()) {
900+ Parser parser = new DefaultParser ();
901+ SystemRegistry systemRegistry = new SystemRegistryImpl (parser , terminal , workDir , null );
902+ systemRegistry .setCommandRegistries (picocliCommands );
903+
904+ LineReader reader =
905+ LineReaderBuilder .builder ()
906+ .terminal (terminal )
907+ .completer (systemRegistry .completer ())
908+ .parser (parser )
909+ .variable (LineReader .LIST_MAX , 50 )
910+ .build ();
911+
912+ String prompt = "ice> " ;
913+
914+ while (true ) {
915+ try {
916+ systemRegistry .cleanUp ();
917+ String line = reader .readLine (prompt );
918+ systemRegistry .execute (line );
919+ } catch (UserInterruptException e ) {
920+ System .exit (0 );
921+ } catch (EndOfFileException e ) {
922+ return ;
923+ } catch (Exception e ) {
924+ systemRegistry .trace (e );
925+ }
926+ }
927+ }
928+ }
929+
838930 public static void main (String [] args ) {
839931 CommandLine cmd = new CommandLine (new Main ());
840932 CommandLine .IExecutionStrategy defaultExecutionStrategy = cmd .getExecutionStrategy ();
0 commit comments