worm_hole 1.6.2

CLI tool to easily jump between directories
// Copyright (C) 2025 Rignchen
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

use crate::{db::Database, error::WHResult, path::FilePath};
use clap::Parser;
use std::str::FromStr;

#[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,
	Nu,
}
impl Shell {
	/// Add an alias to the string builder that will output the init code
	fn new_alias(&self, alias: &str, command: &str) -> String {
		match self {
			Shell::Bash | Shell::Zsh | Shell::Fish => format!("alias {}='{}'", alias, command),
			Shell::Nu => format!("alias {} = {}", alias, command),
		}
	}

	/// 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 = Vec::new();
		let wh = &aliases.worm_hole;
		builder.push(self.new_alias(
			&aliases.worm_hole,
			&format!("worm_hole --db-path {}", FilePath::from_str(db_path).unwrap().str()),
		));
		builder.push(self.get_cd_function(aliases));
		if let Some(add) = &aliases.add {
			builder.push(self.new_alias(add, &format!("{wh} add")));
		}
		if let Some(remove) = &aliases.remove {
			builder.push(self.new_alias(remove, &format!("{wh} rm")));
		}
		if let Some(list) = &aliases.list {
			builder.push(self.new_alias(list, &format!("{wh} ls")));
		}
		if let Some(search) = &aliases.search {
			builder.push(self.new_alias(search, &format!("{wh} search")));
		}
		if let Some(query) = &aliases.query {
			builder.push(self.new_alias(query, &format!("{wh} query")));
		}
		if let Some(edit) = &aliases.edit {
			builder.push(self.new_alias(edit, &format!("{wh} edit")));
		}
		if let Some(rename) = &aliases.rename {
			builder.push(self.new_alias(rename, &format!("{wh} rename")));
		}
		builder.push(match self {
			Shell::Bash | Shell::Zsh => format!("export WH_ALIAS=\"{wh}\""),
			Shell::Fish => format!("set -gx WH_ALIAS \"{wh}\""),
			Shell::Nu => format!("$env.WH_ALIAS = '{wh}'"),
		});
		builder.join("\n")
	}

	/// 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 whcd by default but can be changed with the --cd flag
	#[rustfmt::skip]
	fn get_cd_function(&self, aliases: &Init) -> String {
		let cd = &aliases.cd;
		let wh = &aliases.worm_hole;
		match self {
			Shell::Bash | Shell::Zsh => vec![
				     format!("{cd}() {{"),
				String::from("    if [ -z \"$1\" ]"),
				String::from("    then"),
				String::from("        cd $HOME"),
				String::from("    else"),
				     format!("        CD=$({wh} query \"$1\") && \\builtin cd \"$CD\""),
				String::from("    fi"),
				String::from("}"),
			],
			Shell::Fish => vec![
				     format!("function {cd}"),
				String::from("    if test -z $argv"),
				String::from("        cd $HOME"),
				String::from("    else"),
				     format!("        set -l CD ({wh} query \"$argv\")"),
				String::from("        if test -n $CD"),
				String::from("            builtin cd \"$CD\""),
				String::from("        end"),
				String::from("    end"),
				String::from("end"),
			],
			Shell::Nu => vec![
				     format!("def __worm_hole_list [] {{ ({wh} ls | parse --regex '(?P<value>.+?)\\s+-> (?P<description>.+)') }}"),
				     format!("def --env {cd} [alias?: string@__worm_hole_list] {{"),
				String::from("    if $alias == null {"),
				String::from("        cd $env.HOME"),
				String::from("    } else {"),
				     format!("        let CD = {wh} query $alias"),
				String::from("        if $CD != null {"),
				String::from("            cd $CD"),
				String::from("        }"),
				String::from("    }"),
				String::from("}"),
			],
		}.join("\n")
	}
}