use clap::{ArgMatches, Command};
use colored::*;
use rustyline::error::ReadlineError;
use rustyline::Editor;
use crate::globals::{server_host, server_port};
use crate::utils::connection::create_client;
use crate::utils::display::print_unicode_box;
use crate::utils::query::{execute_query, QueryResult};
use crate::utils::server::check_and_start_server;
pub fn command() -> Command {
Command::new("shell").about("Launch the interactive shell")
}
pub fn execute(_matches: &ArgMatches) {
print_unicode_box(
"Launching interactive shell...",
crate::utils::display::BorderColor::Cyan,
);
let host = server_host();
let port = server_port();
check_and_start_server();
let mut stackql_client_conn = create_client();
println!("Type 'exit' to quit the shell");
println!("---");
let mut rl = Editor::<()>::new().unwrap();
let _ = rl.load_history("stackql_history.txt");
let mut query_buffer = String::new();
loop {
let prompt = if query_buffer.is_empty() {
format!("stackql ({}:{})=> ", host, port)
} else {
"... ".to_string()
};
let readline = rl.readline(&prompt);
match readline {
Ok(line) => {
let input = line.trim();
if input.eq_ignore_ascii_case("exit") || input.eq_ignore_ascii_case("quit") {
println!("Goodbye");
break;
}
query_buffer.push_str(input);
query_buffer.push(' ');
if input.ends_with(';') {
let normalized_input = normalize_query(&query_buffer);
rl.add_history_entry(&normalized_input);
match execute_query(&normalized_input, &mut stackql_client_conn) {
Ok(result) => match result {
QueryResult::Data {
columns,
rows,
notices,
} => {
print_table(columns, rows);
if !notices.is_empty() {
println!("\n{}", "Notices:".yellow().bold());
for notice in notices {
for line in notice.lines() {
println!(" {}", line.yellow());
}
}
}
}
QueryResult::Command(cmd) => {
println!("{}", cmd.green());
}
QueryResult::Empty => {
println!("{}", "Query executed successfully. No results.".green());
}
},
Err(e) => {
eprintln!("{}", format!("Error: {}", e).red());
}
}
query_buffer.clear();
}
}
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
query_buffer.clear();
continue;
}
Err(ReadlineError::Eof) => {
println!("Goodbye");
break;
}
Err(err) => {
eprintln!("Error: {:?}", err);
break;
}
}
}
let _ = rl.save_history("stackql_history.txt");
}
fn normalize_query(input: &str) -> String {
input
.split('\n')
.map(|line| line.trim())
.filter(|line| !line.is_empty())
.collect::<Vec<_>>()
.join(" ")
}
fn print_table(
columns: Vec<crate::utils::query::QueryResultColumn>,
rows: Vec<crate::utils::query::QueryResultRow>,
) {
let mut column_widths: Vec<usize> = columns.iter().map(|col| col.name.len()).collect();
for row in &rows {
for (i, value) in row.values.iter().enumerate() {
if i < column_widths.len() && value.len() > column_widths[i] {
column_widths[i] = value.len();
}
}
}
print!("+");
for width in &column_widths {
print!("{}+", "-".repeat(width + 2));
}
println!();
print!("|");
for (i, col) in columns.iter().enumerate() {
print!(
" {}{} |",
col.name,
" ".repeat(column_widths[i] - col.name.len())
);
}
println!();
print!("+");
for width in &column_widths {
print!("{}+", "-".repeat(width + 2));
}
println!();
let row_count = rows.len();
for row in rows {
print!("|");
for (i, value) in row.values.iter().enumerate() {
if i < column_widths.len() {
print!(" {}{} |", value, " ".repeat(column_widths[i] - value.len()));
}
}
println!();
print!("+");
for width in &column_widths {
print!("{}+", "-".repeat(width + 2));
}
println!();
}
if row_count > 0 {
println!("{} rows returned", row_count);
}
}