diff --git a/src/bin/huak/commands/config/completion.rs b/src/bin/huak/commands/config/completion.rs index 082e3b01..f78e985d 100644 --- a/src/bin/huak/commands/config/completion.rs +++ b/src/bin/huak/commands/config/completion.rs @@ -1,9 +1,91 @@ use std::fs::File; use std::io::{Error, Write}; use std::path::Path; +use std::process::ExitCode; -use clap::Command; +use clap::{Command, CommandFactory}; use clap_complete::{generate, Shell}; +use huak::errors::HuakError; + +use crate::commands::Cli; +use crate::errors::{CliError, CliResult}; + +/// Prints the script to stdout and a way to add the script to the shell init file to stderr. This +/// way if the user runs completion > completion.sh only the stdout will be redirected into +/// completion.sh. +pub fn run( + shell: Option, + install: bool, + uninstall: bool, +) -> CliResult<()> { + if (install || uninstall) && shell.is_none() { + return Err(CliError::new( + HuakError::ConfigurationError("No shell provided".to_string()), + ExitCode::FAILURE, + )); + } + if install { + run_with_install(shell)?; + } else if uninstall { + run_with_uninstall(shell)?; + } else { + generate_shell_completion_script() + } + Ok(()) +} + +fn generate_shell_completion_script() { + let mut cmd = Cli::command(); + + generate(Shell::Bash, &mut cmd, "huak", &mut std::io::stdout()) +} + +fn run_with_install(shell: Option) -> CliResult<()> { + let err = Err(CliError::new( + HuakError::ConfigurationError("Invalid shell".to_string()), + ExitCode::FAILURE, + )); + let sh = match shell { + Some(it) => it, + None => return err, + }; + let mut cmd: Command = Cli::command(); + match sh { + Shell::Bash => add_completion_bash(), + Shell::Elvish => add_completion_elvish(), + Shell::Fish => add_completion_fish(&mut cmd), + Shell::PowerShell => add_completion_powershell(), + Shell::Zsh => add_completion_zsh(&mut cmd), + _ => { + return err; + } + }?; + + Ok(()) +} + +fn run_with_uninstall(shell: Option) -> CliResult<()> { + let err = Err(CliError::new( + HuakError::ConfigurationError("Invalid shell".to_string()), + ExitCode::FAILURE, + )); + let sh = match shell { + Some(it) => it, + None => return err, + }; + match sh { + Shell::Bash => remove_completion_bash(), + Shell::Elvish => remove_completion_elvish(), + Shell::Fish => remove_completion_fish(), + Shell::PowerShell => remove_completion_powershell(), + Shell::Zsh => remove_completion_zsh(), + _ => { + return err; + } + }?; + + Ok(()) +} /// Bash has a couple of files that can contain the actual completion script. /// Only the line `eval "$(huak config completion bash)"` needs to be added @@ -12,7 +94,7 @@ use clap_complete::{generate, Shell}; /// ~/.bash_login /// ~/.profile /// ~/.bashrc -pub fn add_completion_bash() -> Result<(), Error> { +pub fn add_completion_bash() -> CliResult<()> { let home = match std::env::var("HOME") { Ok(dir) => dir, Err(e) => { @@ -22,13 +104,10 @@ pub fn add_completion_bash() -> Result<(), Error> { } }; - let _file_path = format!("{}/.bashrc", home); - - #[cfg(test)] - let _file_path = format!("test_files/test_bashrc"); + let file_path = format!("{}/.bashrc", home); // opening file in append mode - let mut file: File = File::options().append(true).open(_file_path)?; + let mut file = File::options().append(true).open(file_path)?; // This needs to be a string since there will be a \n prepended if it is file.write_all( @@ -40,114 +119,87 @@ pub fn add_completion_bash() -> Result<(), Error> { } // TODO -pub fn add_completion_elvish() -> Result<(), Error> { +pub fn add_completion_elvish() -> CliResult<()> { todo!() } /// huak config completion fish > ~/.config/fish/completions/huak.fish /// Fish has a completions directory in which all files are loaded on init. /// The naming convention is $HOME/.config/fish/completions/huak.fish -pub fn add_completion_fish(cli: &mut Command) -> Result<(), Error> { +pub fn add_completion_fish(cli: &mut Command) -> CliResult<()> { let home = match std::env::var("HOME") { Ok(dir) => dir, - Err(e) => { - // defaulting to root, this might not be the right call - eprintln!("{}", e); - String::from("root") - } + Err(e) => return Err(CliError::from(e)), }; - let _target_file = format!("{}/.config/fish/completions/huak.fish", home); - - #[cfg(test)] - let _target_file = "test_files/test_fish".to_string(); + let target_file = format!("{}/.config/fish/completions/huak.fish", home); - generate_target_file(_target_file, cli)?; + generate_target_file(target_file, cli)?; Ok(()) } // TODO -pub fn add_completion_powershell() -> Result<(), Error> { +pub fn add_completion_powershell() -> CliResult<()> { todo!() } /// Zsh and fish are the same in the sense that the use an entire directory to collect shell init /// scripts. -pub fn add_completion_zsh(cli: &mut Command) -> Result<(), Error> { - let _target_file = "/usr/local/share/zsh/site-functions/_huak".to_string(); - - #[cfg(test)] - let _target_file = "test_files/test_zsh".to_string(); +pub fn add_completion_zsh(cli: &mut Command) -> CliResult<()> { + let target_file = "/usr/local/share/zsh/site-functions/_huak".to_string(); - generate_target_file(_target_file, cli)?; + generate_target_file(target_file, cli)?; Ok(()) } /// Reads the entire file and removes lines that match exactly with: /// \neval "$(huak config completion) -pub fn remove_completion_bash() -> Result<(), Error> { +pub fn remove_completion_bash() -> CliResult<()> { let home = match std::env::var("HOME") { Ok(dir) => dir, - Err(e) => { - // defaulting to root, this might not be the right call - eprintln!("{}", e); - String::from("root") - } + Err(e) => return Err(CliError::from(e)), }; - let _file_path = format!("{}/.bashrc", home); + let file_path = format!("{}/.bashrc", home); - #[cfg(test)] - let _file_path = format!("test_files/test_bashrc"); - - let file_content = std::fs::read_to_string(&_file_path)?; + let file_content = std::fs::read_to_string(&file_path)?; let new_content = file_content.replace( &format!(r##"{}eval "$(huak config completion)"{}"##, '\n', '\n'), "", ); - std::fs::write(&_file_path, new_content)?; + std::fs::write(&file_path, new_content)?; Ok(()) } // TODO -pub fn remove_completion_elvish() -> Result<(), Error> { - todo!() +pub fn remove_completion_elvish() -> CliResult<()> { + unimplemented!() } -pub fn remove_completion_fish() -> Result<(), Error> { +pub fn remove_completion_fish() -> CliResult<()> { let home = match std::env::var("HOME") { Ok(dir) => dir, - Err(e) => { - // defaulting to root, this might not be the right call - eprintln!("{}", e); - String::from("root") - } + Err(e) => return Err(CliError::from(e)), }; - let _target_file = format!("{}/.config/fish/completions/huak.fish", home); - - #[cfg(test)] - let _target_file = "test_files/test_fish".to_string(); + let target_file = format!("{}/.config/fish/completions/huak.fish", home); - std::fs::remove_file(_target_file)?; + std::fs::remove_file(target_file)?; Ok(()) } // TODO -pub fn remove_completion_powershell() -> Result<(), Error> { - todo!() +pub fn remove_completion_powershell() -> CliResult<()> { + unimplemented!() } -pub fn remove_completion_zsh() -> Result<(), Error> { - let _target_file = "/usr/local/share/zsh/site-functions/_huak".to_string(); - - #[cfg(test)] - let _target_file = "test_files/test_zsh".to_string(); +pub fn remove_completion_zsh() -> CliResult<()> { + let target_file = "/usr/local/share/zsh/site-functions/_huak".to_string(); - std::fs::remove_file(_target_file)?; + std::fs::remove_file(target_file)?; Ok(()) } @@ -159,13 +211,16 @@ fn generate_target_file

( where P: AsRef, { - let mut file: File = File::create(&target_file)?; + let mut file = File::create(&target_file)?; generate(Shell::Fish, cmd, "huak", &mut file); Ok(()) } +// TODO: +// - Use tempdir and mocking for testing these features. +// - Requires refactors of functions and their signatures. #[cfg(test)] mod tests { use super::*; @@ -176,7 +231,8 @@ mod tests { #[derive(Parser)] struct Cli {} - #[cfg(target = "linux")] + #[cfg(target_family = "unix")] + #[ignore = "incomplete test"] // See TODO #[test] /// This test ensures the order of operations is always correct fn test_bash_completion() { @@ -184,6 +240,7 @@ mod tests { test_remove_completion_bash(); } + #[cfg(target_family = "unix")] fn test_adding_completion_bash() { let _ = add_completion_bash(); @@ -203,6 +260,7 @@ eval "$(huak config completion)" ) } + #[cfg(target_family = "unix")] fn test_remove_completion_bash() { let _ = remove_completion_bash(); @@ -215,7 +273,8 @@ eval "$(huak config completion)" ", file_content) } - #[cfg(target = "linux")] + #[cfg(target_family = "unix")] + #[ignore = "incomplete test"] // See TODO #[test] /// This test ensures the order of operations is always correct fn test_fish_completion() { @@ -225,6 +284,7 @@ eval "$(huak config completion)" test_remove_completion_fish(); } + #[cfg(target_family = "unix")] fn test_adding_completion_fish(cmd: &mut Command) { let _ = add_completion_fish(cmd); @@ -233,6 +293,7 @@ eval "$(huak config completion)" assert_eq!(true, result.is_ok()); } + #[cfg(target_family = "unix")] fn test_remove_completion_fish() { let _ = remove_completion_fish(); @@ -240,7 +301,8 @@ eval "$(huak config completion)" assert_eq!(true, result.is_err()); } - #[cfg(target = "linux")] + #[cfg(target_family = "unix")] + #[ignore = "incomplete test"] // See TODO #[test] /// This test ensures the order of operations is always correct fn test_zsh_completion() { @@ -250,6 +312,7 @@ eval "$(huak config completion)" test_remove_completion_zsh(); } + #[cfg(target_family = "unix")] fn test_adding_completion_zsh(cmd: &mut Command) { let _ = add_completion_zsh(cmd); @@ -258,6 +321,7 @@ eval "$(huak config completion)" assert_eq!(true, result.is_ok()); } + #[cfg(target_family = "unix")] fn test_remove_completion_zsh() { let _ = remove_completion_zsh(); diff --git a/src/bin/huak/commands/config/mod.rs b/src/bin/huak/commands/config/mod.rs index 718e1000..e6c797fa 100644 --- a/src/bin/huak/commands/config/mod.rs +++ b/src/bin/huak/commands/config/mod.rs @@ -1,91 +1,12 @@ mod completion; -use crate::commands::Cli; -use crate::errors::CliResult; - -use clap::{Args, Command, CommandFactory, Subcommand}; -use clap_complete::{generate, Shell}; - -/// Prints the script to stdout and a way to add the script to the shell init file to stderr. This -/// way if the user runs completion > completion.sh only the stdout will be redirected into -/// completion.sh. -pub fn run(config_command: Config) -> CliResult<()> { - match config_command.command { - ConfigCommand::Completion { - shell, - install, - uninstall, - } => { - if install { - let mut cmd: Command = Cli::command(); - match shell { - Some(shell) => { - let result = match shell { - Shell::Bash => completion::add_completion_bash(), - Shell::Elvish => { - completion::add_completion_elvish() - } - Shell::Fish => { - completion::add_completion_fish(&mut cmd) - } - Shell::PowerShell => { - completion::add_completion_powershell() - } - Shell::Zsh => { - completion::add_completion_zsh(&mut cmd) - } - _ => Ok(()), - }; - if let Err(e) = result { - eprintln!("{}", e); - } - } - None => eprintln!("Please provide a shell"), - } - } else if uninstall { - match shell { - Some(shell) => { - let result = match shell { - Shell::Bash => completion::remove_completion_bash(), - Shell::Elvish => { - completion::remove_completion_elvish() - } - Shell::Fish => completion::remove_completion_fish(), - Shell::PowerShell => { - completion::remove_completion_powershell() - } - Shell::Zsh => completion::remove_completion_zsh(), - _ => Ok(()), - }; +use clap::Subcommand; +use clap_complete::Shell; - if let Err(e) = result { - eprintln!("{}", e); - } - } - None => eprintln!("Please provide a shell"), - } - } else { - generate_shell_completion_script() - } - } - } - Ok(()) -} - -fn generate_shell_completion_script() { - let mut cmd = Cli::command(); - - generate(Shell::Bash, &mut cmd, "huak", &mut std::io::stdout()) -} - -#[derive(Args)] -pub struct Config { - #[command(subcommand)] - command: ConfigCommand, -} +use crate::errors::CliResult; -#[derive(Debug, Subcommand)] -pub enum ConfigCommand { +#[derive(Subcommand)] +pub enum Config { /// Generates a shell completion script for supported shells. /// See the help menu for more information on supported shells. Completion { @@ -103,3 +24,15 @@ pub enum ConfigCommand { uninstall: bool, }, } + +pub fn run(command: Config) -> CliResult<()> { + match command { + Config::Completion { + shell, + install, + uninstall, + } => completion::run(shell, install, uninstall), + }?; + + Ok(()) +} diff --git a/src/bin/huak/commands/mod.rs b/src/bin/huak/commands/mod.rs index fbef6065..14dcbfdd 100644 --- a/src/bin/huak/commands/mod.rs +++ b/src/bin/huak/commands/mod.rs @@ -49,7 +49,10 @@ pub enum Commands { /// Build tarball and wheel for the project. Build, /// Interact with the configuration of huak. - Config(config::Config), + Config { + #[command(subcommand)] + command: config::Config, + }, /// Remove tarball and wheel from the built project. Clean { #[arg(long, required = false)] @@ -123,7 +126,7 @@ pub enum Commands { impl Cli { pub fn run(self) -> CliResult<()> { match self.command { - Commands::Config(config_command) => config::run(config_command), + Commands::Config { command } => config::run(command), Commands::Activate => activate::run(), Commands::Add { dependency, dev } => add::run(dependency, dev), Commands::Audit => audit::run(), diff --git a/src/bin/huak/errors.rs b/src/bin/huak/errors.rs index 307ac080..95fb3aa6 100644 --- a/src/bin/huak/errors.rs +++ b/src/bin/huak/errors.rs @@ -57,3 +57,9 @@ impl From for CliError { CliError::new(HuakError::Utf8Error(err), BASIC_ERROR_CODE) } } + +impl From for CliError { + fn from(err: std::env::VarError) -> CliError { + CliError::new(HuakError::EnvVarError(err), BASIC_ERROR_CODE) + } +}