chest-rs 0.2.0

A multiplatform CLI command organizer
Documentation
use crate::{dirs::db_path, AppResult};

use super::shell_command::ShellCommand;

mod db;

use async_std::task;
pub use db::Database;
use log::{debug, error};
use sqlx::{
    migrate,
    sqlite::{SqliteConnectOptions, SqlitePoolOptions},
    Error, SqlitePool,
};

pub trait Engine: Sized {
    fn init() -> AppResult<Self>;
    fn search_commands(&self, search_term: &str) -> Vec<ShellCommand>;
    fn search_commands_strict(&self, name: &str) -> Option<ShellCommand>;
    fn get_command(&self, name: &str) -> Option<ShellCommand>;
    fn add_command(&self, command: ShellCommand) -> AppResult<()>;
    fn update_command(&self, command: ShellCommand) -> AppResult<()>;
    fn remove_command(&self, name: &str) -> AppResult<()>;
}

fn create_database_connection() -> Result<SqlitePool, Error> {
    task::block_on(async {
        let options = SqliteConnectOptions::new()
            .filename(db_path())
            .create_if_missing(true)
            .optimize_on_close(true, None);
        let connection = SqlitePoolOptions::new()
            .connect_with(options)
            .await
            .map_err(|error| {
                error!(
                    "Unable to open or create database at path : {}",
                    db_path().display()
                );
                debug!("{}", error);
                error
            })?;

        migrate!().run(&connection).await.map_err(|error| {
            error!("Unable to migrate database");
            debug!("{}", error);
            error
        })?;

        Ok(connection)
    })
}

fn get_all_commands(db: &SqlitePool) -> Vec<ShellCommand> {
    task::block_on(async {
        sqlx::query_as::<_, ShellCommand>("SELECT * FROM Commands ORDER BY name")
            .fetch_all(db)
            .await
            .unwrap_or_else(|error| {
                error!("Error while searching for commands without search_term",);
                debug!("{}", error);
                Vec::new()
            })
    })
}

fn get_filtered_commands(db: &SqlitePool, search_term: &str) -> Vec<ShellCommand> {
    task::block_on(async {
        sqlx::query_as::<_, ShellCommand>(
            "SELECT * FROM Commands WHERE Commands MATCH $1 ORDER BY rank",
        )
        .bind(search_term)
        .fetch_all(db)
        .await
        .unwrap_or_else(|error| {
            error!(
                "Error while searching for commands with search_term {}",
                search_term
            );
            debug!("{}", error);
            Vec::new()
        })
    })
}

fn get_filtered_commands_name(db: &SqlitePool, name: &str) -> Option<ShellCommand> {
    task::block_on(async {
        sqlx::query_as::<_, ShellCommand>(
            "SELECT * FROM Commands WHERE name MATCH $1 ORDER BY rank",
        )
        .bind(name)
        .fetch_optional(db)
        .await
        .unwrap_or_else(|error| {
            error!("Error while searching for commands with name {}", name);
            debug!("{}", error);
            None
        })
    })
}

fn get_command(db: &SqlitePool, name: &str) -> Option<ShellCommand> {
    task::block_on(async {
        sqlx::query_as::<_, ShellCommand>("SELECT * FROM Commands WHERE name = $1")
            .bind(name)
            .fetch_optional(db)
            .await
            .unwrap_or_else(|error| {
                error!("Error while searching for a command with name {}", name);
                debug!("{}", error);
                None
            })
    })
}

fn add_command(db: &SqlitePool, command: ShellCommand) -> AppResult<()> {
    Ok(task::block_on(async {
        sqlx::query("INSERT INTO Commands (name, command_text, description) VALUES ($1, $2, $3);")
            .bind(command.name)
            .bind(command.command_text)
            .bind(command.description)
            .execute(db)
            .await
            .map(|_ok| ())
    })?)
}

fn update_command(db: &SqlitePool, command: ShellCommand) -> AppResult<()> {
    Ok(task::block_on(async {
        sqlx::query("UPDATE Commands SET command_text=$2, description=$3 WHERE name=$1;")
            .bind(command.name)
            .bind(command.command_text)
            .bind(command.description)
            .execute(db)
            .await
            .map(|_ok| ())
    })?)
}

fn remove_command(db: &SqlitePool, name: &str) -> AppResult<()> {
    Ok(task::block_on(async {
        sqlx::query("DELETE FROM Commands WHERE name = $1")
            .bind(name)
            .execute(db)
            .await
            .map(|_ok| ())
    })?)
}