Skip to content

Commit 938cda4

Browse files
committed
[+] Add custom_presets config option
1 parent b81d5fe commit 938cda4

File tree

4 files changed

+122
-49
lines changed

4 files changed

+122
-49
lines changed

crates/hyfetch/src/bin/hyfetch.rs

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::borrow::Cow;
22
use std::cmp;
3+
use std::collections::HashMap;
34
use std::fmt::Write as _;
45
use std::fs::{self, File};
56
use std::io::{self, IsTerminal as _, Read as _};
@@ -21,7 +22,7 @@ use hyfetch::color_util::{
2122
NeofetchAsciiIndexedColor, PresetIndexedColor, Theme as _, ToAnsiString as _,
2223
};
2324
use hyfetch::distros::Distro;
24-
use hyfetch::models::Config;
25+
use hyfetch::models::{build_hex_color_profile, Config};
2526
#[cfg(feature = "macchina")]
2627
use hyfetch::neofetch_util::macchina_path;
2728
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};
@@ -133,43 +134,46 @@ fn main() -> Result<()> {
133134
let backend = options.backend.unwrap_or(config.backend);
134135
let args = options.args.as_ref().or(config.args.as_ref());
135136

136-
fn parse_preset_string(preset_string: &str) -> Result<ColorProfile> {
137+
fn parse_preset_string(preset_string: &str, config: &Config) -> Result<ColorProfile> {
137138
if preset_string.contains('#') {
138-
let colors: Vec<&str> = preset_string.split(',').map(|s| s.trim()).collect();
139-
for color in &colors {
140-
if !color.starts_with('#') ||
141-
(color.len() != 4 && color.len() != 7) ||
142-
!color[1..].chars().all(|c| c.is_ascii_hexdigit()) {
143-
return Err(anyhow::anyhow!("invalid hex color: {}", color));
144-
}
145-
}
146-
ColorProfile::from_hex_colors(colors)
147-
.context("failed to create color profile from hex")
148-
} else if preset_string == "random" {
139+
let colors: Vec<String> = preset_string.split(',').map(|s| s.trim().to_owned()).collect();
140+
let color_profile = build_hex_color_profile(&colors)
141+
.context("failed to create color profile from hex")?;
142+
return Ok(color_profile);
143+
}
144+
145+
let mut preset_profiles: HashMap<String, ColorProfile> = <Preset as VariantArray>::VARIANTS
146+
.iter()
147+
.map(|preset| (preset.as_ref().to_owned(), preset.color_profile()))
148+
.collect();
149+
preset_profiles.extend(config.custom_preset_profiles()?);
150+
151+
let presets: Vec<ColorProfile> = preset_profiles.values().cloned().collect();
152+
153+
if preset_string == "random" {
149154
let mut rng = fastrand::Rng::new();
150-
let preset = *rng
151-
.choice(<Preset as VariantArray>::VARIANTS)
152-
.expect("preset iterator should not be empty");
153-
Ok(preset.color_profile())
155+
let selected_index = rng.usize(0..presets.len());
156+
return Ok(presets[selected_index].clone());
157+
}
158+
159+
if let Some(color_profile) = preset_profiles.get(preset_string) {
160+
Ok(color_profile.clone())
154161
} else {
155-
use std::str::FromStr;
156-
let preset = Preset::from_str(preset_string)
157-
.with_context(|| {
158-
format!(
159-
"PRESET should be comma-separated hex colors or one of {{{presets}}}",
160-
presets = <Preset as VariantNames>::VARIANTS
161-
.iter()
162-
.chain(iter::once(&"random"))
163-
.join(",")
164-
)
165-
})?;
166-
Ok(preset.color_profile())
162+
let presets = preset_profiles
163+
.keys()
164+
.map(String::as_str)
165+
.chain(iter::once("random"))
166+
.sorted()
167+
.join(",");
168+
Err(anyhow::anyhow!(
169+
"PRESET should be comma-separated hex colors or one of {{{presets}}}"
170+
))
167171
}
168172
}
169173

170174
// Get preset
171175
let preset_string = options.preset.as_deref().unwrap_or(&config.preset);
172-
let color_profile = parse_preset_string(preset_string)?;
176+
let color_profile = parse_preset_string(preset_string, &config)?;
173177
debug!(?color_profile, "color profile");
174178

175179
// Lighten
@@ -1074,6 +1078,7 @@ fn create_config(
10741078
distro: logo_chosen,
10751079
pride_month_disable: false,
10761080
custom_ascii_path,
1081+
custom_presets: None,
10771082
};
10781083
debug!(?config, "created config");
10791084

crates/hyfetch/src/models.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
use std::collections::{HashMap};
2+
3+
use anyhow::{Context as _, Result};
14
use serde::{Deserialize, Serialize};
25

36
use crate::color_util::Lightness;
7+
use crate::color_profile::ColorProfile;
48
use crate::neofetch_util::ColorAlignment;
59
use crate::types::{AnsiMode, Backend, TerminalTheme};
610

@@ -19,8 +23,11 @@ pub struct Config {
1923
pub distro: Option<String>,
2024
pub pride_month_disable: bool,
2125
pub custom_ascii_path: Option<String>,
26+
pub custom_presets: Option<HashMap<String, Vec<String>>>,
2227
}
2328

29+
30+
2431
impl Config {
2532
pub fn default_lightness(theme: TerminalTheme) -> Lightness {
2633
match theme {
@@ -32,6 +39,32 @@ impl Config {
3239
},
3340
}
3441
}
42+
43+
pub fn custom_preset_profiles(&self) -> Result<HashMap<String, ColorProfile>> {
44+
let mut profiles = HashMap::new();
45+
if let Some(custom_presets) = &self.custom_presets {
46+
for (preset_name, colors) in custom_presets {
47+
let color_profile = build_hex_color_profile(colors).with_context(|| {
48+
format!("failed to validate custom preset key `{preset_name}`")
49+
})?;
50+
profiles.insert(preset_name.clone(), color_profile);
51+
}
52+
}
53+
Ok(profiles)
54+
}
55+
}
56+
57+
pub fn build_hex_color_profile(hex_colors: &[String]) -> Result<ColorProfile> {
58+
for color in hex_colors {
59+
if !color.starts_with('#') ||
60+
(color.len() != 4 && color.len() != 7) ||
61+
!color[1..].chars().all(|c| c.is_ascii_hexdigit()) {
62+
return Err(anyhow::anyhow!("invalid hex color: {color}"));
63+
}
64+
}
65+
66+
ColorProfile::from_hex_colors(hex_colors.to_vec())
67+
.context("failed to create color profile from hex")
3568
}
3669

3770
mod args_serde {

hyfetch/main.py

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from .color_util import clear_screen
1717
from .constants import *
1818
from .font_logo import get_font_logo
19-
from .models import Config
19+
from .models import build_hex_color_profile, Config
2020
from .neofetch_util import *
2121
from .presets import PRESETS, ColorProfile
2222

@@ -482,30 +482,40 @@ def run():
482482
config.backend = args.backend
483483
if args.args:
484484
config.args = args.args
485-
486-
# Random preset
487-
if config.preset == 'random':
488-
config.preset = random.choice(list(PRESETS.keys()))
489-
490485
# Override global color mode
491486
GLOBAL_CFG.color_mode = config.mode
492487
GLOBAL_CFG.is_light = config.light_dark == 'light'
493488

494489
# Get preset
495-
preset = None
496-
if config.preset in PRESETS:
497-
preset = PRESETS.get(config.preset)
498-
elif '#' in config.preset:
499-
colors = [color.strip() for color in config.preset.split(',')]
500-
501-
for color in colors:
502-
if not (color.startswith('#') and len(color) in [4, 7] and all(c in '0123456789abcdefABCDEF' for c in color[1:])):
503-
print(f'Error: invalid hex color "{color}"')
504-
preset = ColorProfile(colors)
505-
else:
506-
print(f'Preset should be a comma-separated list of hex colors, or one of the following: {", ".join(sorted(PRESETS.keys()))}')
490+
def parse_preset_string(preset_string: str, config: Config) -> ColorProfile:
491+
if '#' in preset_string:
492+
colors = [s.strip() for s in preset_string.split(',')]
493+
return build_hex_color_profile(colors)
494+
495+
preset_profiles: dict[str, ColorProfile] = {
496+
name: preset for name, preset in PRESETS.items()
497+
}
498+
preset_profiles.update(config.custom_preset_profiles())
499+
500+
presets = list(preset_profiles.values())
501+
if preset_string == 'random':
502+
if not presets:
503+
raise ValueError('no presets available for random selection')
504+
return random.choice(presets)
505+
506+
if preset_string in preset_profiles:
507+
return preset_profiles[preset_string]
508+
509+
available = sorted([*preset_profiles.keys(), 'random'])
510+
raise ValueError(
511+
"PRESET should be comma-separated hex colors or one of "
512+
f"{{{','.join(available)}}}"
513+
)
507514

508-
if preset is None:
515+
try:
516+
preset = parse_preset_string(config.preset, config)
517+
except ValueError as err:
518+
print(f'Error: {err}')
509519
exit(1)
510520

511521
# Lighten (args > config)

hyfetch/models.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,22 @@
44

55
from .constants import CONFIG_PATH
66
from .neofetch_util import ColorAlignment
7+
from .presets import ColorProfile
78
from .serializer import json_stringify, from_dict
89
from .types import AnsiMode, LightDark, BackendLiteral
910

1011

12+
def build_hex_color_profile(hex_colors: list[str]) -> ColorProfile:
13+
for color in hex_colors:
14+
if not (
15+
color.startswith('#')
16+
and len(color) in [4, 7]
17+
and all(c in '0123456789abcdefABCDEF' for c in color[1:])
18+
):
19+
raise ValueError(f'invalid hex color: {color}')
20+
return ColorProfile(hex_colors)
21+
22+
1123
@dataclass
1224
class Config:
1325
preset: str
@@ -21,6 +33,7 @@ class Config:
2133
pride_month_shown: list[int] = field(default_factory=list) # This is deprecated, see issue #136
2234
pride_month_disable: bool = False
2335
custom_ascii_path: str | None = None
36+
custom_presets: dict[str, list[str]] | None = None
2437

2538
@classmethod
2639
def from_dict(cls, d: dict):
@@ -30,3 +43,15 @@ def from_dict(cls, d: dict):
3043
def save(self):
3144
CONFIG_PATH.parent.mkdir(exist_ok=True, parents=True)
3245
CONFIG_PATH.write_text(json_stringify(self, indent=4), 'utf-8')
46+
47+
def custom_preset_profiles(self) -> dict[str, ColorProfile]:
48+
profiles: dict[str, ColorProfile] = {}
49+
if self.custom_presets:
50+
for preset_name, colors in self.custom_presets.items():
51+
try:
52+
profiles[preset_name] = build_hex_color_profile(colors)
53+
except ValueError as err:
54+
raise ValueError(
55+
f'failed to validate custom preset key "{preset_name}": {err}'
56+
) from err
57+
return profiles

0 commit comments

Comments
 (0)