commit
ca583bddcb
5 changed files with 279 additions and 113 deletions
|
@ -64,7 +64,8 @@ fn main() -> Result<()> {
|
|||
println!();
|
||||
|
||||
if !june_path.is_file() {
|
||||
fs::create_dir_all(cache_path).context("failed to create cache dir")?;
|
||||
fs::create_dir_all(&cache_path)
|
||||
.with_context(|| format!("failed to create cache dir {cache_path:?}"))?;
|
||||
File::create(&june_path)
|
||||
.with_context(|| format!("failed to create file {june_path:?}"))?;
|
||||
}
|
||||
|
@ -116,7 +117,7 @@ fn main() -> Result<()> {
|
|||
let asc = color_align
|
||||
.recolor_ascii(asc, color_profile, color_mode, config.light_dark)
|
||||
.context("failed to recolor ascii")?;
|
||||
neofetch_util::run(asc, backend, args).context("failed to run")?;
|
||||
neofetch_util::run(asc, backend, args)?;
|
||||
|
||||
if options.ask_exit {
|
||||
print!("Press any key to exit...");
|
||||
|
|
|
@ -248,7 +248,7 @@ where
|
|||
let start = m.end();
|
||||
let end = msg[start..]
|
||||
.find(')')
|
||||
.ok_or_else(|| anyhow!("missing closing brace for color code"));
|
||||
.context("missing closing brace for color code");
|
||||
let end = match end {
|
||||
Ok(end) => end,
|
||||
Err(err) => {
|
||||
|
@ -306,7 +306,7 @@ where
|
|||
true
|
||||
});
|
||||
if let Some(err) = ret_err {
|
||||
Err(err)?;
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
Ok(dst)
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::Write;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::ExitStatusExt as _;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, ExitStatus};
|
||||
use std::process::Command;
|
||||
use std::sync::OnceLock;
|
||||
use std::{env, fmt};
|
||||
|
||||
|
@ -24,6 +22,7 @@ use crate::color_util::{
|
|||
use crate::distros::Distro;
|
||||
use crate::presets::ColorProfile;
|
||||
use crate::types::{AnsiMode, Backend, LightDark};
|
||||
use crate::utils::{find_file, find_in_path, process_command_status};
|
||||
|
||||
const NEOFETCH_COLOR_PATTERNS: [&str; 6] = ["${c1}", "${c2}", "${c3}", "${c4}", "${c5}", "${c6}"];
|
||||
static NEOFETCH_COLORS_AC: OnceLock<AhoCorasick> = OnceLock::new();
|
||||
|
@ -287,51 +286,59 @@ impl ColorAlignment {
|
|||
}
|
||||
|
||||
/// Gets the absolute path of the neofetch command.
|
||||
pub fn get_command_path() -> Result<PathBuf> {
|
||||
pub fn neofetch_path() -> Result<Option<PathBuf>> {
|
||||
if let Some(workspace_dir) = env::var_os("CARGO_WORKSPACE_DIR") {
|
||||
let path = Path::new(&workspace_dir);
|
||||
if path.exists() {
|
||||
let path = path.join("neofetch");
|
||||
match path.try_exists() {
|
||||
Ok(true) => {
|
||||
#[cfg(not(windows))]
|
||||
return path.canonicalize().context("failed to canonicalize path");
|
||||
#[cfg(windows)]
|
||||
return path
|
||||
.normalize()
|
||||
.map(|p| p.into())
|
||||
.context("failed to normalize path");
|
||||
},
|
||||
Ok(false) => {
|
||||
Err(anyhow!("{path:?} does not exist or is not readable"))?;
|
||||
},
|
||||
Err(err) => {
|
||||
Err(err)
|
||||
.with_context(|| format!("failed to check for existence of {path:?}"))?;
|
||||
},
|
||||
}
|
||||
}
|
||||
debug!(
|
||||
?workspace_dir,
|
||||
"CARGO_WORKSPACE_DIR env var is set; using neofetch from project directory"
|
||||
);
|
||||
let workspace_path = Path::new(&workspace_dir);
|
||||
let workspace_path = match workspace_path.try_exists() {
|
||||
Ok(true) => workspace_path,
|
||||
Ok(false) => {
|
||||
return Err(anyhow!(
|
||||
"{workspace_path:?} does not exist or is not readable"
|
||||
));
|
||||
},
|
||||
Err(err) => {
|
||||
return Err(err).with_context(|| {
|
||||
format!("failed to check for existence of {workspace_path:?}")
|
||||
});
|
||||
},
|
||||
};
|
||||
let neofetch_path = workspace_path.join("neofetch");
|
||||
return find_file(&neofetch_path)
|
||||
.with_context(|| format!("failed to check existence of file {neofetch_path:?}"));
|
||||
}
|
||||
|
||||
let Some(path_env) = env::var_os("PATH") else {
|
||||
return Err(anyhow!("`PATH` env var is not set or invalid"));
|
||||
let neowofetch_path = find_in_path("neowofetch")
|
||||
.context("failed to check existence of `neowofetch` in `PATH`")?;
|
||||
|
||||
// Fall back to `neowofetch` in directory of current executable
|
||||
let neowofetch_path = if neowofetch_path.is_some() {
|
||||
neowofetch_path
|
||||
} else {
|
||||
let current_exe_path = env::current_exe()
|
||||
.and_then(|p| {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
p.canonicalize()
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
p.normalize().map(|p| p.into())
|
||||
}
|
||||
})
|
||||
.context("failed to get path of current running executable")?;
|
||||
let neowofetch_path = current_exe_path
|
||||
.parent()
|
||||
.expect("parent should not be `None`")
|
||||
.join("neowofetch");
|
||||
find_file(&neowofetch_path)
|
||||
.with_context(|| format!("failed to check existence of file {neowofetch_path:?}"))?
|
||||
};
|
||||
|
||||
for search_path in env::split_paths(&path_env) {
|
||||
let path = search_path.join("neowofetch");
|
||||
if !path.is_file() {
|
||||
continue;
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
return path.canonicalize().context("failed to canonicalize path");
|
||||
#[cfg(windows)]
|
||||
return path
|
||||
.normalize()
|
||||
.map(|p| p.into())
|
||||
.context("failed to normalize path");
|
||||
}
|
||||
|
||||
Err(anyhow!("neofetch command not found"))
|
||||
Ok(neowofetch_path)
|
||||
}
|
||||
|
||||
/// Ensures git bash installation for Windows.
|
||||
|
@ -342,16 +349,12 @@ pub fn ensure_git_bash() -> Result<PathBuf> {
|
|||
let git_bash_path = {
|
||||
// Bundled git bash
|
||||
let current_exe_path = env::current_exe()
|
||||
.and_then(|p| {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
p.canonicalize()
|
||||
}
|
||||
#[cfg(windows)]
|
||||
p.normalize().map(|p| p.into())
|
||||
})
|
||||
.and_then(|p| p.normalize().map(|p| p.into()))
|
||||
.context("failed to get path of current running executable")?;
|
||||
let bash_path = current_exe_path.join("git/bin/bash.exe");
|
||||
let bash_path = current_exe_path
|
||||
.parent()
|
||||
.expect("parent should not be `None`")
|
||||
.join("git/bin/bash.exe");
|
||||
if bash_path.is_file() {
|
||||
Some(bash_path)
|
||||
} else {
|
||||
|
@ -377,9 +380,7 @@ pub fn ensure_git_bash() -> Result<PathBuf> {
|
|||
}
|
||||
});
|
||||
|
||||
let Some(git_bash_path) = git_bash_path else {
|
||||
return Err(anyhow!("failed to find git bash executable"));
|
||||
};
|
||||
let git_bash_path = git_bash_path.context("failed to find git bash executable")?;
|
||||
|
||||
Ok(git_bash_path)
|
||||
}
|
||||
|
@ -421,17 +422,16 @@ where
|
|||
Ok((normalize_ascii(asc), None))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(asc))]
|
||||
pub fn run(asc: String, backend: Backend, args: Option<&Vec<String>>) -> Result<()> {
|
||||
match backend {
|
||||
Backend::Neofetch => {
|
||||
run_neofetch(asc, args).context("failed to run neofetch")?;
|
||||
},
|
||||
Backend::Fastfetch => {
|
||||
todo!();
|
||||
run_fastfetch(asc, args, false).context("failed to run fastfetch")?;
|
||||
},
|
||||
Backend::FastfetchOld => {
|
||||
todo!();
|
||||
run_fastfetch(asc, args, true).context("failed to run fastfetch")?;
|
||||
},
|
||||
Backend::Qwqfetch => {
|
||||
todo!();
|
||||
|
@ -512,9 +512,9 @@ where
|
|||
// line starts with neofetch color code, do nothing
|
||||
},
|
||||
_ => {
|
||||
new.push_str(last.ok_or_else(|| {
|
||||
anyhow!("failed to find neofetch color code from a previous line")
|
||||
})?);
|
||||
new.push_str(
|
||||
last.context("failed to find neofetch color code from a previous line")?,
|
||||
);
|
||||
},
|
||||
}
|
||||
new.push_str(line);
|
||||
|
@ -529,29 +529,12 @@ where
|
|||
Ok(new)
|
||||
}
|
||||
|
||||
/// Runs neofetch command.
|
||||
#[tracing::instrument(level = "debug")]
|
||||
fn run_neofetch_command<S>(args: &[S]) -> Result<()>
|
||||
where
|
||||
S: AsRef<OsStr> + fmt::Debug,
|
||||
{
|
||||
let mut command = make_neofetch_command(args).context("failed to make neofetch command")?;
|
||||
|
||||
let status = command
|
||||
.status()
|
||||
.context("failed to execute neofetch command as child process")?;
|
||||
process_command_status(&status).context("neofetch command exited with error")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs neofetch command, returning the piped stdout output.
|
||||
#[tracing::instrument(level = "debug")]
|
||||
fn run_neofetch_command_piped<S>(args: &[S]) -> Result<String>
|
||||
where
|
||||
S: AsRef<OsStr> + fmt::Debug,
|
||||
{
|
||||
let mut command = make_neofetch_command(args).context("failed to make neofetch command")?;
|
||||
let mut command = make_neofetch_command(args)?;
|
||||
|
||||
let output = command
|
||||
.output()
|
||||
|
@ -570,7 +553,8 @@ fn make_neofetch_command<S>(args: &[S]) -> Result<Command>
|
|||
where
|
||||
S: AsRef<OsStr>,
|
||||
{
|
||||
let neofetch_path = get_command_path().context("failed to get neofetch command path")?;
|
||||
let neofetch_path = neofetch_path().context("failed to get neofetch path")?;
|
||||
let neofetch_path = neofetch_path.context("neofetch command not found")?;
|
||||
|
||||
debug!(?neofetch_path, "neofetch path");
|
||||
|
||||
|
@ -591,29 +575,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn process_command_status(status: &ExitStatus) -> Result<()> {
|
||||
if status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let err = if let Some(code) = status.code() {
|
||||
anyhow!("child process exited with status code: {code}")
|
||||
} else {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
anyhow!(
|
||||
"child process terminated by signal: {}",
|
||||
status
|
||||
.signal()
|
||||
.expect("either one of status code or signal should be set")
|
||||
)
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
unimplemented!("status code not expected to be `None` on non-Unix platforms")
|
||||
};
|
||||
Err(err)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
fn get_distro_name() -> Result<String> {
|
||||
run_neofetch_command_piped(&["ascii_distro_name"])
|
||||
|
@ -651,7 +612,111 @@ fn run_neofetch(asc: String, args: Option<&Vec<String>>) -> Result<()> {
|
|||
}
|
||||
v
|
||||
};
|
||||
run_neofetch_command(&args).context("failed to run neofetch command")?;
|
||||
let mut command = make_neofetch_command(&args)?;
|
||||
|
||||
debug!(?command, "neofetch command");
|
||||
|
||||
let status = command
|
||||
.status()
|
||||
.context("failed to execute neofetch command as child process")?;
|
||||
process_command_status(&status).context("neofetch command exited with error")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fastfetch_path() -> Result<Option<PathBuf>> {
|
||||
let fastfetch_path =
|
||||
find_in_path("fastfetch").context("failed to check existence of `fastfetch` in `PATH`")?;
|
||||
|
||||
// Fall back to `fastfetch` in directory of current executable
|
||||
let current_exe_path = env::current_exe()
|
||||
.and_then(|p| {
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
p.canonicalize()
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
p.normalize().map(|p| p.into())
|
||||
}
|
||||
})
|
||||
.context("failed to get path of current running executable")?;
|
||||
let current_exe_dir_path = current_exe_path
|
||||
.parent()
|
||||
.expect("parent should not be `None`");
|
||||
let fastfetch_path = if fastfetch_path.is_some() {
|
||||
fastfetch_path
|
||||
} else {
|
||||
let fastfetch_path = current_exe_dir_path.join("fastfetch");
|
||||
find_file(&fastfetch_path)
|
||||
.with_context(|| format!("failed to check existence of file {fastfetch_path:?}"))?
|
||||
};
|
||||
|
||||
// Bundled fastfetch
|
||||
#[cfg(unix)]
|
||||
let fastfetch_path = if fastfetch_path.is_some() {
|
||||
fastfetch_path
|
||||
} else {
|
||||
let fastfetch_path = current_exe_dir_path.join("fastfetch/usr/bin/fastfetch");
|
||||
find_file(&fastfetch_path)
|
||||
.with_context(|| format!("failed to check existence of file {fastfetch_path:?}"))?
|
||||
};
|
||||
let fastfetch_path = if fastfetch_path.is_some() {
|
||||
fastfetch_path
|
||||
} else {
|
||||
let fastfetch_path = current_exe_dir_path.join("fastfetch/fastfetch");
|
||||
find_file(&fastfetch_path)
|
||||
.with_context(|| format!("failed to check existence of file {fastfetch_path:?}"))?
|
||||
};
|
||||
#[cfg(windows)]
|
||||
let fastfetch_path = if fastfetch_path.is_some() {
|
||||
fastfetch_path
|
||||
} else {
|
||||
let fastfetch_path = current_exe_dir_path.join("fastfetch/fastfetch.exe");
|
||||
find_file(&fastfetch_path)
|
||||
.with_context(|| format!("failed to check existence of file {fastfetch_path:?}"))?
|
||||
};
|
||||
|
||||
Ok(fastfetch_path)
|
||||
}
|
||||
|
||||
/// Runs fastfetch with colors.
|
||||
#[tracing::instrument(level = "debug", skip(asc))]
|
||||
fn run_fastfetch(asc: String, args: Option<&Vec<String>>, legacy: bool) -> Result<()> {
|
||||
// Find fastfetch binary
|
||||
let fastfetch_path = fastfetch_path().context("failed to get fastfetch path")?;
|
||||
let fastfetch_path = fastfetch_path.context("fastfetch command not found")?;
|
||||
|
||||
debug!(?fastfetch_path, "fastfetch path");
|
||||
|
||||
// Write temp file
|
||||
let mut temp_file =
|
||||
NamedTempFile::with_prefix("ascii.txt").context("failed to create temp file for ascii")?;
|
||||
temp_file
|
||||
.write_all(asc.as_bytes())
|
||||
.context("failed to write ascii to temp file")?;
|
||||
|
||||
// Call fastfetch with the temp file
|
||||
let temp_file_path = temp_file.into_temp_path();
|
||||
let mut command = Command::new(fastfetch_path);
|
||||
command.arg(if legacy { "--raw" } else { "--file-raw" });
|
||||
command.arg(&temp_file_path);
|
||||
if let Some(args) = args {
|
||||
command.args(args);
|
||||
}
|
||||
|
||||
debug!(?command, "fastfetch command");
|
||||
|
||||
let status = command
|
||||
.status()
|
||||
.context("failed to execute fastfetch command as child process")?;
|
||||
if status.code() == Some(144) {
|
||||
eprintln!(
|
||||
"exit code 144 detected; please upgrade fastfetch to >=1.8.0 or use the \
|
||||
'fastfetch-old' backend"
|
||||
);
|
||||
}
|
||||
process_command_status(&status).context("fastfetch command exited with error")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use palette::num::ClampAssign;
|
|||
use palette::{Hsl, IntoColorMut, LinSrgb, Srgb};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumString, VariantNames};
|
||||
use tracing::debug;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::color_util::{ForegroundBackground, Lightness, ToAnsiString};
|
||||
|
@ -413,9 +414,10 @@ impl ColorProfile {
|
|||
/// `colors[i]` appears)
|
||||
pub fn with_weights(&self, weights: Vec<u8>) -> Result<Self> {
|
||||
if weights.len() != self.colors.len() {
|
||||
Err(anyhow!(
|
||||
debug!(?weights, ?self.colors, "length mismatch between `weights` and `colors`");
|
||||
return Err(anyhow!(
|
||||
"`weights` should have the same number of elements as `colors`"
|
||||
))?;
|
||||
));
|
||||
}
|
||||
|
||||
let mut weighted_colors = vec![];
|
||||
|
@ -488,7 +490,7 @@ impl ColorProfile {
|
|||
let length = txt.len();
|
||||
let length: u8 = length.try_into().expect("`length` should fit in `u8`");
|
||||
self.with_length(length)
|
||||
.context("failed to spread color profile to length")?
|
||||
.with_context(|| format!("failed to spread color profile to length {length}"))?
|
||||
};
|
||||
|
||||
let mut buf = String::new();
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
use std::path::PathBuf;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::process::ExitStatusExt as _;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitStatus;
|
||||
use std::{env, fs, io};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use directories::ProjectDirs;
|
||||
#[cfg(windows)]
|
||||
use normpath::PathExt as _;
|
||||
use tracing::debug;
|
||||
|
||||
pub fn get_cache_path() -> Result<PathBuf> {
|
||||
let path = ProjectDirs::from("", "", "hyfetch")
|
||||
|
@ -11,6 +18,97 @@ pub fn get_cache_path() -> Result<PathBuf> {
|
|||
Ok(path)
|
||||
}
|
||||
|
||||
/// Finds a command in `PATH`.
|
||||
///
|
||||
/// Returns the canonicalized / normalized absolute path to the command.
|
||||
pub fn find_in_path<P>(program: P) -> Result<Option<PathBuf>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let program = program.as_ref();
|
||||
|
||||
// Only accept program name, i.e. a relative path with one component
|
||||
if program.parent() != Some(Path::new("")) {
|
||||
return Err(anyhow!("invalid command name {program:?}"));
|
||||
};
|
||||
|
||||
let path_env = env::var_os("PATH").context("`PATH` env var is not set or invalid")?;
|
||||
|
||||
for search_path in env::split_paths(&path_env) {
|
||||
let path = search_path.join(program);
|
||||
let path = find_file(&path)
|
||||
.with_context(|| format!("failed to check existence of file {path:?}"))?;
|
||||
if path.is_some() {
|
||||
return Ok(path);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Finds a file.
|
||||
///
|
||||
/// Returns the canonicalized / normalized absolute path to the file.
|
||||
pub fn find_file<P>(path: P) -> Result<Option<PathBuf>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let path = path.as_ref();
|
||||
|
||||
let metadata = match fs::metadata(path) {
|
||||
Ok(metadata) => metadata,
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => {
|
||||
return Ok(None);
|
||||
},
|
||||
Err(err) => {
|
||||
return Err(err).with_context(|| format!("failed to get metadata for {path:?}"));
|
||||
},
|
||||
};
|
||||
|
||||
if !metadata.is_file() {
|
||||
debug!(?path, "path exists but is not a file");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
path.canonicalize()
|
||||
.with_context(|| format!("failed to canonicalize path {path:?}"))
|
||||
.map(Some)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
path.normalize()
|
||||
.with_context(|| format!("failed to normalize path {path:?}"))
|
||||
.map(|p| Some(p.into()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_command_status(status: &ExitStatus) -> Result<()> {
|
||||
if status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let err = if let Some(code) = status.code() {
|
||||
anyhow!("child process exited with status code: {code}")
|
||||
} else {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
anyhow!(
|
||||
"child process terminated by signal: {}",
|
||||
status
|
||||
.signal()
|
||||
.expect("either one of status code or signal should be set")
|
||||
)
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
unimplemented!("status code not expected to be `None` on non-Unix platforms")
|
||||
}
|
||||
};
|
||||
Err(err)
|
||||
}
|
||||
|
||||
pub(crate) mod index_map_serde {
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
|
|
Loading…
Reference in a new issue