broot/shell_install/
mod.rs1mod bash;
2mod fish;
3mod nushell;
4mod powershell;
5mod state;
6mod util;
7
8use {
9 crate::{
10 cli,
11 errors::*,
12 skin,
13 },
14 std::{
15 fs,
16 os,
17 path::Path,
18 },
19 termimad::{
20 MadSkin,
21 mad_print_inline,
22 },
23};
24
25pub use state::ShellInstallState;
26
27const MD_INSTALL_REQUEST: &str = r"
28**Broot** should be launched using a shell function.
29This function most notably makes it possible to `cd` from inside broot
30(see *https://dystroy.org/broot/install-br/* for explanations).
31
32Can I install it now? [**Y**/n]
33";
34
35const MD_UPGRADE_REQUEST: &str = r"
36Broot's shell function should be upgraded.
37
38Can I proceed? [**Y**/n]
39";
40
41const MD_INSTALL_CANCELLED: &str = r"
42You refused the installation (for now).
43You can still used `broot` but some features won't be available.
44If you want the `br` shell function, you may either
45* do `broot --install`
46* install the various pieces yourself
47(see *https://dystroy.org/broot/install-br/* for details).
48
49";
50
51const MD_PERMISSION_DENIED: &str = r"
52Installation check resulted in **Permission Denied**.
53Please relaunch with elevated privilege.
54This is typically only needed once.
55Error details:
56";
57
58const MD_INSTALL_DONE: &str = r"
59The **br** function has been successfully installed.
60You may have to restart your shell or source your shell init files.
61Afterwards, you should start broot with `br` in order to use its full power.
62
63";
64
65pub struct ShellInstall {
66 force_install: bool, skin: MadSkin,
68 pub should_quit: bool,
69 authorization: Option<bool>,
70 done: bool, }
72
73impl ShellInstall {
74 pub fn new(force_install: bool) -> Self {
75 Self {
76 force_install,
77 skin: skin::make_cli_mad_skin(),
78 should_quit: false,
79 authorization: if force_install { Some(true) } else { None },
80 done: false,
81 }
82 }
83
84 pub fn print(shell: &str) -> Result<(), ProgramError> {
87 match shell {
88 "bash" | "zsh" => println!("{}", bash::get_script()),
89 "fish" => println!("{}", fish::get_script()),
90 "nushell" => println!("{}", nushell::get_script()),
91 "powershell" => println!("{}", powershell::get_script()),
92 _ => {
93 return Err(ProgramError::UnknownShell {
94 shell: shell.to_string(),
95 });
96 }
97 }
98 Ok(())
99 }
100
101 pub fn check(&mut self) -> Result<(), ShellInstallError> {
105 let install_state = ShellInstallState::detect();
106 info!("Shell installation state: {install_state:?}");
107 if self.force_install {
108 self.skin.print_text("You requested a clean (re)install.");
109 ShellInstallState::remove(self)?;
110 } else {
111 match install_state {
112 ShellInstallState::Refused => {
113 return Ok(());
114 }
115 ShellInstallState::UpToDate => {
116 return Ok(());
117 }
118 ShellInstallState::Obsolete => {
119 if !self.can_upgrade()? {
120 debug!("User refuses the upgrade. Doing nothing.");
121 return Ok(());
122 }
123 }
124 ShellInstallState::NotInstalled => {
125 if !self.can_install()? {
126 debug!("User refuses the installation. Doing nothing.");
127 return Ok(());
128 }
129 }
130 }
131 }
132 ShellInstallState::UpToDate.write(self)?;
136 debug!("Starting install");
137 bash::install(self)?;
138 fish::install(self)?;
139 nushell::install(self)?;
140 powershell::install(self)?;
141 self.should_quit = true;
142 if self.done {
143 self.skin.print_text(MD_INSTALL_DONE);
144 }
145 Ok(())
146 }
147
148 pub fn comment_error(
151 &self,
152 err: &ShellInstallError,
153 ) {
154 if err.is_permission_denied() {
155 self.skin.print_text(MD_PERMISSION_DENIED);
156 }
157 }
158
159 pub fn remove(
160 &self,
161 path: &Path,
162 ) -> Result<(), ShellInstallError> {
163 if fs::read_link(path).is_ok() || path.exists() {
167 mad_print_inline!(self.skin, "Removing `$0`.\n", path.to_string_lossy());
168 fs::remove_file(path).context(&|| format!("removing {path:?}"))?;
169 }
170 Ok(())
171 }
172
173 fn can_install(&mut self) -> Result<bool, ShellInstallError> {
175 self.can_do(false)
176 }
177 fn can_upgrade(&mut self) -> Result<bool, ShellInstallError> {
178 self.can_do(true)
179 }
180 fn can_do(
181 &mut self,
182 upgrade: bool,
183 ) -> Result<bool, ShellInstallError> {
184 if let Some(authorization) = self.authorization {
185 return Ok(authorization);
186 }
187 let refused_path = ShellInstallState::get_refused_path();
188 if refused_path.exists() {
189 debug!("User already refused the installation");
190 return Ok(false);
191 }
192 self.skin.print_text(if upgrade {
193 MD_UPGRADE_REQUEST
194 } else {
195 MD_INSTALL_REQUEST
196 });
197 let proceed = cli::ask_authorization().context(&|| "asking user".to_string())?; debug!("proceed: {:?}", proceed);
199 self.authorization = Some(proceed);
200 if !proceed {
201 ShellInstallState::Refused.write(self)?;
202 self.skin.print_text(MD_INSTALL_CANCELLED);
203 }
204 Ok(proceed)
205 }
206
207 fn write_script(
209 &self,
210 script_path: &Path,
211 content: &str,
212 ) -> Result<(), ShellInstallError> {
213 self.remove(script_path)?;
214 info!("Writing `br` shell function in `{:?}`", &script_path);
215 mad_print_inline!(
216 &self.skin,
217 "Writing *br* shell function in `$0`.\n",
218 script_path.to_string_lossy(),
219 );
220 fs::create_dir_all(script_path.parent().unwrap())
221 .context(&|| format!("creating parent dirs to {script_path:?}"))?;
222 fs::write(script_path, content)
223 .context(&|| format!("writing script in {script_path:?}"))?;
224 Ok(())
225 }
226
227 fn create_link(
229 &self,
230 link_path: &Path,
231 script_path: &Path,
232 ) -> Result<(), ShellInstallError> {
233 info!("Creating link from {:?} to {:?}", &link_path, &script_path);
234 self.remove(link_path)?;
235 let link_path_str = link_path.to_string_lossy();
236 let script_path_str = script_path.to_string_lossy();
237 mad_print_inline!(
238 &self.skin,
239 "Creating link from `$0` to `$1`.\n",
240 &link_path_str,
241 &script_path_str,
242 );
243 let parent = link_path.parent().unwrap();
244 fs::create_dir_all(parent).context(&|| format!("creating directory {parent:?}"))?;
245 #[cfg(unix)]
246 os::unix::fs::symlink(script_path, link_path)
247 .context(&|| format!("linking from {link_path:?} to {script_path:?}"))?;
248 #[cfg(windows)]
249 os::windows::fs::symlink_file(&script_path, &link_path)
250 .context(&|| format!("linking from {link_path:?} to {script_path:?}"))?;
251 Ok(())
252 }
253}