Merge pull request #34 from teohhanhui/riir

Add `macchina` backend
This commit is contained in:
Teoh Han Hui 2024-07-22 02:09:00 +08:00 committed by GitHub
commit 632296dbb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 494 additions and 221 deletions

64
Cargo.lock generated
View file

@ -17,6 +17,34 @@ version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a1558bd2075d341b9ca698ec8eb6fcc55a746b1fc4255585aad5b141d918a80"
[[package]]
name = "anstream"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
dependencies = [
"anstyle",
"anstyle-parse",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
[[package]]
name = "anstyle-parse"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
dependencies = [
"utf8parse",
]
[[package]]
name = "anyhow"
version = "1.0.86"
@ -72,6 +100,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "colorchoice"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
[[package]]
name = "deranged"
version = "0.3.11"
@ -187,6 +221,7 @@ version = "1.4.11"
dependencies = [
"aho-corasick",
"ansi_colours",
"anstream",
"anyhow",
"bpaf",
"deranged",
@ -211,6 +246,7 @@ dependencies = [
"terminal_size",
"thiserror",
"time",
"toml_edit",
"tracing",
"tracing-subscriber",
"unicode-normalization",
@ -234,6 +270,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
[[package]]
name = "itertools"
version = "0.13.0"
@ -710,6 +752,22 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml_datetime"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
[[package]]
name = "toml_edit"
version = "0.22.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788"
dependencies = [
"indexmap",
"toml_datetime",
]
[[package]]
name = "topology-traits"
version = "0.1.2"
@ -797,6 +855,12 @@ version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "valuable"
version = "0.1.0"

View file

@ -14,6 +14,7 @@ license = "MIT"
[workspace.dependencies]
aho-corasick = { version = "1.1.3", default-features = false }
ansi_colours = { version = "1.2.2", default-features = false }
anstream = { version = "0.6.14", default-features = false }
anyhow = { version = "1.0.86", default-features = false }
bpaf = { version = "0.9.12", default-features = false }
deranged = { version = "0.3.11", default-features = false }
@ -38,6 +39,7 @@ terminal-colorsaurus = { version = "0.4.3", default-features = false }
terminal_size = { version = "0.3.0", default-features = false }
thiserror = { version = "1.0.61", default-features = false }
time = { version = "0.3.36", default-features = false }
toml_edit = { version = "0.22.16", default-features = false }
tracing = { version = "0.1.40", default-features = false }
tracing-subscriber = { version = "0.3.18", default-features = false }
unicode-normalization = { version = "0.1.23", default-features = false }

View file

@ -12,6 +12,7 @@ default-run = "hyfetch"
[dependencies]
aho-corasick = { workspace = true, features = ["perf-literal", "std"] }
ansi_colours = { workspace = true, features = [] }
anstream = { workspace = true, features = [], optional = true }
anyhow = { workspace = true, features = ["std"] }
bpaf = { workspace = true, features = [] }
deranged = { workspace = true, features = ["serde", "std"] }
@ -32,6 +33,7 @@ terminal-colorsaurus = { workspace = true, features = [] }
terminal_size = { workspace = true, features = [] }
thiserror = { workspace = true, features = [] }
time = { workspace = true, features = ["local-offset", "std"] }
toml_edit = { workspace = true, features = [], optional = true }
tracing = { workspace = true, features = ["attributes", "std"] }
tracing-subscriber = { workspace = true, features = ["ansi", "fmt", "smallvec", "std", "tracing-log"] }
unicode-segmentation = { workspace = true, features = [] }
@ -47,6 +49,7 @@ normpath = { workspace = true, features = [] }
same-file = { workspace = true, features = [] }
[features]
default = ["autocomplete", "color"]
default = ["autocomplete", "color", "macchina"]
autocomplete = ["bpaf/autocomplete"]
color = ["bpaf/dull-color"]
macchina = ["dep:anstream", "dep:toml_edit", "toml_edit/display"]

View file

@ -4,8 +4,6 @@ use std::io::{self, IsTerminal as _};
use anyhow::{Context as _, Result};
use hyfetch::cli_options::options;
use hyfetch::models::Config;
#[cfg(windows)]
use hyfetch::neofetch_util::ensure_git_bash;
use hyfetch::neofetch_util::{self, get_distro_ascii, ColorAlignment};
use hyfetch::presets::AssignLightness;
use hyfetch::types::Backend;
@ -33,9 +31,6 @@ fn main() -> Result<()> {
let backend = options.backend.unwrap_or(Backend::Neofetch);
let use_overlay = options.overlay;
#[cfg(windows)]
ensure_git_bash().context("failed to find git bash")?;
if options.test_print {
let (asc, _) = get_distro_ascii(distro, backend).context("failed to get distro ascii")?;
println!("{asc}");

View file

@ -56,7 +56,7 @@ pub fn options() -> OptionParser<Options> {
PRESET={{{presets}}}",
presets = Preset::VARIANTS.join(",")
))
.argument("PRESET");
.argument::<String>("PRESET");
#[cfg(feature = "autocomplete")]
let preset = preset.complete(complete_preset);
let preset = preset
@ -76,7 +76,7 @@ PRESET={{{presets}}}",
MODE={{{modes}}}",
modes = AnsiMode::VARIANTS.join(",")
))
.argument("MODE");
.argument::<String>("MODE");
#[cfg(feature = "autocomplete")]
let mode = mode.complete(complete_mode);
let mode = mode
@ -96,7 +96,7 @@ MODE={{{modes}}}",
BACKEND={{{backends}}}",
backends = Backend::VARIANTS.join(",")
))
.argument("BACKEND");
.argument::<String>("BACKEND");
#[cfg(feature = "autocomplete")]
let backend = backend.complete(complete_backend);
let backend = backend

View file

@ -26,6 +26,8 @@ use crate::color_util::{
clear_screen, color, printc, ContrastGrayscale as _, ForegroundBackground, Lightness,
NeofetchAsciiIndexedColor, PresetIndexedColor, Theme as _, ToAnsiString as _,
};
#[cfg(feature = "macchina")]
use crate::neofetch_util::macchina_path;
use crate::neofetch_util::{
ascii_size, fastfetch_path, get_distro_ascii, literal_input, ColorAlignment,
NEOFETCH_COLORS_AC, NEOFETCH_COLOR_PATTERNS,
@ -809,8 +811,9 @@ impl Config {
// Check if fastfetch is installed
let fastfetch_path = fastfetch_path().context("failed to get fastfetch path")?;
// Check if qwqfetch is installed
// TODO: qwqfetch
// Check if macchina is installed
#[cfg(feature = "macchina")]
let macchina_path = macchina_path().context("failed to get macchina path")?;
printc(
"- &bneofetch&r: Written in bash, &nbest compatibility&r on Unix systems",
@ -827,7 +830,18 @@ impl Config {
color_mode,
)
.expect("message should not contain invalid color codes");
// TODO: qwqfetch
#[cfg(feature = "macchina")]
printc(
format!(
"- &bmacchina&r: Written in Rust, &nbest performance&r \
{installed_not_installed}",
installed_not_installed = macchina_path
.map(|path| format!("&a(Installed at {path})", path = path.display()))
.unwrap_or_else(|| "&c(Not installed)".to_owned())
),
color_mode,
)
.expect("message should not contain invalid color codes");
println!();
let choice = literal_input(

View file

@ -1,6 +1,8 @@
use std::borrow::Cow;
use std::ffi::OsStr;
use std::fmt::Write as _;
#[cfg(feature = "macchina")]
use std::fs;
#[cfg(windows)]
use std::io;
use std::io::Write as _;
@ -19,7 +21,8 @@ use normpath::PathExt as _;
use same_file::is_same_file;
use serde::{Deserialize, Serialize};
use strum::AsRefStr;
use tempfile::NamedTempFile;
#[cfg(feature = "macchina")]
use toml_edit::{value, DocumentMut, Item, Table};
use tracing::debug;
use unicode_segmentation::UnicodeSegmentation as _;
@ -437,7 +440,9 @@ where
}
}
/// Gets the absolute path of the neofetch command.
/// Gets the absolute path of the [neofetch] command.
///
/// [neofetch]: https://github.com/hykilpikonna/hyfetch#running-updated-original-neofetch
pub fn neofetch_path() -> Result<Option<PathBuf>> {
if let Some(workspace_dir) = env::var_os("CARGO_WORKSPACE_DIR") {
debug!(
@ -466,193 +471,99 @@ pub fn neofetch_path() -> Result<Option<PathBuf>> {
.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: PathBuf = 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:?}"))?
};
#[cfg(windows)]
let neowofetch_path = neowofetch_path.map_or_else(
|| {
let current_exe_path: PathBuf = env::current_exe()
.and_then(|p| 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:?}"))
},
|path| Ok(Some(path)),
)?;
Ok(neowofetch_path)
}
/// Ensures git bash installation for Windows.
/// Gets the absolute path of the [fastfetch] command.
///
/// Returns the path to git bash.
#[cfg(windows)]
pub fn ensure_git_bash() -> Result<PathBuf> {
// Find `bash.exe` in `PATH`, but exclude the known bad paths
let git_bash_path = {
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")?;
match is_same_file(&bash_path, Path::new(&windir).join(r"System32\bash.exe")) {
Ok(true) => None,
Ok(false) => Some(bash_path),
Err(err) if err.kind() == io::ErrorKind::NotFound => Some(bash_path),
Err(err) => {
return Err(err).context("failed to check if paths refer to the same file");
},
}
},
_ => 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: PathBuf = 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(r"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")?;
Ok(git_bash_path)
}
/// [fastfetch]: https://github.com/fastfetch-cli/fastfetch
pub fn fastfetch_path() -> Result<Option<PathBuf>> {
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`")?
let fastfetch_path = {
#[cfg(not(windows))]
{
find_in_path("fastfetch")
.context("failed to check existence of `fastfetch` in `PATH`")?
}
#[cfg(windows)]
{
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: PathBuf = 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
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:?}"))?
};
// Fall back to `fastfetch/fastfetch.exe` in directory of current executable
#[cfg(windows)]
let fastfetch_path = if fastfetch_path.is_some() {
fastfetch_path
} else {
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:?}"))?
};
let fastfetch_path = fastfetch_path.map_or_else(
|| {
let current_exe_path: PathBuf = env::current_exe()
.and_then(|p| 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 = current_exe_dir_path.join("fastfetch/fastfetch.exe");
find_file(&fastfetch_path)
.with_context(|| format!("failed to check existence of file {fastfetch_path:?}"))
},
|path| Ok(Some(path)),
)?;
Ok(fastfetch_path)
}
/// Gets the absolute path of the [macchina] command.
///
/// [macchina]: https://github.com/Macchina-CLI/macchina
#[cfg(feature = "macchina")]
pub fn macchina_path() -> Result<Option<PathBuf>> {
let macchina_path = {
#[cfg(not(windows))]
{
find_in_path("macchina").context("failed to check existence of `macchina` in `PATH`")?
}
#[cfg(windows)]
{
find_in_path("macchina.exe")
.context("failed to check existence of `macchina.exe` in `PATH`")?
}
};
// Fall back to `macchina.exe` in directory of current executable
#[cfg(windows)]
let macchina_path = macchina_path.map_or_else(
|| {
let current_exe_path: PathBuf = env::current_exe()
.and_then(|p| 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 macchina_path = current_exe_dir_path.join("macchina.exe");
find_file(&macchina_path)
.with_context(|| format!("failed to check existence of file {macchina_path:?}"))
},
|path| Ok(Some(path)),
)?;
Ok(macchina_path)
}
/// Gets the distro ascii of the current distro. Or if distro is specified, get
/// the specific distro's ascii art instead.
#[tracing::instrument(level = "debug")]
@ -705,8 +616,9 @@ pub fn run(asc: String, backend: Backend, args: Option<&Vec<String>>) -> Result<
Backend::FastfetchOld => {
run_fastfetch(asc, args, true).context("failed to run fastfetch")?;
},
Backend::Qwqfetch => {
todo!();
#[cfg(feature = "macchina")]
Backend::Macchina => {
run_macchina(asc, args).context("failed to run macchina")?;
},
}
@ -801,6 +713,106 @@ where
.join("\n"))
}
/// Ensures git bash installation for Windows.
///
/// Returns the path of git bash.
#[cfg(windows)]
fn ensure_git_bash() -> Result<PathBuf> {
// Find `bash.exe` in `PATH`, but exclude the known bad paths
let git_bash_path = find_in_path("bash.exe")
.context("failed to check existence of `bash.exe` in `PATH`")?
.map_or_else(
|| Ok(None),
|bash_path| {
if bash_path.ends_with(r"Git\usr\bin\bash.exe") {
// See https://stackoverflow.com/a/58418686/1529493
Ok(None)
} else {
// See https://github.com/hykilpikonna/hyfetch/issues/233
let windir = env::var_os("windir")
.context("`windir` environment variable is not set or invalid")?;
match is_same_file(&bash_path, Path::new(&windir).join(r"System32\bash.exe")) {
Ok(true) => Ok(None),
Ok(false) => Ok(Some(bash_path)),
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(Some(bash_path)),
Err(err) => {
Err(err).context("failed to check if paths refer to the same file")
},
}
}
},
)?;
// Detect any Git for Windows installation in `PATH`
let git_bash_path = git_bash_path.map_or_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() {
Ok(Some(bash_path))
} else {
Ok(None)
}
},
_ => Ok(None),
}
},
|path| Ok(Some(path)),
)?;
// 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 = git_bash_path.map_or_else(
|| {
let current_exe_path: PathBuf = 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(r"git\bin\bash.exe");
if bash_path.is_file() {
Ok(Some(bash_path))
} else {
Ok(None)
}
},
|path| Ok(Some(path)),
)?;
let git_bash_path = git_bash_path.context("failed to find git bash executable")?;
Ok(git_bash_path)
}
/// Runs neofetch command, returning the piped stdout output.
fn run_neofetch_command_piped<S>(args: &[S]) -> Result<String>
where
@ -825,8 +837,10 @@ fn make_neofetch_command<S>(args: &[S]) -> Result<Command>
where
S: AsRef<OsStr>,
{
let neofetch_path = neofetch_path().context("failed to get neofetch path")?;
let neofetch_path = neofetch_path.context("neofetch command not found")?;
// Find neofetch script
let neofetch_path = neofetch_path()
.context("failed to get neofetch path")?
.context("neofetch command not found")?;
debug!(?neofetch_path, "neofetch path");
@ -871,9 +885,10 @@ fn make_fastfetch_command<S>(args: &[S]) -> Result<Command>
where
S: AsRef<OsStr>,
{
// Find fastfetch binary
let fastfetch_path = fastfetch_path().context("failed to get fastfetch path")?;
let fastfetch_path = fastfetch_path.context("fastfetch command not found")?;
// Find fastfetch executable
let fastfetch_path = fastfetch_path()
.context("failed to get fastfetch path")?
.context("fastfetch command not found")?;
debug!(?fastfetch_path, "fastfetch path");
@ -882,6 +897,44 @@ where
Ok(command)
}
/// Runs macchina command, returning the piped stdout output.
#[cfg(feature = "macchina")]
fn run_macchina_command_piped<S>(args: &[S]) -> Result<String>
where
S: AsRef<OsStr> + fmt::Debug,
{
let mut command = make_macchina_command(args)?;
let output = command
.output()
.context("failed to execute macchina as child process")?;
debug!(?output, "macchina output");
process_command_status(&output.status).context("macchina command exited with error")?;
let out = String::from_utf8(output.stdout)
.context("failed to process macchina output as it contains invalid UTF-8")?
.trim()
.to_owned();
Ok(out)
}
#[cfg(feature = "macchina")]
fn make_macchina_command<S>(args: &[S]) -> Result<Command>
where
S: AsRef<OsStr>,
{
// Find macchina executable
let macchina_path = macchina_path()
.context("failed to get macchina path")?
.context("macchina command not found")?;
debug!(?macchina_path, "macchina path");
let mut command = Command::new(macchina_path);
command.args(args);
Ok(command)
}
#[tracing::instrument(level = "debug")]
fn get_distro_name(backend: Backend) -> Result<String> {
match backend {
@ -897,34 +950,104 @@ fn get_distro_name(backend: Backend) -> Result<String> {
" ",
])
.context("failed to get distro name from fastfetch"),
Backend::Qwqfetch => {
todo!()
#[cfg(feature = "macchina")]
Backend::Macchina => {
// Write ascii art to temp file
let asc_file_path = {
let mut temp_file = tempfile::Builder::new()
.suffix("ascii.txt")
.tempfile()
.context("failed to create temp file for ascii art")?;
temp_file
.write_all(b"\t\n\t\n")
.context("failed to write ascii art to temp file")?;
temp_file.into_temp_path()
};
// Write macchina theme to temp file
let theme_file_path = {
let project_dirs = directories::ProjectDirs::from("", "", "macchina")
.context("failed to get base dirs")?;
let themes_path = project_dirs.config_dir().join("themes");
fs::create_dir_all(&themes_path).with_context(|| {
format!("failed to create macchina themes dir {themes_path:?}")
})?;
let mut temp_file = tempfile::Builder::new()
.suffix("theme.toml")
.tempfile_in(themes_path)
.context("failed to create temp file for macchina theme")?;
let theme_doc = {
let mut doc = DocumentMut::new();
doc["spacing"] = value(0);
doc["padding"] = value(0);
// See https://github.com/Macchina-CLI/macchina/issues/319
// doc["hide_ascii"] = value(true);
doc["separator"] = value("");
doc["custom_ascii"] = Item::Table(Table::from_iter([(
"path",
&*asc_file_path.to_string_lossy(),
)]));
doc["keys"] = Item::Table(Table::from_iter([("os", ""), ("distro", "")]));
doc
};
debug!(%theme_doc, "macchina theme");
temp_file
.write_all(theme_doc.to_string().as_bytes())
.context("failed to write macchina theme to temp file")?;
temp_file.into_temp_path()
};
let args: [&OsStr; 4] = [
"--show".as_ref(),
if cfg!(target_os = "linux") {
"distribution"
} else {
"operating-system"
}
.as_ref(),
"--theme".as_ref(),
theme_file_path
.file_stem()
.expect("file name should not be `None`"),
];
run_macchina_command_piped(&args[..])
.map(|s| {
anstream::adapter::strip_str(&s)
.to_string()
.trim()
.to_owned()
})
.context("failed to get distro name from macchina")
},
}
}
/// Runs neofetch with colors.
/// Runs neofetch with custom ascii art.
#[tracing::instrument(level = "debug", skip(asc))]
fn run_neofetch(asc: String, args: Option<&Vec<String>>) -> Result<()> {
// Escape backslashes here because backslashes are escaped in neofetch for
// printf
let asc = asc.replace('\\', r"\\");
// 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")?;
// Write ascii art to temp file
let asc_file_path = {
let mut temp_file = tempfile::Builder::new()
.suffix("ascii.txt")
.tempfile()
.context("failed to create temp file for ascii art")?;
temp_file
.write_all(asc.as_bytes())
.context("failed to write ascii art to temp file")?;
temp_file.into_temp_path()
};
// Call neofetch with the temp file
let temp_file_path = temp_file.into_temp_path();
// Call neofetch
let args = {
let mut v: Vec<Cow<OsStr>> = vec![
OsStr::new("--ascii").into(),
OsStr::new("--source").into(),
OsStr::new(&temp_file_path).into(),
OsStr::new("--ascii-colors").into(),
OsStr::new(&asc_file_path).into(),
OsStr::new("--ascii_colors").into(),
];
if let Some(args) = args {
v.extend(args.iter().map(|arg| OsStr::new(arg).into()));
@ -943,22 +1066,26 @@ fn run_neofetch(asc: String, args: Option<&Vec<String>>) -> Result<()> {
Ok(())
}
/// Runs fastfetch with colors.
/// Runs fastfetch with custom ascii art.
#[tracing::instrument(level = "debug", skip(asc))]
fn run_fastfetch(asc: String, args: Option<&Vec<String>>, legacy: bool) -> Result<()> {
// 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")?;
// Write ascii art to temp file
let asc_file_path = {
let mut temp_file = tempfile::Builder::new()
.suffix("ascii.txt")
.tempfile()
.context("failed to create temp file for ascii art")?;
temp_file
.write_all(asc.as_bytes())
.context("failed to write ascii art to temp file")?;
temp_file.into_temp_path()
};
// Call fastfetch with the temp file
let temp_file_path = temp_file.into_temp_path();
// Call fastfetch
let args = {
let mut v: Vec<Cow<OsStr>> = vec![
OsStr::new(if legacy { "--raw" } else { "--file-raw" }).into(),
OsStr::new(&temp_file_path).into(),
OsStr::new(&asc_file_path).into(),
];
if let Some(args) = args {
v.extend(args.iter().map(|arg| OsStr::new(arg).into()));
@ -982,3 +1109,70 @@ fn run_fastfetch(asc: String, args: Option<&Vec<String>>, legacy: bool) -> Resul
Ok(())
}
/// Runs macchina with custom ascii art.
#[cfg(feature = "macchina")]
#[tracing::instrument(level = "debug", skip(asc))]
fn run_macchina(asc: String, args: Option<&Vec<String>>) -> Result<()> {
// Write ascii art to temp file
let asc_file_path = {
let mut temp_file = tempfile::Builder::new()
.suffix("ascii.txt")
.tempfile()
.context("failed to create temp file for ascii art")?;
temp_file
.write_all(asc.as_bytes())
.context("failed to write ascii art to temp file")?;
temp_file.into_temp_path()
};
// Write macchina theme to temp file
let theme_file_path = {
let project_dirs = directories::ProjectDirs::from("", "", "macchina")
.context("failed to get base dirs")?;
let themes_path = project_dirs.config_dir().join("themes");
fs::create_dir_all(&themes_path)
.with_context(|| format!("failed to create macchina themes dir {themes_path:?}"))?;
let mut temp_file = tempfile::Builder::new()
.suffix("theme.toml")
.tempfile_in(themes_path)
.context("failed to create temp file for macchina theme")?;
let theme_doc = {
let mut doc = DocumentMut::new();
doc["custom_ascii"] = Item::Table(Table::from_iter([(
"path",
&*asc_file_path.to_string_lossy(),
)]));
doc
};
debug!(%theme_doc, "macchina theme");
temp_file
.write_all(theme_doc.to_string().as_bytes())
.context("failed to write macchina theme to temp file")?;
temp_file.into_temp_path()
};
let args = {
let mut v: Vec<Cow<OsStr>> = vec![
OsStr::new("--theme").into(),
theme_file_path
.file_stem()
.expect("file name should not be `None`")
.into(),
];
if let Some(args) = args {
v.extend(args.iter().map(|arg| OsStr::new(arg).into()));
}
v
};
let mut command = make_macchina_command(&args[..])?;
debug!(?command, "macchina command");
let status = command
.status()
.context("failed to execute macchina command as child process")?;
process_command_status(&status).context("macchina command exited with error")?;
Ok(())
}

View file

@ -55,7 +55,8 @@ pub enum Backend {
Neofetch,
Fastfetch,
FastfetchOld,
Qwqfetch,
#[cfg(feature = "macchina")]
Macchina,
}
// See https://github.com/Peternator7/strum/issues/244

View file

@ -52,7 +52,7 @@ where
/// Finds a command in `PATH`.
///
/// Returns the canonicalized / normalized absolute path to the command.
/// Returns the canonicalized / normalized absolute path of the command.
pub fn find_in_path<P>(program: P) -> Result<Option<PathBuf>>
where
P: AsRef<Path>,
@ -80,7 +80,7 @@ where
/// Finds a file.
///
/// Returns the canonicalized / normalized absolute path to the file.
/// Returns the canonicalized / normalized absolute path of the file.
pub fn find_file<P>(path: P) -> Result<Option<PathBuf>>
where
P: AsRef<Path>,