From e2d538df1f332b7a7c8dca68643957d116367983 Mon Sep 17 00:00:00 2001 From: Teoh Han Hui Date: Sat, 13 Jul 2024 16:52:19 +0800 Subject: [PATCH] Attempt to better support fallback executable paths on Windows --- crates/hyfetch/src/bin/hyfetch.rs | 79 +++++++++++- crates/hyfetch/src/neofetch_util.rs | 184 +++++++++++++--------------- 2 files changed, 160 insertions(+), 103 deletions(-) diff --git a/crates/hyfetch/src/bin/hyfetch.rs b/crates/hyfetch/src/bin/hyfetch.rs index 758741bd..6ce4c182 100644 --- a/crates/hyfetch/src/bin/hyfetch.rs +++ b/crates/hyfetch/src/bin/hyfetch.rs @@ -10,7 +10,7 @@ use hyfetch::color_util::{clear_screen, color, printc, ForegroundBackground, The use hyfetch::models::Config; #[cfg(windows)] use hyfetch::neofetch_util::ensure_git_bash; -use hyfetch::neofetch_util::{self, ascii_size, get_distro_ascii, literal_input, ColorAlignment}; +use hyfetch::neofetch_util::{self, ascii_size, get_distro_ascii, ColorAlignment}; use hyfetch::presets::{AssignLightness, ColorProfile, Preset}; use hyfetch::types::{AnsiMode, Backend, TerminalTheme}; use hyfetch::utils::get_cache_path; @@ -488,6 +488,83 @@ fn create_config( todo!() } +/// Asks the user to provide an input among a list of options. +fn literal_input<'a, S>( + prompt: S, + options: &[&'a str], + default: &str, + show_options: bool, + color_mode: AnsiMode, +) -> Result<&'a str> +where + S: AsRef, +{ + let prompt = prompt.as_ref(); + + if show_options { + let options_text = options + .iter() + .map(|&o| { + if o == default { + format!("&l&n{o}&L&N") + } else { + o.to_owned() + } + }) + .collect::>() + .join("|"); + printc(format!("{prompt} ({options_text})"), color_mode) + .context("failed to print input prompt")?; + } else { + printc(format!("{prompt} (default: {default})"), color_mode) + .context("failed to print input prompt")?; + } + + let find_selection = |sel: &str| { + if sel.is_empty() { + return None; + } + + // Find exact match + if let Some(selected) = options.iter().find(|&&o| o.to_lowercase() == sel) { + return Some(selected); + } + + // Find starting abbreviation + if let Some(selected) = options.iter().find(|&&o| o.to_lowercase().starts_with(sel)) { + return Some(selected); + } + + None + }; + + loop { + let mut buf = String::new(); + print!("> "); + io::stdout().flush()?; + io::stdin() + .read_line(&mut buf) + .context("failed to read line from input")?; + let selection = { + let selection = buf.trim_end_matches(&['\r', '\n']); + if selection.is_empty() { + default.to_owned() + } else { + selection.to_lowercase() + } + }; + + if let Some(selected) = find_selection(&selection) { + println!(); + + return Ok(selected); + } else { + let options_text = options.join("|"); + println!("Invalid selection! {selection} is not one of {options_text}"); + } + } +} + fn init_tracing_subsriber(debug_mode: bool) -> Result<()> { use std::env; use std::str::FromStr; diff --git a/crates/hyfetch/src/neofetch_util.rs b/crates/hyfetch/src/neofetch_util.rs index 3fe11ec0..470cf040 100644 --- a/crates/hyfetch/src/neofetch_util.rs +++ b/crates/hyfetch/src/neofetch_util.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; use std::ffi::OsStr; -use std::io::{self, Write}; +use std::io::Write; use std::path::{Path, PathBuf}; use std::process::Command; use std::sync::OnceLock; @@ -17,8 +17,7 @@ use tracing::debug; use unicode_segmentation::UnicodeSegmentation; use crate::color_util::{ - color, printc, ForegroundBackground, NeofetchAsciiIndexedColor, PresetIndexedColor, - ToAnsiString, + color, ForegroundBackground, NeofetchAsciiIndexedColor, PresetIndexedColor, ToAnsiString, }; use crate::distros::Distro; use crate::presets::ColorProfile; @@ -286,83 +285,6 @@ impl ColorAlignment { } } -/// Asks the user to provide an input among a list of options. -pub fn literal_input<'a, S>( - prompt: S, - options: &[&'a str], - default: &str, - show_options: bool, - color_mode: AnsiMode, -) -> Result<&'a str> -where - S: AsRef, -{ - let prompt = prompt.as_ref(); - - if show_options { - let options_text = options - .iter() - .map(|&o| { - if o == default { - format!("&l&n{o}&L&N") - } else { - o.to_owned() - } - }) - .collect::>() - .join("|"); - printc(format!("{prompt} ({options_text})"), color_mode) - .context("failed to print input prompt")?; - } else { - printc(format!("{prompt} (default: {default})"), color_mode) - .context("failed to print input prompt")?; - } - - let find_selection = |sel: &str| { - if sel.is_empty() { - return None; - } - - // Find exact match - if let Some(selected) = options.iter().find(|&&o| o.to_lowercase() == sel) { - return Some(selected); - } - - // Find starting abbreviation - if let Some(selected) = options.iter().find(|&&o| o.to_lowercase().starts_with(sel)) { - return Some(selected); - } - - None - }; - - loop { - let mut buf = String::new(); - print!("> "); - io::stdout().flush()?; - io::stdin() - .read_line(&mut buf) - .context("failed to read line from input")?; - let selection = { - let selection = buf.trim_end_matches(&['\r', '\n']); - if selection.is_empty() { - default.to_owned() - } else { - selection.to_lowercase() - } - }; - - if let Some(selected) = find_selection(&selection) { - println!(); - - return Ok(selected); - } else { - let options_text = options.join("|"); - println!("Invalid selection! {selection} is not one of {options_text}"); - } - } -} - /// Gets the absolute path of the neofetch command. pub fn neofetch_path() -> Result> { if let Some(workspace_dir) = env::var_os("CARGO_WORKSPACE_DIR") { @@ -424,39 +346,91 @@ pub fn neofetch_path() -> Result> { /// Returns the path to git bash. #[cfg(windows)] pub fn ensure_git_bash() -> Result { + // Find `bash.exe` in `PATH`, but exclude the known bad paths let git_bash_path = { - // Bundled git bash + let bash_path = find_in_path("bash.exe") + .context("failed to check existence of `bash.exe` in `PATH`")?; + match bash_path { + Some(bash_path) if bash_path.ends_with(r"Git\usr\bin\bash.exe") => { + // See https://stackoverflow.com/a/58418686/1529493 + None + }, + Some(bash_path) => { + // See https://github.com/hykilpikonna/hyfetch/issues/233 + let windir = env::var_os("windir") + .context("`windir` environment variable is not set or invalid")?; + if bash_path == Path::new(&windir).join(r"System32\bash.exe") { + None + } else { + Some(bash_path) + } + }, + _ => bash_path, + } + }; + + // Detect any Git for Windows installation in `PATH` + let git_bash_path = if git_bash_path.is_some() { + git_bash_path + } else { + let git_path = + find_in_path("git.exe").context("failed to check existence of `git.exe` in `PATH`")?; + match git_path { + Some(git_path) if git_path.ends_with(r"Git\cmd\git.exe") => { + let bash_path = git_path + .parent() + .expect("parent should not be `None`") + .parent() + .expect("parent should not be `None`") + .join(r"bin\bash.exe"); + if bash_path.is_file() { + Some(bash_path) + } else { + None + } + }, + _ => None, + } + }; + + // Fall back to default Git for Windows installation paths + let git_bash_path = git_bash_path + .or_else(|| { + let program_files_dir = env::var_os("ProgramFiles")?; + let bash_path = Path::new(&program_files_dir).join(r"Git\bin\bash.exe"); + if bash_path.is_file() { + Some(bash_path) + } else { + None + } + }) + .or_else(|| { + let program_files_x86_dir = env::var_os("ProgramFiles(x86)")?; + let bash_path = Path::new(&program_files_x86_dir).join(r"Git\bin\bash.exe"); + if bash_path.is_file() { + Some(bash_path) + } else { + None + } + }); + + // Bundled git bash + let git_bash_path = if git_bash_path.is_some() { + git_bash_path + } else { let current_exe_path = env::current_exe() .and_then(|p| p.normalize().map(|p| p.into())) .context("failed to get path of current running executable")?; let bash_path = current_exe_path .parent() .expect("parent should not be `None`") - .join("git/bin/bash.exe"); + .join(r"git\bin\bash.exe"); if bash_path.is_file() { Some(bash_path) } else { None } }; - let git_bash_path = git_bash_path.or_else(|| { - let program_files_path = env::var_os("ProgramFiles")?; - let bash_path = Path::new(&program_files_path).join("Git/bin/bash.exe"); - if bash_path.is_file() { - Some(bash_path) - } else { - None - } - }); - let git_bash_path = git_bash_path.or_else(|| { - let program_files_x86_path = env::var_os("ProgramFiles(x86)")?; - let bash_path = Path::new(&program_files_x86_path).join("Git/bin/bash.exe"); - if bash_path.is_file() { - Some(bash_path) - } else { - None - } - }); let git_bash_path = git_bash_path.context("failed to find git bash executable")?; @@ -756,6 +730,13 @@ fn run_neofetch(asc: String, args: Option<&Vec>) -> Result<()> { fn fastfetch_path() -> Result> { let fastfetch_path = find_in_path("fastfetch").context("failed to check existence of `fastfetch` in `PATH`")?; + #[cfg(windows)] + let fastfetch_path = if fastfetch_path.is_some() { + fastfetch_path + } else { + find_in_path("fastfetch.exe") + .context("failed to check existence of `fastfetch.exe` in `PATH`")? + }; // Fall back to `fastfetch` in directory of current executable let current_exe_path = env::current_exe() @@ -782,7 +763,6 @@ fn fastfetch_path() -> Result> { }; // Bundled fastfetch - #[cfg(unix)] let fastfetch_path = if fastfetch_path.is_some() { fastfetch_path } else { @@ -801,7 +781,7 @@ fn fastfetch_path() -> Result> { let fastfetch_path = if fastfetch_path.is_some() { fastfetch_path } else { - let fastfetch_path = current_exe_dir_path.join("fastfetch/fastfetch.exe"); + let fastfetch_path = current_exe_dir_path.join(r"fastfetch\fastfetch.exe"); find_file(&fastfetch_path) .with_context(|| format!("failed to check existence of file {fastfetch_path:?}"))? };