use std::sync::Arc;
use tracing::Level;
mod cli;
mod config;
mod connection;
mod error;
mod executor;
mod formatter;
mod parser;
mod repl;
use cli::CliInterface;
use connection::ConnectionManager;
use error::Result;
use executor::{CommandRouter, ExecutionContext};
use formatter::Formatter;
use repl::{ReplEngine, SharedState};
#[tokio::main]
async fn main() {
if let Err(e) = run().await {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}
async fn run() -> Result<()> {
let cli = CliInterface::new()?;
initialize_logging(&cli);
if cli.handle_subcommand().await? {
return Ok(());
}
cli.print_banner();
run_interactive_mode(&cli).await
}
async fn run_interactive_mode(cli: &CliInterface) -> Result<()> {
let (conn_manager, server_version) = setup_connection(cli).await?;
let shared_state = initialize_shared_state(cli, server_version)?;
let config_path = cli.config_path().map(|p| p.to_path_buf());
let exec_context =
create_execution_context(conn_manager, shared_state.clone(), config_path).await?;
let mut repl = create_repl_engine(cli, shared_state.clone(), exec_context.clone())?;
run_repl_loop(cli, &mut repl, &exec_context, &shared_state).await?;
println!("Goodbye!");
Ok(())
}
async fn setup_connection(cli: &CliInterface) -> Result<(ConnectionManager, Option<String>)> {
let uri = cli.get_connection_uri();
let mut conn_manager = ConnectionManager::new(uri, cli.config().connection.clone());
if cli.args().no_connect {
return Ok((conn_manager, None));
}
conn_manager.connect().await?;
let version = conn_manager.get_client().ok().and_then(|client| {
futures::executor::block_on(conn_manager.get_server_version(client)).ok()
});
if let Some(ref ver) = version {
cli.print_connection_info(ver);
}
Ok((conn_manager, version))
}
fn initialize_shared_state(
cli: &CliInterface,
server_version: Option<String>,
) -> Result<SharedState> {
let database = cli.get_database();
let mut shared_state = SharedState::with_config(database, &cli.config().display);
shared_state.set_connected(server_version);
if cli.args().no_color {
shared_state.set_color_enabled(false);
}
Ok(shared_state)
}
async fn create_execution_context(
conn_manager: ConnectionManager,
shared_state: SharedState,
config_path: Option<std::path::PathBuf>,
) -> Result<ExecutionContext> {
let exec_context = ExecutionContext::with_config_path(conn_manager, shared_state, config_path);
CommandRouter::new(exec_context.clone()).await?;
Ok(exec_context)
}
fn create_repl_engine(
cli: &CliInterface,
shared_state: SharedState,
exec_context: ExecutionContext,
) -> Result<ReplEngine> {
ReplEngine::new(
shared_state,
cli.config().history.clone(),
cli.config().display.syntax_highlighting,
Some(Arc::new(exec_context)),
)
}
async fn run_repl_loop(
cli: &CliInterface,
repl: &mut ReplEngine,
exec_context: &ExecutionContext,
shared_state: &SharedState,
) -> Result<()> {
while repl.is_running() {
let mut context_clone = exec_context.clone();
context_clone.reset_cancel_token();
let input = match repl.read_line()? {
Some(line) if !line.trim().is_empty() => line,
Some(_) => continue,
None => break,
};
let command = match repl.process_input(&input) {
Ok(cmd) => cmd,
Err(e) => {
eprintln!("{}", e);
continue;
}
};
if matches!(command, parser::Command::Exit) {
break;
}
let cancel_token = context_clone.get_cancel_token();
let cancel_token_clone = cancel_token.clone();
let ctrl_c_handle = tokio::spawn(async move {
match tokio::signal::ctrl_c().await {
Ok(()) => {
cancel_token_clone.cancel();
}
Err(err) => {
eprintln!("Failed to listen for Ctrl+C: {}", err);
}
}
});
execute_and_display(cli, &context_clone, shared_state, command).await;
ctrl_c_handle.abort();
}
Ok(())
}
async fn execute_and_display(
cli: &CliInterface,
exec_context: &ExecutionContext,
shared_state: &SharedState,
command: parser::Command,
) {
let is_config_cmd = matches!(command, parser::Command::Config(_));
let is_execute_named_query = matches!(
command,
parser::Command::Config(parser::ConfigCommand::ExecuteNamedQuery { .. })
);
match exec_context.execute(command).await {
Ok(result) => {
if is_execute_named_query {
display_result(cli, shared_state, &result);
} else if is_config_cmd {
if let executor::ResultData::Message(msg) = &result.data {
println!("{}", msg);
}
} else {
display_result(cli, shared_state, &result);
}
}
Err(e) => eprintln!("{}", e),
}
}
fn display_result(
cli: &CliInterface,
shared_state: &SharedState,
result: &executor::ExecutionResult,
) {
let mut display_config = cli.config().display.clone();
display_config.format = shared_state.get_format();
display_config.color_output = shared_state.get_color_enabled();
let formatter = Formatter::from_config(&display_config);
match formatter.format(result) {
Ok(output) => println!("{}", output),
Err(e) => eprintln!("Format error: {}", e),
}
}
fn initialize_logging(cli: &CliInterface) {
let level = if cli.args().very_verbose {
Level::TRACE
} else if cli.args().verbose {
Level::DEBUG
} else {
cli.config().logging.level.to_tracing_level()
};
let subscriber = tracing_subscriber::fmt()
.with_max_level(level)
.with_target(false);
if cli.config().logging.timestamps {
subscriber.init();
} else {
subscriber.without_time().init();
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_module_structure() {
assert!(true);
}
}