use {
crate::{cli, conf, errors::ProgramError, skin},
std::{
fs, io, os,
path::{Path, PathBuf},
str::FromStr,
},
termimad::{mad_print_inline, MadSkin},
};
mod bash;
mod fish;
mod util;
const MD_INSTALL_REQUEST: &str = r#"
**Broot** should be launched using a shell function.
This function most notably makes it possible to `cd` from inside broot
(see *https://dystroy.org/broot/install* for explanations).
Can I install it now? [**Y**/n]
"#;
const MD_INSTALL_CANCELLED: &str = r#"
You refused the installation (for now).
You can still used `broot` but some features won't be available.
If you want the `br` shell function, you may either
* do `broot --install`
* install the various pieces yourself
(see *https://dystroy.org/broot/install* for details).
"#;
const MD_INSTALL_DONE: &str = r#"
The **br** function has been successfully installed.
You may have to restart your shell or source your shell init files.
Afterwards, you should start broot with `br` in order to use its full power.
"#;
const REFUSED_FILE_CONTENT: &str = r#"
This file tells broot you refused the installation of the companion shell function.
If you want to install it run
broot -- install
"#;
const INSTALLED_FILE_CONTENT: &str = r#"
This file tells broot the installation of the br function was done.
If there's a problem and you want to install it again run
broot -- install
"#;
pub struct ShellInstall {
force_install: bool, skin: MadSkin,
pub should_quit: bool,
authorization: Option<bool>,
done: bool, }
#[derive(Debug, Clone, Copy)]
pub enum ShellInstallState {
Undefined, Refused,
Installed,
}
impl FromStr for ShellInstallState {
type Err = ProgramError;
fn from_str(state: &str) -> Result<Self, Self::Err> {
match state {
"undefined" => Ok(Self::Undefined),
"refused" => Ok(Self::Refused),
"installed" => Ok(Self::Installed),
_ => Err(ProgramError::InternalError {
details: format!("unexpected install state: {:?}", state),
}),
}
}
}
impl ShellInstallState {
pub fn write_file(self) -> Result<(), ProgramError> {
let refused_path = get_refused_path();
let installed_path = get_installed_path();
if installed_path.exists() {
fs::remove_file(&installed_path)?;
}
if refused_path.exists() {
fs::remove_file(&refused_path)?;
}
match self {
Self::Refused => {
fs::create_dir_all(refused_path.parent().unwrap())?;
fs::write(&refused_path, REFUSED_FILE_CONTENT)?;
}
Self::Installed => {
fs::create_dir_all(installed_path.parent().unwrap())?;
fs::write(&installed_path, INSTALLED_FILE_CONTENT)?;
}
_ => {}
}
Ok(())
}
}
fn get_refused_path() -> PathBuf {
conf::dir().join("launcher").join("refused")
}
fn get_installed_path() -> PathBuf {
conf::dir().join("launcher").join("installed-v1")
}
impl ShellInstall {
pub fn new(force_install: bool) -> Self {
Self {
force_install,
skin: skin::make_cli_mad_skin(),
should_quit: false,
authorization: if force_install { Some(true) } else { None },
done: false,
}
}
pub fn print(shell: &str) -> Result<(), ProgramError> {
match shell {
"bash" | "zsh" => println!("{}", bash::get_script()),
"fish" => println!("{}", fish::get_script()),
_ => {
return Err(ProgramError::UnknowShell {
shell: shell.to_string(),
});
}
}
Ok(())
}
pub fn check(&mut self) -> Result<(), ProgramError> {
let installed_path = get_installed_path();
if self.force_install {
self.skin.print_text("You requested a clean (re)install.");
self.remove(&get_refused_path())?;
self.remove(&installed_path)?;
} else {
if installed_path.exists() {
debug!("Shell script already installed. Doing nothing.");
return Ok(());
}
debug!("No 'installed' : we ask if we can install");
if !self.can_do()? {
debug!("User refuses the installation. Doing nothing.");
return Ok(());
}
ShellInstallState::Installed.write_file()?;
}
debug!("Starting install");
bash::install(self)?;
fish::install(self)?;
self.should_quit = true;
if self.done {
self.skin.print_text(MD_INSTALL_DONE);
}
Ok(())
}
pub fn remove(&self, path: &Path) -> io::Result<()> {
if fs::read_link(path).is_ok() || path.exists() {
mad_print_inline!(self.skin, "Removing `$0`.\n", path.to_string_lossy());
fs::remove_file(path)?;
}
Ok(())
}
fn can_do(&mut self) -> Result<bool, ProgramError> {
if let Some(authorization) = self.authorization {
return Ok(authorization);
}
let refused_path = get_refused_path();
if refused_path.exists() {
debug!("User already refused the installation");
return Ok(false);
}
self.skin.print_text(MD_INSTALL_REQUEST);
let proceed = cli::ask_authorization()?;
debug!("proceed: {:?}", proceed);
self.authorization = Some(proceed);
if !proceed {
ShellInstallState::Refused.write_file()?;
self.skin.print_text(MD_INSTALL_CANCELLED);
}
Ok(proceed)
}
fn write_script(&self, script_path: &Path, content: &str) -> Result<(), ProgramError> {
self.remove(&script_path)?;
info!("Writing `br` shell function in `{:?}`", &script_path);
mad_print_inline!(
&self.skin,
"Writing *br* shell function in `$0`.\n",
script_path.to_string_lossy(),
);
fs::create_dir_all(script_path.parent().unwrap())?;
fs::write(&script_path, content)?;
Ok(())
}
fn create_link(&self, link_path: &Path, script_path: &Path) -> Result<(), ProgramError> {
info!("Creating link from {:?} to {:?}", &link_path, &script_path);
self.remove(&link_path)?;
let link_path_str = link_path.to_string_lossy();
let script_path_str = script_path.to_string_lossy();
mad_print_inline!(
&self.skin,
"Creating link from `$0` to `$1`.\n",
&link_path_str,
&script_path_str,
);
fs::create_dir_all(link_path.parent().unwrap())?;
#[cfg(unix)]
os::unix::fs::symlink(&script_path, &link_path)?;
#[cfg(windows)]
os::windows::fs::symlink_file(&script_path, &link_path)?;
Ok(())
}
}