Attempt to better support fallback executable paths on Windows
This commit is contained in:
parent
d11f6f0a9f
commit
e2d538df1f
2 changed files with 160 additions and 103 deletions
|
@ -10,7 +10,7 @@ use hyfetch::color_util::{clear_screen, color, printc, ForegroundBackground, The
|
||||||
use hyfetch::models::Config;
|
use hyfetch::models::Config;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use hyfetch::neofetch_util::ensure_git_bash;
|
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::presets::{AssignLightness, ColorProfile, Preset};
|
||||||
use hyfetch::types::{AnsiMode, Backend, TerminalTheme};
|
use hyfetch::types::{AnsiMode, Backend, TerminalTheme};
|
||||||
use hyfetch::utils::get_cache_path;
|
use hyfetch::utils::get_cache_path;
|
||||||
|
@ -488,6 +488,83 @@ fn create_config(
|
||||||
todo!()
|
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<str>,
|
||||||
|
{
|
||||||
|
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::<Vec<_>>()
|
||||||
|
.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<()> {
|
fn init_tracing_subsriber(debug_mode: bool) -> Result<()> {
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::io::{self, Write};
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
@ -17,8 +17,7 @@ use tracing::debug;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::color_util::{
|
use crate::color_util::{
|
||||||
color, printc, ForegroundBackground, NeofetchAsciiIndexedColor, PresetIndexedColor,
|
color, ForegroundBackground, NeofetchAsciiIndexedColor, PresetIndexedColor, ToAnsiString,
|
||||||
ToAnsiString,
|
|
||||||
};
|
};
|
||||||
use crate::distros::Distro;
|
use crate::distros::Distro;
|
||||||
use crate::presets::ColorProfile;
|
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<str>,
|
|
||||||
{
|
|
||||||
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::<Vec<_>>()
|
|
||||||
.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.
|
/// Gets the absolute path of the neofetch command.
|
||||||
pub fn neofetch_path() -> Result<Option<PathBuf>> {
|
pub fn neofetch_path() -> Result<Option<PathBuf>> {
|
||||||
if let Some(workspace_dir) = env::var_os("CARGO_WORKSPACE_DIR") {
|
if let Some(workspace_dir) = env::var_os("CARGO_WORKSPACE_DIR") {
|
||||||
|
@ -424,39 +346,91 @@ pub fn neofetch_path() -> Result<Option<PathBuf>> {
|
||||||
/// Returns the path to git bash.
|
/// Returns the path to git bash.
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub fn ensure_git_bash() -> Result<PathBuf> {
|
pub fn ensure_git_bash() -> Result<PathBuf> {
|
||||||
|
// Find `bash.exe` in `PATH`, but exclude the known bad paths
|
||||||
let git_bash_path = {
|
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()
|
let current_exe_path = env::current_exe()
|
||||||
.and_then(|p| p.normalize().map(|p| p.into()))
|
.and_then(|p| p.normalize().map(|p| p.into()))
|
||||||
.context("failed to get path of current running executable")?;
|
.context("failed to get path of current running executable")?;
|
||||||
let bash_path = current_exe_path
|
let bash_path = current_exe_path
|
||||||
.parent()
|
.parent()
|
||||||
.expect("parent should not be `None`")
|
.expect("parent should not be `None`")
|
||||||
.join("git/bin/bash.exe");
|
.join(r"git\bin\bash.exe");
|
||||||
if bash_path.is_file() {
|
if bash_path.is_file() {
|
||||||
Some(bash_path)
|
Some(bash_path)
|
||||||
} else {
|
} else {
|
||||||
None
|
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")?;
|
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<String>>) -> Result<()> {
|
||||||
fn fastfetch_path() -> Result<Option<PathBuf>> {
|
fn fastfetch_path() -> Result<Option<PathBuf>> {
|
||||||
let fastfetch_path =
|
let fastfetch_path =
|
||||||
find_in_path("fastfetch").context("failed to check existence of `fastfetch` in `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
|
// Fall back to `fastfetch` in directory of current executable
|
||||||
let current_exe_path = env::current_exe()
|
let current_exe_path = env::current_exe()
|
||||||
|
@ -782,7 +763,6 @@ fn fastfetch_path() -> Result<Option<PathBuf>> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bundled fastfetch
|
// Bundled fastfetch
|
||||||
#[cfg(unix)]
|
|
||||||
let fastfetch_path = if fastfetch_path.is_some() {
|
let fastfetch_path = if fastfetch_path.is_some() {
|
||||||
fastfetch_path
|
fastfetch_path
|
||||||
} else {
|
} else {
|
||||||
|
@ -801,7 +781,7 @@ fn fastfetch_path() -> Result<Option<PathBuf>> {
|
||||||
let fastfetch_path = if fastfetch_path.is_some() {
|
let fastfetch_path = if fastfetch_path.is_some() {
|
||||||
fastfetch_path
|
fastfetch_path
|
||||||
} else {
|
} 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)
|
find_file(&fastfetch_path)
|
||||||
.with_context(|| format!("failed to check existence of file {fastfetch_path:?}"))?
|
.with_context(|| format!("failed to check existence of file {fastfetch_path:?}"))?
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue