use crate::{
db::Database,
error::{WHError, WHResult},
path::FilePath,
};
use clap::Parser;
use std::{env::current_exe, fmt::Display, str::FromStr};
#[derive(Parser, Debug)]
pub struct Init {
shell: Shell,
#[clap(long, default_value = "wh")]
worm_hole: String,
#[clap(long, default_value = "whcd")]
cd: String,
#[clap(long)]
add: Option<String>,
#[clap(long)]
remove: Option<String>,
#[clap(long)]
list: Option<String>,
#[clap(long)]
search: Option<String>,
#[clap(long)]
query: Option<String>,
#[clap(long)]
edit: Option<String>,
#[clap(long)]
rename: Option<String>,
}
impl Init {
pub fn run(&self, database: &Database, db_path: &str) -> WHResult<()> {
database.init();
print!("{}", self.shell.get_init_code(self, db_path)?);
Ok(())
}
}
#[derive(clap::ValueEnum, Debug, Clone)]
enum Shell {
Bash,
Fish,
Zsh,
Nu,
}
impl Shell {
fn new_alias(&self, alias: &str, command: &str) -> WHResult<String> {
Ok(match self {
Shell::Bash | Shell::Zsh | Shell::Fish => {
if alias.contains(' ') {
return Err(WHError::Err(format!("Aliases cannot contain spaces in {self:?}"), 1));
}
format!("alias {}='{}'", alias, command)
}
Shell::Nu => format!("export alias '{}' = {}", alias, command),
})
}
fn comment(&self, text: impl Display) -> String {
format!("# {}", text)
}
pub fn get_init_code(&self, aliases: &Init, db_path: &str) -> WHResult<String> {
let mut builder = vec![self.comment(format!("This was generated using worm hole {}\n", env!("CARGO_PKG_VERSION")))];
let wh = &aliases.worm_hole;
builder.push(self.new_alias(
&aliases.worm_hole,
&format!(
"{} --db-path {}",
current_exe().unwrap().into_os_string().into_string().unwrap(),
FilePath::from_str(db_path).unwrap().str()
),
)?);
builder.push(self.get_cd_function(aliases));
if let Some(add) = &aliases.add {
match self {
#[rustfmt::skip]
Shell::Nu => {
builder.push(String::from("#Add an alias-path pair to worm hole"));
builder.push(format!("export def '{add}' ["));
builder.push(String::from(" alias: string"));
builder.push(String::from(" path?: string # The real path to the location"));
builder.push(String::from("]: nothing -> nothing {"));
builder.push(format!(" {wh} 'add' ...([$alias $path] | compact)"));
builder.push(String::from("}"));
}
_ => builder.push(self.new_alias(add, &format!("{wh} add"))?),
};
}
if let Some(remove) = &aliases.remove {
match self {
#[rustfmt::skip]
Shell::Nu => {
builder.push(String::from("#Remove an alias-path pair from worm hole"));
builder.push(format!("export def '{remove}' ["));
builder.push(String::from(" alias: string@__worm_hole_list"));
builder.push(String::from("]: nothing -> nothing {"));
builder.push(format!(" {wh} 'rm' $alias"));
builder.push(String::from("}"));
}
_ => builder.push(self.new_alias(remove, &format!("{wh} rm"))?),
};
}
if let Some(list) = &aliases.list {
match self {
#[rustfmt::skip]
Shell::Nu => {
builder.push(String::from("#List all aliases and the corresponding path in worm hole"));
builder.push(format!("export def '{list}' []: nothing -> table<alias: string, path: string> {{"));
builder.push(format!(" {wh} 'ls'"));
builder.push(String::from(" | parse --regex '(?P<alias>.+?)\\s+-> (?P<path>.+)'"));
builder.push(String::from("}"));
}
_ => builder.push(self.new_alias(list, &format!("{wh} ls"))?),
};
}
if let Some(search) = &aliases.search {
match self {
#[rustfmt::skip]
Shell::Nu => {
builder.push(String::from("#Search for worm hole aliases with path matching a pattern"));
builder.push(format!("export def '{search}' ["));
builder.push(String::from(" pattern?: string # The pattern to search for in the aliases's paths, search for aliases in the current directory if not provided"));
builder.push(String::from("]: nothing -> table<alias: string, path: string> {"));
builder.push(format!(" {wh} 'search' ...([$pattern] | compact)"));
builder.push(String::from(" | parse --regex '(?P<alias>.+?)\\s+-> (?P<path>.+)'"));
builder.push(String::from("}"));
}
_ => builder.push(self.new_alias(search, &format!("{wh} search"))?),
};
}
if let Some(query) = &aliases.query {
match self {
#[rustfmt::skip]
Shell::Nu => {
builder.push(String::from("#Get the path for the given worm hole alias"));
builder.push(format!("export def '{query}' ["));
builder.push(String::from(" alias: string@__worm_hole_list"));
builder.push(String::from("]: nothing -> nothing {"));
builder.push(format!(" {wh} query $alias"));
builder.push(String::from("}"));
}
_ => builder.push(self.new_alias(query, &format!("{wh} query"))?),
};
}
if let Some(edit) = &aliases.edit {
match self {
#[rustfmt::skip]
Shell::Nu => {
builder.push(String::from("#Edit the path of a worm hole alias"));
builder.push(format!("export def '{edit}' ["));
builder.push(String::from(" alias: string@__worm_hole_list"));
builder.push(String::from(" path?: string # The new path to the location"));
builder.push(String::from("]: nothing -> nothing {"));
builder.push(format!(" {wh} edit ...([$alias $path] | compact)"));
builder.push(String::from("}"));
}
_ => builder.push(self.new_alias(edit, &format!("{wh} edit"))?),
};
}
if let Some(rename) = &aliases.rename {
match self {
#[rustfmt::skip]
Shell::Nu => {
builder.push(String::from("#Change an alias in worm hole without changing the path"));
builder.push(format!("export def '{rename}' ["));
builder.push(String::from(" old_alias: string@__worm_hole_list"));
builder.push(String::from(" new_alias: string"));
builder.push(String::from("]: nothing -> nothing {"));
builder.push(format!(" {wh} rename $old_alias $new_alias"));
builder.push(String::from("}"));
}
_ => 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!("export-env {{ $env.WH_ALIAS = '{wh}' }}"),
});
Ok(builder.join("\n"))
}
#[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/$2\""),
String::from(" fi"),
String::from("}"),
],
Shell::Fish => vec![
format!("function {cd} -a alias -a subpath"),
String::from(" if test -z $alias"),
String::from(" cd $HOME"),
String::from(" else"),
format!(" set -l CD ({wh} query \"$alias\")"),
String::from(" if test -n $CD"),
String::from(" builtin cd \"$CD/$subpath\""),
String::from(" end"),
String::from(" end"),
String::from("end"),
],
Shell::Nu => vec![
if let Some(list) = &aliases.list {
format!("def __worm_hole_list [] {{ ({list} | rename -c {{ alias: value path: description }}) }}")
} else {
format!("def __worm_hole_list [] {{ ({wh} ls | parse --regex '(?P<value>.+?)\\s+-> (?P<description>.+)') }}")
},
String::from("#Change the current working directory using worm hole's aliases"),
format!("export def --env {cd} ["),
String::from(" alias?: string@__worm_hole_list"),
String::from(" subpath?: string"),
String::from("] {"),
String::from(" if $alias == null {"),
String::from(" cd $env.HOME"),
String::from(" } else {"),
format!(" mut CD = {wh} query $alias"),
String::from(" if ($subpath != null) { $CD = $CD | path join $subpath }"),
String::from(" cd $CD"),
String::from(" }"),
String::from("}"),
],
}.join("\n")
}
}