//! The goal of this mod is to ensure the launcher shell function //! is available for bash and zsh i.e. the `br` shell function can //! be used to launch broot (and thus make it possible to execute //! some commands, like `cd`, from the starting shell. //! //! In a correct installation, we have: //! - a function declaration script in ~/.local/share/broot/launcher/bash/br/1 //! - a link to that script in ~/.config/broot/launcher/bash/br/1 //! - a line to source the link in ~/.bashrc and ~/.zshrc //! (exact paths depend on XDG variables) use { std::{ env, fs::OpenOptions, io::Write, path::PathBuf, }, directories::UserDirs, crate::{ conf, errors::ProgramError, }, super::{ ShellInstall, util, }, minimad::*, regex::{Captures, Regex}, termimad::mad_print_inline, }; const NAME: &str = "bash"; const SOURCING_FILES: &[&str] = &[ ".bashrc", ".bash_profile", ".zshrc", "$ZDOTDIR/.zshrc", ]; const VERSION: &str = "1"; // This script has been tested on bash and zsh. // It's installed under the bash name (~/.config/broot // but linked from both the .bashrc and the .zshrc files const BASH_FUNC: &str = r#" # This script was automatically generated by the broot program # More information can be found in https://github.com/Canop/broot # This function starts broot and executes the command # it produces, if any. # It's needed because some shell commands, like `cd`, # have no useful effect if executed in a subshell. function br { f=$(mktemp) ( set +e broot --outcmd "$f" "$@" code=$? if [ "$code" != 0 ]; then rm -f "$f" exit "$code" fi ) code=$? if [ "$code" != 0 ]; then return "$code" fi d=$(<"$f") rm -f "$f" eval "$d" } "#; const MD_NO_SOURCING: &str = r#" I found no sourcing file for the bash/zsh family. If you're using bash or zsh, then installation isn't complete: the br function initialization script won't be sourced unless you source it yourself. "#; pub fn get_script() -> &'static str { BASH_FUNC } /// return the path to the link to the function script fn get_link_path() -> PathBuf { conf::dir().join("launcher").join(NAME).join("br") } /// return the path to the script containing the function. /// /// At version 0.10.4 we change the location of the script: /// It was previously with the link, but it's now in /// XDG_DATA_HOME (typically ~/.local/share on linux) fn get_script_path() -> PathBuf { conf::app_dirs().data_dir().join("launcher").join(NAME).join(VERSION) } /// return the paths to the files in which the br function is sourced. /// Paths in SOURCING_FILES can be absolute or relative to the home /// directory. Environnement variables designed as $NAME are interpolated. fn get_sourcing_paths() -> Vec { let homedir_path = UserDirs::new() .expect("no home directory!") .home_dir() .to_path_buf(); SOURCING_FILES.iter() .map(|name| regex!(r#"\$(\w+)"#) .replace(name, |c: &Captures<'_>| env::var(&c[1]).unwrap_or(name.to_string()) ) .to_string() ) .map(PathBuf::from) .map(|path| if path.is_absolute() { path } else { homedir_path.join(path) } ) .filter(|path| { debug!("considering path: {:?}", &path); path.exists() }) .collect() } /// check for bash and zsh shells. /// check whether the shell function is installed, install /// it if it wasn't refused before or if broot is launched /// with --install. pub fn install(si: &mut ShellInstall) -> Result<(), ProgramError> { let script_path = get_script_path(); si.write_script(&script_path, BASH_FUNC)?; let link_path = get_link_path(); si.create_link(&link_path, &script_path)?; let sourcing_paths = get_sourcing_paths(); if sourcing_paths.is_empty() { warn!("no sourcing path for bash/zsh!"); si.skin.print_text(MD_NO_SOURCING); return Ok(()); } let source_line = format!("source {}", &link_path.to_string_lossy()); let sourced_template = TextTemplate::from(r#" ${path} successfully patched, you can make the function immediately available with source ${path} "#); for sourcing_path in &sourcing_paths { let sourcing_path_str = sourcing_path.to_string_lossy(); if util::file_contains_line(sourcing_path, &source_line)? { mad_print_inline!( &si.skin, "`$0` already patched, no change made.\n", &sourcing_path_str ); } else { let mut shellrc = OpenOptions::new() .write(true) .append(true) .open(sourcing_path)?; shellrc.write_all(b"\n")?; shellrc.write_all(source_line.as_bytes())?; shellrc.write_all(b"\n")?; let mut expander = sourced_template.expander(); expander.set("path", &sourcing_path_str); si.skin.print_expander(expander); } } si.done = true; Ok(()) }