worm_hole 1.5.0

CLI tool to easily jump between directories
use crate::{
	alias::AliasList,
	error::{WHError, WHResult},
};
use sqlite::{Connection, State};

pub struct Database {
	connection: Connection,
}

impl Database {
	pub fn new(path: &str) -> WHResult<Database> {
		let connection = Connection::open(path);
		match connection {
			Ok(connection) => Ok(Database { connection }),
			Err(_) => Err(WHError::DatabaseConnectionError(path.to_string())),
		}
	}

	pub fn init(&self) {
		self.connection
			.execute(
				"create table if not exists aliases (
                    id integer primary key,
                    alias text not null,
                    path text not null,
                    unique(alias)
                )",
			)
			.unwrap();
	}

	fn run_statement<T: sqlite::Bindable>(&self, query: &str, bind: T) -> sqlite::Statement {
		let mut statement = self.connection.prepare(query).unwrap();
		statement.bind::<T>(bind).unwrap();
		statement
	}

	fn run_querry(&self, query: &str) -> sqlite::Statement {
		self.connection.prepare(query).unwrap()
	}

	pub fn add_alias(&self, alias: &str, path: &str) -> WHResult<()> {
		let mut statement = self.run_statement::<&[(&str, &str)]>(
			"insert into aliases (alias, path) values (:alias, :path)",
			&[(":alias", alias), (":path", path)],
		);
		match statement.next() {
			Ok(_) => Ok(()),
			Err(_) => Err(WHError::AliasAlreadyExists(alias.to_string())),
		}
	}

	pub fn edit_alias(&self, alias: &str, paths: &str) -> WHResult<()> {
		self.get_alias(alias)?; // Check if alias exists
		let mut statement = self.run_statement::<&[(&str, &str)]>(
			"update aliases set path = :path where alias = :alias",
			&[(":alias", alias), (":path", paths)],
		);
		statement.next().unwrap();
		Ok(())
	}

	pub fn rename_alias(&self, old_alias: &str, new_alias: &str) -> WHResult<()> {
		self.get_alias(old_alias)?; // Check if alias exists
		let mut statement = self.run_statement::<&[(&str, &str)]>(
			"update aliases set alias = :new_alias where alias = :old_alias",
			&[(":old_alias", old_alias), (":new_alias", new_alias)],
		);
		match statement.next() {
			Ok(_) => Ok(()),
			Err(_) => Err(WHError::AliasAlreadyExists(new_alias.to_string())),
		}
	}

	pub fn remove_alias(&self, alias: &str) -> WHResult<()> {
		let mut statement = self.run_statement::<(&str, &str)>("delete from aliases where alias = :alias", (":alias", alias));
		statement.next().unwrap();
		Ok(())
	}

	pub fn get_all_aliases(&self) -> WHResult<AliasList> {
		let mut statement = self.run_querry("select alias, path from aliases order by alias");
		let mut aliases = AliasList::new();
		while let Ok(State::Row) = statement.next() {
			aliases.add(
				(
					statement.read::<String, _>("alias").unwrap(),
					statement.read::<String, _>("path").unwrap(),
				)
					.into(),
			);
		}
		Ok(aliases)
	}

	pub fn get_alias(&self, alias: &str) -> WHResult<String> {
		let mut statement = self.run_statement::<(&str, &str)>("select path from aliases where alias = :alias", (":alias", alias));
		if let Ok(State::Row) = statement.next() {
			Ok(statement.read::<String, _>("path").unwrap())
		} else {
			Err(WHError::AliasNotFound(alias.to_string()))
		}
	}

	pub fn get_aliases_matching(&self, pattern: &str) -> WHResult<AliasList> {
		let mut statement = self.run_statement::<(&str, &str)>(
			"select alias, path from aliases where path like :pattern order by alias",
			(":pattern", pattern),
		);
		let mut aliases = AliasList::new();
		if let Ok(State::Done) = statement.next() {
			return Err(WHError::PatternNotMatch(pattern.to_string()));
		}
		let _ = statement.reset();
		while let Ok(State::Row) = statement.next() {
			aliases.add(
				(
					statement.read::<String, _>("alias").unwrap(),
					statement.read::<String, _>("path").unwrap(),
				)
					.into(),
			);
		}
		Ok(aliases)
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use std::fs::remove_file;

	#[test]
	fn test_database() {
		let path = ".db_test.db";
		if std::path::Path::new(path).exists() {
			remove_file(path).unwrap();
		}
		let database = Database::new(path).unwrap();
		database.init();
		// Add
		database.add_alias("alias", "path").unwrap();
		assert_eq!(database.get_alias("alias").unwrap(), "path");
		// Edit
		database.edit_alias("alias", "new_path").unwrap();
		assert_eq!(database.get_alias("alias").unwrap(), "new_path");
		// Rename
		database.rename_alias("alias", "new_alias").unwrap();
		assert_eq!(database.get_alias("new_alias").unwrap(), "new_path");
		// Get all
		database.add_alias("alias2", "path2").unwrap();
		let aliases = database.get_all_aliases().unwrap();
		assert_eq!(format!("{}", aliases), "alias2    -> path2\nnew_alias -> new_path\n");
		// Get matching
		let aliases = database.get_aliases_matching("new%").unwrap();
		assert_eq!(format!("{}", aliases), "new_alias -> new_path\n");
		// Remove
		database.remove_alias("new_alias").unwrap();
		assert!(database.get_alias("new_alias").is_err());
	}
}