From d11f6f0a9f4308c6da8112df65cf1434e5814fdf Mon Sep 17 00:00:00 2001
From: Teoh Han Hui <teohhanhui@gmail.com>
Date: Sat, 13 Jul 2024 04:04:14 +0800
Subject: [PATCH] Use selected backend to get distro name

---
 crates/hyfetch/src/bin/hyfetch.rs   |  85 +++++++++++++--------
 crates/hyfetch/src/color_util.rs    |  10 +--
 crates/hyfetch/src/models.rs        |  12 +--
 crates/hyfetch/src/neofetch_util.rs | 114 ++++++++++++++++++++--------
 crates/hyfetch/src/presets.rs       |  14 ++--
 crates/hyfetch/src/types.rs         |   2 +-
 6 files changed, 154 insertions(+), 83 deletions(-)

diff --git a/crates/hyfetch/src/bin/hyfetch.rs b/crates/hyfetch/src/bin/hyfetch.rs
index adc6ba7b..758741bd 100644
--- a/crates/hyfetch/src/bin/hyfetch.rs
+++ b/crates/hyfetch/src/bin/hyfetch.rs
@@ -12,7 +12,7 @@ use hyfetch::models::Config;
 use hyfetch::neofetch_util::ensure_git_bash;
 use hyfetch::neofetch_util::{self, ascii_size, get_distro_ascii, literal_input, ColorAlignment};
 use hyfetch::presets::{AssignLightness, ColorProfile, Preset};
-use hyfetch::types::{AnsiMode, LightDark};
+use hyfetch::types::{AnsiMode, Backend, TerminalTheme};
 use hyfetch::utils::get_cache_path;
 use palette::Srgb;
 use strum::{EnumCount, VariantArray, VariantNames};
@@ -28,34 +28,54 @@ fn main() -> Result<()> {
 
     let options = options().run();
 
-    init_tracing_subsriber(options.debug).context("failed to init tracing subscriber")?;
+    let debug_mode = options.debug;
+
+    init_tracing_subsriber(debug_mode).context("failed to init tracing subscriber")?;
 
     debug!(?options, "CLI options");
 
     // Use a custom distro
     let distro = options.distro.as_ref();
 
+    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).context("failed to get distro ascii")?;
+        let (asc, _) = get_distro_ascii(distro, backend).context("failed to get distro ascii")?;
         println!("{asc}");
         return Ok(());
     }
 
     let config = if options.config {
-        create_config(distro, &options.config_file, options.overlay, options.debug)
-            .context("failed to create config")?
+        create_config(
+            &options.config_file,
+            distro,
+            backend,
+            use_overlay,
+            debug_mode,
+        )
+        .context("failed to create config")?
     } else if let Some(config) =
         read_config(&options.config_file).context("failed to read config")?
     {
         config
     } else {
-        create_config(distro, &options.config_file, options.overlay, options.debug)
-            .context("failed to create config")?
+        create_config(
+            &options.config_file,
+            distro,
+            backend,
+            use_overlay,
+            debug_mode,
+        )
+        .context("failed to create config")?
     };
 
+    let color_mode = options.mode.unwrap_or(config.mode);
+    let theme = config.light_dark;
+
     // Check if it's June (pride month)
     let now =
         OffsetDateTime::now_local().context("failed to get current datetime in local timezone")?;
@@ -83,7 +103,6 @@ fn main() -> Result<()> {
     // Use a custom distro
     let distro = options.distro.as_ref().or(config.distro.as_ref());
 
-    let color_mode = options.mode.unwrap_or(config.mode);
     let backend = options.backend.unwrap_or(config.backend);
     let args = options.args.as_ref().or(config.args.as_ref());
 
@@ -98,7 +117,7 @@ fn main() -> Result<()> {
     } else if let Some(lightness) = options.lightness {
         color_profile.with_lightness(AssignLightness::Replace(lightness))
     } else {
-        color_profile.with_lightness_dl(config.lightness(), config.light_dark, options.overlay)
+        color_profile.with_lightness_adaptive(config.lightness(), theme, use_overlay)
     };
     debug!(?color_profile, "lightened color profile");
 
@@ -109,7 +128,7 @@ fn main() -> Result<()> {
             None,
         )
     } else {
-        get_distro_ascii(distro).context("failed to get distro ascii")?
+        get_distro_ascii(distro, backend).context("failed to get distro ascii")?
     };
     let color_align = if fore_back.is_some() {
         match config.color_align {
@@ -124,7 +143,7 @@ fn main() -> Result<()> {
         config.color_align
     };
     let asc = color_align
-        .recolor_ascii(asc, color_profile, color_mode, config.light_dark)
+        .recolor_ascii(asc, color_profile, color_mode, theme)
         .context("failed to recolor ascii")?;
     neofetch_util::run(asc, backend, args)?;
 
@@ -174,8 +193,9 @@ fn read_config(path: &Path) -> Result<Option<Config>> {
 /// The config is automatically stored to file.
 #[tracing::instrument(level = "debug")]
 fn create_config(
-    distro: Option<&String>,
     path: &Path,
+    distro: Option<&String>,
+    backend: Backend,
     use_overlay: bool,
     debug_mode: bool,
 ) -> Result<Config> {
@@ -202,14 +222,15 @@ fn create_config(
     });
     debug!(?det_ansi, "detected color mode");
 
-    let (asc, fore_back) = get_distro_ascii(distro).context("failed to get distro ascii")?;
+    let (asc, fore_back) =
+        get_distro_ascii(distro, backend).context("failed to get distro ascii")?;
     let (asc_width, asc_lines) = ascii_size(asc);
-    let theme = det_bg.map(|bg| bg.theme()).unwrap_or(LightDark::Light);
+    let theme = det_bg.map(|bg| bg.theme()).unwrap_or(TerminalTheme::Light);
     let color_mode = det_ansi.unwrap_or(AnsiMode::Ansi256);
     let logo = color(
         match theme {
-            LightDark::Light => "&l&bhyfetch&~&L",
-            LightDark::Dark => "&l&bhy&ffetch&~&L",
+            TerminalTheme::Light => "&l&bhyfetch&~&L",
+            TerminalTheme::Dark => "&l&bhy&ffetch&~&L",
         },
         color_mode,
     )
@@ -280,9 +301,9 @@ fn create_config(
     };
 
     //////////////////////////////
-    // 2. Select light/dark mode
+    // 2. Select theme (light/dark mode)
 
-    let select_light_dark = || -> Result<(LightDark, &str)> {
+    let select_theme = || -> Result<(TerminalTheme, &str)> {
         if let Some(det_bg) = det_bg {
             return Ok((det_bg.theme(), "Detected background color"));
         }
@@ -294,11 +315,10 @@ fn create_config(
     };
 
     let theme = {
-        let (light_dark, ttl) =
-            select_light_dark().context("failed to select light / dark mode")?;
-        debug!(?light_dark, "selected theme");
-        update_title(&mut title, &mut option_counter, ttl, light_dark.into());
-        light_dark
+        let (selected_theme, ttl) = select_theme().context("failed to select theme")?;
+        debug!(?selected_theme, "selected theme");
+        update_title(&mut title, &mut option_counter, ttl, selected_theme.into());
+        selected_theme
     };
 
     //////////////////////////////
@@ -400,7 +420,7 @@ fn create_config(
     let color_profile: ColorProfile;
     let preset_rainbow = Preset::Rainbow
         .color_profile()
-        .with_lightness_dl(Config::default_lightness(theme), theme, use_overlay)
+        .with_lightness_adaptive(Config::default_lightness(theme), theme, use_overlay)
         .color_text(
             "preset",
             color_mode,
@@ -421,7 +441,7 @@ fn create_config(
             opts.push("prev");
         }
         println!("Enter 'next' to go to the next page and 'prev' to go to the previous page.");
-        let preset = literal_input(
+        let selection = literal_input(
             format!("Which {preset_rainbow} do you want to use? "),
             &opts[..],
             Preset::Rainbow.into(),
@@ -429,18 +449,19 @@ fn create_config(
             color_mode,
         )
         .context("failed to select preset")?;
-        if preset == "next" {
+        if selection == "next" {
             page += 1;
-        } else if preset == "prev" {
+        } else if selection == "prev" {
             page -= 1;
         } else {
-            let preset: Preset = preset.parse().expect("selected preset should be valid");
-            debug!(?preset, "selected preset");
-            color_profile = preset.color_profile();
+            let selected_preset: Preset =
+                selection.parse().expect("selected preset should be valid");
+            debug!(?selected_preset, "selected preset");
+            color_profile = selected_preset.color_profile();
             {
-                let preset_name: &'static str = preset.into();
+                let preset_name: &'static str = selected_preset.into();
                 let preset_colored_name = color_profile
-                    .with_lightness_dl(Config::default_lightness(theme), theme, use_overlay)
+                    .with_lightness_adaptive(Config::default_lightness(theme), theme, use_overlay)
                     .color_text(
                         preset_name,
                         color_mode,
diff --git a/crates/hyfetch/src/color_util.rs b/crates/hyfetch/src/color_util.rs
index 17c2dee2..311e74e6 100644
--- a/crates/hyfetch/src/color_util.rs
+++ b/crates/hyfetch/src/color_util.rs
@@ -11,7 +11,7 @@ use palette::{IntoColorMut, LinSrgb, Okhsl, Srgb};
 use serde::{Deserialize, Serialize};
 use thiserror::Error;
 
-use crate::types::{AnsiMode, LightDark};
+use crate::types::{AnsiMode, TerminalTheme};
 
 const MINECRAFT_COLORS: [(&str, &str); 30] = [
     // Minecraft formatting codes
@@ -114,7 +114,7 @@ pub trait ToAnsiString {
 }
 
 pub trait Theme {
-    fn theme(&self) -> LightDark;
+    fn theme(&self) -> TerminalTheme;
 }
 
 impl Lightness {
@@ -227,16 +227,16 @@ impl ToAnsiString for Srgb<u8> {
 }
 
 impl Theme for Srgb<u8> {
-    fn theme(&self) -> LightDark {
+    fn theme(&self) -> TerminalTheme {
         let mut rgb_f32_color: LinSrgb = self.into_linear();
 
         {
             let okhsl_f32_color: &mut Okhsl = &mut rgb_f32_color.into_color_mut();
 
             if okhsl_f32_color.lightness > 0.5 {
-                LightDark::Light
+                TerminalTheme::Light
             } else {
-                LightDark::Dark
+                TerminalTheme::Dark
             }
         }
     }
diff --git a/crates/hyfetch/src/models.rs b/crates/hyfetch/src/models.rs
index 63c67c6f..d43929d0 100644
--- a/crates/hyfetch/src/models.rs
+++ b/crates/hyfetch/src/models.rs
@@ -3,13 +3,13 @@ use serde::{Deserialize, Serialize};
 use crate::color_util::Lightness;
 use crate::neofetch_util::ColorAlignment;
 use crate::presets::Preset;
-use crate::types::{AnsiMode, Backend, LightDark};
+use crate::types::{AnsiMode, Backend, TerminalTheme};
 
 #[derive(Clone, Debug, Deserialize, Serialize)]
 pub struct Config {
     pub preset: Preset,
     pub mode: AnsiMode,
-    pub light_dark: LightDark,
+    pub light_dark: TerminalTheme,
     lightness: Option<Lightness>,
     pub color_align: ColorAlignment,
     pub backend: Backend,
@@ -21,12 +21,12 @@ pub struct Config {
 }
 
 impl Config {
-    pub fn default_lightness(term: LightDark) -> Lightness {
-        match term {
-            LightDark::Dark => {
+    pub fn default_lightness(theme: TerminalTheme) -> Lightness {
+        match theme {
+            TerminalTheme::Dark => {
                 Lightness::new(0.65).expect("default lightness should not be invalid")
             },
-            LightDark::Light => {
+            TerminalTheme::Light => {
                 Lightness::new(0.4).expect("default lightness should not be invalid")
             },
         }
diff --git a/crates/hyfetch/src/neofetch_util.rs b/crates/hyfetch/src/neofetch_util.rs
index fba8f9b3..3fe11ec0 100644
--- a/crates/hyfetch/src/neofetch_util.rs
+++ b/crates/hyfetch/src/neofetch_util.rs
@@ -22,7 +22,7 @@ use crate::color_util::{
 };
 use crate::distros::Distro;
 use crate::presets::ColorProfile;
-use crate::types::{AnsiMode, Backend, LightDark};
+use crate::types::{AnsiMode, Backend, TerminalTheme};
 use crate::utils::{find_file, find_in_path, process_command_status};
 
 const NEOFETCH_COLOR_PATTERNS: [&str; 6] = ["${c1}", "${c2}", "${c3}", "${c4}", "${c5}", "${c6}"];
@@ -84,7 +84,7 @@ impl ColorAlignment {
         asc: String,
         color_profile: ColorProfile,
         color_mode: AnsiMode,
-        term: LightDark,
+        theme: TerminalTheme,
     ) -> Result<String> {
         let reset = color("&~&*", color_mode).expect("color reset should not be invalid");
 
@@ -105,9 +105,9 @@ impl ColorAlignment {
                 let asc = asc.replace(
                     &format!("${{c{fore}}}"),
                     &color(
-                        match term {
-                            LightDark::Light => "&0",
-                            LightDark::Dark => "&f",
+                        match theme {
+                            TerminalTheme::Light => "&0",
+                            TerminalTheme::Dark => "&f",
                         },
                         color_mode,
                     )
@@ -466,14 +466,17 @@ pub fn ensure_git_bash() -> Result<PathBuf> {
 /// 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")]
-pub fn get_distro_ascii<S>(distro: Option<S>) -> Result<(String, Option<ForeBackColorPair>)>
+pub fn get_distro_ascii<S>(
+    distro: Option<S>,
+    backend: Backend,
+) -> Result<(String, Option<ForeBackColorPair>)>
 where
     S: AsRef<str> + fmt::Debug,
 {
     let distro: Cow<_> = if let Some(distro) = distro.as_ref() {
         distro.as_ref().into()
     } else {
-        get_distro_name()
+        get_distro_name(backend)
             .context("failed to get distro name")?
             .into()
     };
@@ -500,6 +503,7 @@ 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 => {
@@ -653,10 +657,60 @@ where
     }
 }
 
+/// Runs fastfetch command, returning the piped stdout output.
+fn run_fastfetch_command_piped<S>(args: &[S]) -> Result<String>
+where
+    S: AsRef<OsStr> + fmt::Debug,
+{
+    let mut command = make_fastfetch_command(args)?;
+
+    let output = command
+        .output()
+        .context("failed to execute fastfetch as child process")?;
+    debug!(?output, "fastfetch output");
+    process_command_status(&output.status).context("fastfetch command exited with error")?;
+
+    let out = String::from_utf8(output.stdout)
+        .context("failed to process fastfetch output as it contains invalid UTF-8")?
+        .trim()
+        .to_owned();
+    Ok(out)
+}
+
+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")?;
+
+    debug!(?fastfetch_path, "fastfetch path");
+
+    let mut command = Command::new(fastfetch_path);
+    command.args(args);
+    Ok(command)
+}
+
 #[tracing::instrument(level = "debug")]
-fn get_distro_name() -> Result<String> {
-    run_neofetch_command_piped(&["ascii_distro_name"])
-        .context("failed to get distro name from neofetch")
+fn get_distro_name(backend: Backend) -> Result<String> {
+    match backend {
+        Backend::Neofetch => run_neofetch_command_piped(&["ascii_distro_name"])
+            .context("failed to get distro name from neofetch"),
+        Backend::Fastfetch | Backend::FastfetchOld => run_fastfetch_command_piped(&[
+            "--logo",
+            "none",
+            "-s",
+            "OS",
+            "--disable-linewrap",
+            "--os-key",
+            " ",
+        ])
+        .context("failed to get distro name from fastfetch"),
+        Backend::Qwqfetch => {
+            todo!()
+        },
+    }
 }
 
 /// Runs neofetch with colors.
@@ -676,21 +730,18 @@ fn run_neofetch(asc: String, args: Option<&Vec<String>>) -> Result<()> {
     // Call neofetch with the temp file
     let temp_file_path = temp_file.into_temp_path();
     let args = {
-        let mut v = vec![
-            "--ascii",
-            "--source",
-            temp_file_path
-                .to_str()
-                .expect("temp file path should not contain invalid UTF-8"),
-            "--ascii-colors",
+        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(),
         ];
         if let Some(args) = args {
-            let args: Vec<_> = args.iter().map(|s| &**s).collect();
-            v.extend(args);
+            v.extend(args.iter().map(|arg| OsStr::new(arg).into()));
         }
         v
     };
-    let mut command = make_neofetch_command(&args)?;
+    let mut command = make_neofetch_command(&args[..])?;
 
     debug!(?command, "neofetch command");
 
@@ -761,12 +812,6 @@ fn fastfetch_path() -> Result<Option<PathBuf>> {
 /// 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")?;
@@ -776,12 +821,17 @@ fn run_fastfetch(asc: String, args: Option<&Vec<String>>, legacy: bool) -> Resul
 
     // 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);
-    }
+    let args = {
+        let mut v: Vec<Cow<OsStr>> = vec![
+            OsStr::new(if legacy { "--raw" } else { "--file-raw" }).into(),
+            OsStr::new(&temp_file_path).into(),
+        ];
+        if let Some(args) = args {
+            v.extend(args.iter().map(|arg| OsStr::new(arg).into()));
+        }
+        v
+    };
+    let mut command = make_fastfetch_command(&args[..])?;
 
     debug!(?command, "fastfetch command");
 
diff --git a/crates/hyfetch/src/presets.rs b/crates/hyfetch/src/presets.rs
index ecabc60d..a20d06f9 100644
--- a/crates/hyfetch/src/presets.rs
+++ b/crates/hyfetch/src/presets.rs
@@ -10,7 +10,7 @@ use tracing::debug;
 use unicode_segmentation::UnicodeSegmentation;
 
 use crate::color_util::{ForegroundBackground, Lightness, ToAnsiString};
-use crate::types::{AnsiMode, LightDark};
+use crate::types::{AnsiMode, TerminalTheme};
 
 #[derive(
     Copy,
@@ -639,20 +639,20 @@ impl ColorProfile {
     }
 
     /// Creates a new color profile, with the colors set to the specified
-    /// [`Okhsl`] lightness value, with respect to dark/light terminals.
-    pub fn with_lightness_dl(
+    /// [`Okhsl`] lightness value, adapted to the terminal theme.
+    pub fn with_lightness_adaptive(
         &self,
         lightness: Lightness,
-        term: LightDark,
+        theme: TerminalTheme,
         use_overlay: bool,
     ) -> Self {
         if use_overlay {
             todo!()
         }
 
-        match term {
-            LightDark::Dark => self.with_lightness(AssignLightness::ClampMin(lightness)),
-            LightDark::Light => self.with_lightness(AssignLightness::ClampMax(lightness)),
+        match theme {
+            TerminalTheme::Dark => self.with_lightness(AssignLightness::ClampMin(lightness)),
+            TerminalTheme::Light => self.with_lightness(AssignLightness::ClampMax(lightness)),
         }
     }
 
diff --git a/crates/hyfetch/src/types.rs b/crates/hyfetch/src/types.rs
index b2ead9b9..d934763a 100644
--- a/crates/hyfetch/src/types.rs
+++ b/crates/hyfetch/src/types.rs
@@ -23,7 +23,7 @@ pub enum AnsiMode {
 )]
 #[serde(rename_all = "lowercase")]
 #[strum(serialize_all = "lowercase")]
-pub enum LightDark {
+pub enum TerminalTheme {
     Light,
     Dark,
 }