[+] Allow passing hex colors as preset (#435)

This commit is contained in:
thea 2025-10-02 01:13:09 +10:00 committed by GitHub
parent fb1e35172e
commit 5dc1709f58
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 72 additions and 39 deletions

View file

@ -3,6 +3,7 @@ use std::cmp;
use std::fmt::Write as _;
use std::fs::{self, File};
use std::io::{self, IsTerminal as _, Read as _, Write as _};
use std::iter;
use std::iter::zip;
use std::num::NonZeroU8;
use std::path::{Path, PathBuf};
@ -24,7 +25,7 @@ use hyfetch::models::Config;
#[cfg(feature = "macchina")]
use hyfetch::neofetch_util::macchina_path;
use hyfetch::neofetch_util::{self, add_pkg_path, fastfetch_path, get_distro_ascii, get_distro_name, literal_input, ColorAlignment, NEOFETCH_COLORS_AC, NEOFETCH_COLOR_PATTERNS, TEST_ASCII};
use hyfetch::presets::{AssignLightness, Preset};
use hyfetch::presets::{AssignLightness, ColorProfile, Preset};
use hyfetch::pride_month;
use hyfetch::types::{AnsiMode, Backend, TerminalTheme};
use hyfetch::utils::{get_cache_path, input};
@ -129,9 +130,43 @@ fn main() -> Result<()> {
let backend = options.backend.unwrap_or(config.backend);
let args = options.args.as_ref().or(config.args.as_ref());
fn parse_preset_string(preset_string: &str) -> Result<ColorProfile> {
if preset_string.contains('#') {
let colors: Vec<&str> = preset_string.split(',').map(|s| s.trim()).collect();
for color in &colors {
if !color.starts_with('#') ||
(color.len() != 4 && color.len() != 7) ||
!color[1..].chars().all(|c| c.is_ascii_hexdigit()) {
return Err(anyhow::anyhow!("invalid hex color: {}", color));
}
}
ColorProfile::from_hex_colors(colors)
.context("failed to create color profile from hex")
} else if preset_string == "random" {
let mut rng = fastrand::Rng::new();
let preset = *rng
.choice(<Preset as VariantArray>::VARIANTS)
.expect("preset iterator should not be empty");
Ok(preset.color_profile())
} else {
use std::str::FromStr;
let preset = Preset::from_str(preset_string)
.with_context(|| {
format!(
"PRESET should be comma-separated hex colors or one of {{{presets}}}",
presets = <Preset as VariantNames>::VARIANTS
.iter()
.chain(iter::once(&"random"))
.join(",")
)
})?;
Ok(preset.color_profile())
}
}
// Get preset
let preset = options.preset.unwrap_or(config.preset);
let color_profile = preset.color_profile();
let preset_string = options.preset.as_deref().unwrap_or(&config.preset);
let color_profile = parse_preset_string(preset_string)?;
debug!(?color_profile, "color profile");
// Lighten
@ -1155,7 +1190,7 @@ fn create_config(
// Create config
clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
let config = Config {
preset,
preset: preset.as_ref().to_string(),
mode: color_mode,
light_dark: Some(theme),
auto_detect_light_dark: Some(det_bg.is_some()),

View file

@ -8,7 +8,7 @@ use bpaf::ShellComp;
use bpaf::{construct, long, OptionParser, Parser as _};
use directories::BaseDirs;
use itertools::Itertools as _;
use strum::{VariantArray, VariantNames};
use strum::VariantNames;
use crate::color_util::{color, Lightness};
use crate::presets::Preset;
@ -18,7 +18,7 @@ use crate::types::{AnsiMode, Backend};
pub struct Options {
pub config: bool,
pub config_file: PathBuf,
pub preset: Option<Preset>,
pub preset: Option<String>,
pub mode: Option<AnsiMode>,
pub backend: Option<Backend>,
pub args: Option<Vec<String>>,
@ -55,7 +55,7 @@ pub fn options() -> OptionParser<Options> {
let preset = long("preset")
.short('p')
.help(&*format!(
"Use preset
"Use preset or comma-separated color list or comma-separated hex colors (e.g., \"#ff0000,#00ff00,#0000ff\")
PRESET={{{presets}}}",
presets = <Preset as VariantNames>::VARIANTS
.iter()
@ -65,30 +65,7 @@ PRESET={{{presets}}}",
.argument::<String>("PRESET");
#[cfg(feature = "autocomplete")]
let preset = preset.complete(complete_preset);
let preset = preset
.parse(|s| {
Preset::from_str(&s)
.or_else(|e| {
if s == "random" {
let mut rng = fastrand::Rng::new();
Ok(*rng
.choice(<Preset as VariantArray>::VARIANTS)
.expect("preset iterator should not be empty"))
} else {
Err(e)
}
})
.with_context(|| {
format!(
"PRESET should be one of {{{presets}}}",
presets = <Preset as VariantNames>::VARIANTS
.iter()
.chain(iter::once(&"random"))
.join(",")
)
})
})
.optional();
let preset = preset.optional();
let mode = long("mode")
.short('m')
.help(&*format!(

View file

@ -2,12 +2,11 @@ use serde::{Deserialize, Serialize};
use crate::color_util::Lightness;
use crate::neofetch_util::ColorAlignment;
use crate::presets::Preset;
use crate::types::{AnsiMode, Backend, TerminalTheme};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Config {
pub preset: Preset,
pub preset: String,
pub mode: AnsiMode,
pub auto_detect_light_dark: Option<bool>,
pub light_dark: Option<TerminalTheme>,