sqlite-rs 0.3.7

SQLite reader in pure Rust
Documentation
mod dbinfo;
mod help;
mod open;
mod sql;
mod traits;

use self::{dbinfo::ReplDbInfo, help::ReplHelp, open::ReplOpen};
use super::{
  cli::Cli,
  result::{SqliteCliError, SqliteCliResult},
};
use sqlite_rs::{io::SqliteIoMode, SqliteConnection};

#[derive(Debug)]
pub(crate) struct SqliteCliRepl {
  is_tty: bool,
  cli: Cli,
  conn: SqliteConnection,
}

impl SqliteCliRepl {
  pub(crate) fn start(cli: Cli) -> SqliteCliResult<()> {
    let mut repl = Self::new(cli)?;
    if repl.is_tty {
      repl.main_loop()
    } else {
      repl.run_from_pipe()
    }
  }
  fn new(cli: Cli) -> SqliteCliResult<Self> {
    use std::io::stdin;
    use std::io::IsTerminal;

    let conn = match cli.database_file() {
      Some(file_path) => {
        SqliteConnection::open(format!("sqlite://{}", file_path.as_str()))?
      }
      None => SqliteConnection::open(":memory:")?,
    };

    let is_tty = stdin().is_terminal();

    Ok(Self { cli, conn, is_tty })
  }
  fn run_from_pipe(&mut self) -> SqliteCliResult<()> {
    use std::io;
    let mut input = String::new();

    io::stdin().read_line(&mut input)?;

    let normalized_input = input.trim();
    if normalized_input.starts_with('.') {
      match normalized_input {
        ".quit" => (),
        s => self.internal_command(s)?,
      };
    } else {
      self::sql::run(normalized_input)?;
    }
    Ok(())
  }
  fn main_loop(&mut self) -> SqliteCliResult<()> {
    use std::io;
    use std::io::Write;
    println!(
      "{} v{} - {}",
      env!("CARGO_PKG_NAME"),
      env!("CARGO_PKG_VERSION"),
      env!("SQLITERS_BUILT_AT")
    );
    println!(r#"Enter ".help" for usage hints."#);
    if *self.conn.runtime().pager().io().mode() == SqliteIoMode::InMemory {
      println!("Connected to a transient in-memory database.");
      println!(r#"Use ".open FILENAME" to reopen on a persistent database."#);
    }
    let mut is_repl_running = true;
    while is_repl_running {
      let mut input = String::new();

      print!("sqlite-rs> ");

      io::stdout().flush()?;
      io::stdin().read_line(&mut input)?;

      let normalized_input = input.trim();
      if normalized_input.starts_with('.') {
        match normalized_input {
          ".quit" => is_repl_running = false,
          s => self.internal_command(s)?,
        };
      } else {
        self::sql::run(normalized_input)?;
      }
    }
    Ok(())
  }

  fn internal_command(
    &mut self,
    normalized_input: impl AsRef<str>,
  ) -> SqliteCliResult<()> {
    let mut line = normalized_input.as_ref().split_ascii_whitespace();
    let command = line.next().ok_or(SqliteCliError::Custom(format!(
      "Impossible state in {} at line {}",
      file!(),
      line!()
    )))?;
    let maybe_arg1 = line.next().map(|s| s.to_owned());

    match command {
      ".help" => ReplHelp::run(maybe_arg1)?,
      ".dbinfo" => ReplDbInfo::run(&mut self.conn)?,
      ".open" => {
        let new_conn = ReplOpen::run(maybe_arg1)?;
        self.conn = new_conn;
      }

      s => println!(
        r#"Error: unknown command or invalid arguments:  "{s}". Enter ".help" for help"#
      ),
    };
    Ok(())
  }
}