gluesql-cli 0.13.1

GlueSQL - Open source SQL database engine fully written in Rust with pure functional execution layer, easily swappable storage and web assembly support!
Documentation
use {
    crate::{
        command::{Command, CommandError},
        helper::CliHelper,
        print::Print,
    },
    edit::{edit_file, edit_with_builder, Builder},
    gluesql_core::{
        prelude::Glue,
        store::{GStore, GStoreMut},
    },
    rustyline::{error::ReadlineError, Editor},
    std::{
        error::Error,
        fs::File,
        io::{Read, Result, Write},
        path::Path,
    },
};

pub struct Cli<T, W>
where
    T: GStore + GStoreMut,
    W: Write,
{
    glue: Glue<T>,
    print: Print<W>,
}

impl<T, W> Cli<T, W>
where
    T: GStore + GStoreMut,
    W: Write,
{
    pub fn new(storage: T, output: W) -> Self {
        let glue = Glue::new(storage);
        let print = Print::new(output, None, Default::default());

        Self { glue, print }
    }

    pub fn run(&mut self) -> std::result::Result<(), Box<dyn Error>> {
        macro_rules! println {
            ($($p:tt),*) => ( writeln!(&mut self.print.output, $($p),*)?; )
        }

        self.print.help()?;

        let mut rl = Editor::<CliHelper>::new();
        rl.set_helper(Some(CliHelper::default()));

        loop {
            let line = match rl.readline("gluesql> ") {
                Ok(line) => line,
                Err(ReadlineError::Interrupted) => {
                    println!("^C");
                    continue;
                }
                Err(ReadlineError::Eof) => {
                    println!("bye\n");
                    break;
                }
                Err(e) => {
                    println!("[unknown error] {:?}", e);
                    break;
                }
            };

            let line = line.trim();
            if !(line.starts_with(".edit") || line.starts_with(".run")) {
                rl.add_history_entry(line);
            }

            let command = match Command::parse(line, &self.print.option) {
                Ok(command) => command,
                Err(CommandError::LackOfTable) => {
                    println!("[error] should specify table. eg: .columns TableName\n");
                    continue;
                }
                Err(CommandError::LackOfFile) => {
                    println!("[error] should specify file path.\n");
                    continue;
                }
                Err(CommandError::NotSupported) => {
                    println!("[error] command not supported: {}", line);
                    println!("\n  type .help to list all available commands.\n");
                    continue;
                }
                Err(CommandError::LackOfOption) => {
                    println!("[error] should specify option.\n");
                    continue;
                }
                Err(CommandError::LackOfValue(usage)) => {
                    println!("[error] should specify value.\n{usage}\n");
                    continue;
                }
                Err(CommandError::WrongOption(e)) => {
                    println!("[error] cannot support option: {e}\n");
                    continue;
                }
                Err(CommandError::LackOfSQLHistory) => {
                    println!("[error] Nothing in SQL history to run.\n");
                    continue;
                }
            };

            match command {
                Command::Help => {
                    self.print.help()?;
                    continue;
                }
                Command::Quit => {
                    println!("bye\n");
                    break;
                }
                Command::Execute(sql) => self.execute(sql)?,
                Command::ExecuteFromFile(filename) => {
                    if let Err(e) = self.load(&filename) {
                        println!("[error] {}\n", e);
                    }
                }
                Command::SpoolOn(path) => {
                    self.print.spool_on(path)?;
                }
                Command::SpoolOff => {
                    self.print.spool_off();
                }
                Command::Set(option) => self.print.set_option(option),
                Command::Show(option) => self.print.show_option(option)?,
                Command::Edit(file_name) => {
                    match file_name {
                        Some(file_name) => {
                            let file = Path::new(&file_name);
                            edit_file(file)?;
                        }
                        None => {
                            let mut builder = Builder::new();
                            builder.prefix("Glue_").suffix(".sql");
                            let last = rl.history().last().map_or_else(|| "", String::as_str);
                            let edited = edit_with_builder(last, &builder)?;
                            rl.add_history_entry(edited);
                        }
                    };
                }
                Command::Run => {
                    let sql = rl.history().last().ok_or(CommandError::LackOfSQLHistory);

                    match sql {
                        Ok(sql) => {
                            self.execute(sql)?;
                        }
                        Err(e) => {
                            println!("[error] {}\n", e);
                        }
                    };
                }
            }
        }

        Ok(())
    }

    fn execute(&mut self, sql: impl AsRef<str>) -> Result<()> {
        match self.glue.execute(sql) {
            Ok(payloads) => self.print.payloads(&payloads)?,
            Err(e) => {
                println!("[error] {}\n", e);
            }
        };

        Ok(())
    }

    pub fn load<P: AsRef<Path>>(&mut self, filename: P) -> Result<()> {
        let mut sqls = String::new();
        File::open(filename)?.read_to_string(&mut sqls)?;
        for sql in sqls.split(';').filter(|sql| !sql.trim().is_empty()) {
            match self.glue.execute(sql) {
                Ok(payloads) => self.print.payloads(&payloads)?,
                Err(e) => {
                    println!("[error] {}\n", e);
                    break;
                }
            }
        }

        Ok(())
    }
}