use std::{collections::VecDeque, io::ErrorKind, path::Path};
use ic_host_text::escape::terminal_text;
use rustyline::{DefaultEditor, error::ReadlineError};
use crate::shell::{
ShellConfig, execute_sql,
input::{self, ShellInput, read_statement},
render,
};
pub(super) fn run_interactive_shell(config: &ShellConfig) -> Result<(), String> {
let mut editor = prepare_editor(config.history_file.as_path())?;
let mut pending_sql = VecDeque::<String>::new();
let mut partial_statement = String::new();
eprintln!(
"{}",
interactive_start_message(config.environment.as_str(), config.canister.as_str())
);
loop {
match read_statement(&mut editor, &mut pending_sql, &mut partial_statement)? {
ShellInput::Exit => break,
ShellInput::Help => {
print_successful_command_output(input::shell_help_text());
}
ShellInput::Sql(sql) => {
match execute_sql(
config.environment.as_str(),
config.canister.as_str(),
sql.as_str(),
) {
Ok(output) => {
print_successful_command_output(output.as_str());
}
Err(err) => println!("ERROR: {err}"),
}
if let Err(error) =
record_history_entry(&mut editor, config.history_file.as_path(), sql.as_str())
{
eprintln!(
"WARNING: could not save SQL history '{}': {}",
terminal_text(&config.history_file.display().to_string()),
terminal_text(&error),
);
}
}
}
}
Ok(())
}
fn prepare_editor(history_file: &Path) -> Result<DefaultEditor, String> {
let mut editor = DefaultEditor::new().map_err(|err| err.to_string())?;
match editor.load_history(history_file) {
Ok(()) => {}
Err(ReadlineError::Io(error)) if error.kind() == ErrorKind::NotFound => {}
Err(error) => eprintln!(
"WARNING: could not load SQL history '{}': {}",
terminal_text(&history_file.display().to_string()),
terminal_text(&error.to_string()),
),
}
Ok(editor)
}
fn record_history_entry(
editor: &mut DefaultEditor,
history_file: &Path,
sql: &str,
) -> Result<(), String> {
editor
.add_history_entry(sql)
.map_err(|err| err.to_string())?;
if let Some(parent) = history_file
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
{
std::fs::create_dir_all(parent).map_err(|err| err.to_string())?;
}
editor
.append_history(history_file)
.map_err(|err| err.to_string())
}
fn print_successful_command_output(output: &str) {
print!("{}", render::finalize_successful_command_output(output));
}
pub(super) fn interactive_start_message(environment: &str, canister: &str) -> String {
format!(
"[icydb sql] interactive mode on '{}:{}' (terminate statements with ';', use \\q, exit, or Ctrl-D to quit)",
terminal_text(environment),
terminal_text(canister),
)
}