worm_hole 1.1.0

CLI tool to easily jump between directories
Documentation
use crate::{db::Database, error::WHResult, path::FilePath};
use clap::Parser;
use std::str::FromStr;
use string_builder::Builder as StringBuilder;

#[derive(Parser, Debug)]
pub struct Init {
	/// The shell to generate the init code for
	shell: Shell,

	// All these flags will be used to set the commands to write in the console to call worm_hole
	// Only the worm_hole and cd command has a default value
	/// The command to use to call worm_hole
	#[clap(long, default_value = "wh")]
	worm_hole: String,
	/// The command to use to change directory with worm_hole
	#[clap(long, default_value = "whcd")]
	cd: String,
	/// The command to use to add a new path to worm_hole
	#[clap(long)]
	add: Option<String>,
	/// The command to use to remove a path from worm_hole
	#[clap(long)]
	remove: Option<String>,
	/// The command to use to list all the paths in worm_hole
	#[clap(long)]
	list: Option<String>,
	/// The command to use to search for all paths containing a given pattern in worm_hole
	#[clap(long)]
	search: Option<String>,
	/// The command to use to query a path in worm_hole
	#[clap(long)]
	query: Option<String>,
	/// The command to use to edit a path in worm_hole
	#[clap(long)]
	edit: Option<String>,
	/// The command to use to rename an alias in worm_hole
	#[clap(long)]
	rename: Option<String>,
}

impl Init {
	pub fn run(&self, database: &Database, db_path: &str) -> WHResult<()> {
		database.init();
		println!("{}", self.shell.get_init_code(self, db_path));
		Ok(())
	}
}

#[derive(clap::ValueEnum, Debug, Clone)]
enum Shell {
	Bash,
	Fish,
	Zsh,
}
impl Shell {
	/// Get the init code for the shell
	/// The init code can be eval to create aliases and functions required to use worm_hole
	/// Works for bash, zsh and fish
	pub fn get_init_code(&self, aliases: &Init, db_path: &str) -> String {
		let mut builder = StringBuilder::default();
		builder.append(format!(
			"alias {}='worm_hole --db-path {}'\n",
			aliases.worm_hole,
			FilePath::from_str(db_path).unwrap().str()
		));
		builder.append(self.get_cd_function(aliases.worm_hole.as_str()));
		builder.append(format!("alias {}=__worm_hole_cd\n", aliases.cd));
		if let Some(add) = &aliases.add {
			builder.append(format!("alias {}='{} add'\n", add, aliases.worm_hole));
		}
		if let Some(remove) = &aliases.remove {
			builder.append(format!("alias {}='{} remove'\n", remove, aliases.worm_hole));
		}
		if let Some(list) = &aliases.list {
			builder.append(format!("alias {}='{} list'\n", list, aliases.worm_hole));
		}
		if let Some(search) = &aliases.search {
			builder.append(format!("alias {}='{} search'\n", search, aliases.worm_hole));
		}
		if let Some(query) = &aliases.query {
			builder.append(format!("alias {}='{} query'\n", query, aliases.worm_hole));
		}
		if let Some(edit) = &aliases.edit {
			builder.append(format!("alias {}='{} edit'\n", edit, aliases.worm_hole));
		}
		if let Some(rename) = &aliases.rename {
			builder.append(format!("alias {}='{} rename'\n", rename, aliases.worm_hole));
		}
		builder.string().unwrap()
	}

	/// Get the function to change directory with worm_hole
	/// The function will call worm_hole query to get the path of the alias and cd to it
	/// Works for bash, zsh and fish
	/// The function is named __worm_hole_cd
	fn get_cd_function(&self, worm_hole_command: &str) -> String {
		let mut builder = StringBuilder::default();
		match self {
			Shell::Bash | Shell::Zsh => {
				builder.append("__worm_hole_cd() {\n");
				builder.append("    if [ -z \"$1\" ]\n");
				builder.append("    then\n");
				builder.append("        cd $HOME\n");
				builder.append("    else\n");
				builder.append(format!(
					"        CD=$({} query \"$1\") && \\builtin cd \"$CD\"\n",
					worm_hole_command
				));
				builder.append("    fi\n");
				builder.append("}\n");
				builder.string().unwrap()
			}
			Shell::Fish => {
				builder.append("function __worm_hole_cd\n");
				builder.append("    if test -z $argv\n");
				builder.append("        cd $HOME\n");
				builder.append("    else\n");
				builder.append(format!("        set -l CD ({} query \"$argv\")\n", worm_hole_command));
				builder.append("        if test -n $CD\n");
				builder.append("            builtin cd \"$CD\"\n");
				builder.append("        end\n");
				builder.append("    end\n");
				builder.append("end\n");
				builder.string().unwrap()
			}
		}
	}
}

#[cfg(test)]
mod test {
	use super::*;

	fn has_bin_index(bin: usize, index: usize) -> bool {
		(bin >> index) & 1 == 1
	}
	#[rustfmt::skip]
	fn get_init_struct(shell: Shell, data: usize) -> Init {
		Init {
			shell,
			worm_hole: if has_bin_index(data, 0) { "custom_wh".to_string() } else { "wh".to_string() },
			cd: if has_bin_index(data, 1) { "custom_whcd".to_string() } else { "whcd".to_string() },
			add: if has_bin_index(data, 2) { Some("custom_add".to_string()) } else { None },
			remove: if has_bin_index(data, 3) { Some("custom_remove".to_string()) } else { None },
			list: if has_bin_index(data, 4) { Some("custom_list".to_string()) } else { None },
			search: if has_bin_index(data, 5) { Some("custom_search".to_string()) } else { None },
			query: if has_bin_index(data, 6) { Some("custom_query".to_string()) } else { None },
			edit: if has_bin_index(data, 7) { Some("custom_edit".to_string()) } else { None },
			rename: if has_bin_index(data, 8) { Some("custom_rename".to_string()) } else { None },
		}
	}

	const DB_PATH: &str = "/usr/bin/bash";

	#[test]
	fn bash() {
		const SHELL: Shell = Shell::Bash;
		let normal = "alias wh='worm_hole --db-path ".to_owned() + DB_PATH + "'\n__worm_hole_cd() {\n    if [ -z \"$1\" ]\n    then\n        cd $HOME\n    else\n        CD=$(wh query \"$1\") && \\builtin cd \"$CD\"\n    fi\n}\nalias whcd=__worm_hole_cd\n";
		vec![
			(0b000000000, normal.clone()),
			(0b000000001, "alias custom_wh='worm_hole --db-path ".to_owned() + DB_PATH + "'\n__worm_hole_cd() {\n    if [ -z \"$1\" ]\n    then\n        cd $HOME\n    else\n        CD=$(custom_wh query \"$1\") && \\builtin cd \"$CD\"\n    fi\n}\nalias whcd=__worm_hole_cd\n"),
			(0b000000010, "alias wh='worm_hole --db-path ".to_owned() + DB_PATH + "'\n__worm_hole_cd() {\n    if [ -z \"$1\" ]\n    then\n        cd $HOME\n    else\n        CD=$(wh query \"$1\") && \\builtin cd \"$CD\"\n    fi\n}\nalias custom_whcd=__worm_hole_cd\n"),
			(0b000000100, normal.clone() + "alias custom_add='wh add'\n"),
			(0b000001000, normal.clone() + "alias custom_remove='wh remove'\n"),
			(0b000010000, normal.clone() + "alias custom_list='wh list'\n"),
			(0b000100000, normal.clone() + "alias custom_search='wh search'\n"),
			(0b001000000, normal.clone() + "alias custom_query='wh query'\n"),
			(0b010000000, normal.clone() + "alias custom_edit='wh edit'\n"),
			(0b100000000, normal.clone() + "alias custom_rename='wh rename'\n"),
			(0b111111111, "alias custom_wh='worm_hole --db-path ".to_owned() + DB_PATH + "'\n__worm_hole_cd() {\n    if [ -z \"$1\" ]\n    then\n        cd $HOME\n    else\n        CD=$(custom_wh query \"$1\") && \\builtin cd \"$CD\"\n    fi\n}\nalias custom_whcd=__worm_hole_cd\nalias custom_add='custom_wh add'\nalias custom_remove='custom_wh remove'\nalias custom_list='custom_wh list'\nalias custom_search='custom_wh search'\nalias custom_query='custom_wh query'\nalias custom_edit='custom_wh edit'\nalias custom_rename='custom_wh rename'\n"),
		].into_iter().for_each(|(data, expected)| {
			assert_eq!(SHELL.get_init_code(&get_init_struct(SHELL, data), DB_PATH), expected);
		});
	}

	#[test]
	fn zsh() {
		const SHELL: Shell = Shell::Zsh;
		let normal = "alias wh='worm_hole --db-path ".to_owned() + DB_PATH + "'\n__worm_hole_cd() {\n    if [ -z \"$1\" ]\n    then\n        cd $HOME\n    else\n        CD=$(wh query \"$1\") && \\builtin cd \"$CD\"\n    fi\n}\nalias whcd=__worm_hole_cd\n";
		vec![
			(0b000000000, normal.clone()),
			(0b000000001, "alias custom_wh='worm_hole --db-path ".to_owned() + DB_PATH + "'\n__worm_hole_cd() {\n    if [ -z \"$1\" ]\n    then\n        cd $HOME\n    else\n        CD=$(custom_wh query \"$1\") && \\builtin cd \"$CD\"\n    fi\n}\nalias whcd=__worm_hole_cd\n"),
			(0b000000010, "alias wh='worm_hole --db-path ".to_owned() + DB_PATH + "'\n__worm_hole_cd() {\n    if [ -z \"$1\" ]\n    then\n        cd $HOME\n    else\n        CD=$(wh query \"$1\") && \\builtin cd \"$CD\"\n    fi\n}\nalias custom_whcd=__worm_hole_cd\n"),
			(0b000000100, normal.clone() + "alias custom_add='wh add'\n"),
			(0b000001000, normal.clone() + "alias custom_remove='wh remove'\n"),
			(0b000010000, normal.clone() + "alias custom_list='wh list'\n"),
			(0b000100000, normal.clone() + "alias custom_search='wh search'\n"),
			(0b001000000, normal.clone() + "alias custom_query='wh query'\n"),
			(0b010000000, normal.clone() + "alias custom_edit='wh edit'\n"),
			(0b100000000, normal.clone() + "alias custom_rename='wh rename'\n"),
			(0b111111111, "alias custom_wh='worm_hole --db-path ".to_owned() + DB_PATH + "'\n__worm_hole_cd() {\n    if [ -z \"$1\" ]\n    then\n        cd $HOME\n    else\n        CD=$(custom_wh query \"$1\") && \\builtin cd \"$CD\"\n    fi\n}\nalias custom_whcd=__worm_hole_cd\nalias custom_add='custom_wh add'\nalias custom_remove='custom_wh remove'\nalias custom_list='custom_wh list'\nalias custom_search='custom_wh search'\nalias custom_query='custom_wh query'\nalias custom_edit='custom_wh edit'\nalias custom_rename='custom_wh rename'\n"),
		].into_iter().for_each(|(data, expected)| {
			assert_eq!(SHELL.get_init_code(&get_init_struct(SHELL, data), DB_PATH), expected);
		});
	}

	#[test]
	fn fish() {
		const SHELL: Shell = Shell::Fish;
		let normal = "alias wh='worm_hole --db-path ".to_owned() + DB_PATH + "'\nfunction __worm_hole_cd\n    if test -z $argv\n        cd $HOME\n    else\n        set -l CD (wh query \"$argv\")\n        if test -n $CD\n            builtin cd \"$CD\"\n        end\n    end\nend\nalias whcd=__worm_hole_cd\n";
		vec![
			(0b000000000, normal.clone()),
			(0b000000001, "alias custom_wh='worm_hole --db-path ".to_owned() + DB_PATH + "'\nfunction __worm_hole_cd\n    if test -z $argv\n        cd $HOME\n    else\n        set -l CD (custom_wh query \"$argv\")\n        if test -n $CD\n            builtin cd \"$CD\"\n        end\n    end\nend\nalias whcd=__worm_hole_cd\n"),
			(0b000000010, "alias wh='worm_hole --db-path ".to_owned() + DB_PATH + "'\nfunction __worm_hole_cd\n    if test -z $argv\n        cd $HOME\n    else\n        set -l CD (wh query \"$argv\")\n        if test -n $CD\n            builtin cd \"$CD\"\n        end\n    end\nend\nalias custom_whcd=__worm_hole_cd\n"),
			(0b000000100, normal.clone() + "alias custom_add='wh add'\n"),
			(0b000001000, normal.clone() + "alias custom_remove='wh remove'\n"),
			(0b000010000, normal.clone() + "alias custom_list='wh list'\n"),
			(0b000100000, normal.clone() + "alias custom_search='wh search'\n"),
			(0b001000000, normal.clone() + "alias custom_query='wh query'\n"),
			(0b010000000, normal.clone() + "alias custom_edit='wh edit'\n"),
			(0b100000000, normal.clone() + "alias custom_rename='wh rename'\n"),
			(0b111111111, "alias custom_wh='worm_hole --db-path ".to_owned() + DB_PATH + "'\nfunction __worm_hole_cd\n    if test -z $argv\n        cd $HOME\n    else\n        set -l CD (custom_wh query \"$argv\")\n        if test -n $CD\n            builtin cd \"$CD\"\n        end\n    end\nend\nalias custom_whcd=__worm_hole_cd\nalias custom_add='custom_wh add'\nalias custom_remove='custom_wh remove'\nalias custom_list='custom_wh list'\nalias custom_search='custom_wh search'\nalias custom_query='custom_wh query'\nalias custom_edit='custom_wh edit'\nalias custom_rename='custom_wh rename'\n"),
		].into_iter().for_each(|(data, expected)| {
			assert_eq!(SHELL.get_init_code(&get_init_struct(SHELL, data), DB_PATH), expected);
		});
	}
}