diff --git a/crates/hyfetch/build.rs b/crates/hyfetch/build.rs index 20976ce2..04b01701 100644 --- a/crates/hyfetch/build.rs +++ b/crates/hyfetch/build.rs @@ -67,6 +67,7 @@ fn main() -> Result<()> { &dir.join("../../neofetch"), ]).context("couldn't find neofetch")?; println!("cargo:rerun-if-changed={}", neofetch_src.display()); + println!("cargo:rerun-if-changed={}", data_dir.display()); fs::copy(&neofetch_src, o.join("neofetch"))?; preset_codegen(&o.join("hyfetch/data/presets.json"), &o.join("presets.rs"))?; diff --git a/crates/hyfetch/src/bin/hyfetch.rs b/crates/hyfetch/src/bin/hyfetch.rs index c342ad85..805696a6 100644 --- a/crates/hyfetch/src/bin/hyfetch.rs +++ b/crates/hyfetch/src/bin/hyfetch.rs @@ -1195,49 +1195,100 @@ fn create_config( (ascii_per_row, ascii_rows) }; - // get distro string and convert it into the enum, neofetch friendly format, so we can check for small logos with the {distro}_small neofetch naming scheme. + // get distro string and convert it into the enum, neofetch friendly format, so we can check for small and old logos with the {distro}_small / {distro}_old neofetch naming scheme. let tmp_dst = get_distro_name(backend).or_else(|_| get_distro_name(Backend::Neofetch)).context("failed to get distro name")?; - let detected_dst = Some(distro.map_or_else( + let detected_dst = distro.map_or_else( || { Distro::detect(&tmp_dst) - .map_or("".to_string(), |d| format!("{:?}", d).to_lowercase()) + .map_or_else(|| tmp_dst.clone(), |d| format!("{:?}", d)) }, - |d| d.to_string().to_lowercase(), - )); + |d| d.to_string(), + ).to_lowercase(); - // in case someone specified {distro}_small already in the --distro arg - let detected_dst_small_fmt = if !detected_dst.clone().unwrap().ends_with("_small") { - format!("{}_small", detected_dst.unwrap()).to_lowercase() + let raw_dst = detected_dst.as_str(); + let base_distro = raw_dst + .strip_suffix("_small") + .or_else(|| raw_dst.strip_suffix("_old")) + .unwrap_or(raw_dst); + + let detected_dst_old_fmt = format!("{base_distro}_old"); + let running_dst_old = if Distro::detect(&detected_dst_old_fmt).is_some() { + detected_dst_old_fmt } else { - detected_dst.unwrap() + "".to_string() }; + let detected_dst_small_fmt = format!("{base_distro}_small"); let running_dst_sml = if Distro::detect(&detected_dst_small_fmt).is_some() { detected_dst_small_fmt } else { "".to_string() }; + // Load ascii for default, old, and small logos + let default_asc = if raw_dst.ends_with("_old") || raw_dst.ends_with("_small") { + get_distro_ascii(Some(base_distro), backend) + .or_else(|_| get_distro_ascii(Some(base_distro), Backend::Neofetch)) + .ok() + .and_then(|a| a.to_normalized().ok()) + .unwrap_or_else(|| asc.clone()) + } else { + asc.clone() + }; - // load ascii - let small_asc = get_distro_ascii(Some(&running_dst_sml), backend).context("failed to get distro ascii")?; - let small_asc = small_asc.to_normalized().context("failed to normalize ascii")?; + let old_asc = if !running_dst_old.is_empty() { + get_distro_ascii(Some(&running_dst_old), backend) + .or_else(|_| get_distro_ascii(Some(&running_dst_old), Backend::Neofetch)) + .ok() + .and_then(|a| a.to_normalized().ok()) + } else { + None + }; + + let small_asc = if !running_dst_sml.is_empty() { + get_distro_ascii(Some(&running_dst_sml), backend) + .or_else(|_| get_distro_ascii(Some(&running_dst_sml), Backend::Neofetch)) + .ok() + .and_then(|a| a.to_normalized().ok()) + } else { + None + }; let mut asc = asc; let mut logo_chosen: Option = distro.cloned(); - if small_asc.lines != asc.lines && running_dst_sml != "" { - let ds_arrangements = [ - ("Default", asc.clone()), - ("Small", small_asc.clone()) - ]; + let mut ds_arrangements: Vec<(&str, NormalizedAsciiArt)> = vec![ + ("Default", default_asc.clone()), + ]; + if let Some(ref o) = old_asc { + if o.lines != default_asc.lines { + ds_arrangements.push(("Old", o.clone())); + } + } + + if let Some(ref s) = small_asc { + if s.lines != default_asc.lines { + ds_arrangements.push(("Small", s.clone())); + } + } + + if ds_arrangements.len() > 1 { let arrangements: IndexMap, NormalizedAsciiArt> = - ds_arrangements.map(|(k, a)| (k.into(), a)).into(); + ds_arrangements.into_iter().map(|(k, a)| (k.into(), a)).collect(); + + let has_old = arrangements.contains_key("Old"); + let has_small = arrangements.contains_key("Small"); + + let max_h = arrangements.values().map(|a| usize::from(a.h)).max().unwrap_or(usize::from(asc.h)); + let max_w = arrangements.values().map(|a| usize::from(a.w)).max().unwrap_or(usize::from(asc.w)); { clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?; + let (Width(term_w), _) = terminal_size().unwrap_or((Width(80), Height(24))); + let ascii_per_row = (term_w / (max_w as u16 + 6)).clamp(1, u8::MAX.into()) as u8; + let asciis: Vec> = arrangements .iter() .map(|(k, a)| { @@ -1245,51 +1296,71 @@ fn create_config( .to_recolored(&ColorAlignment::Horizontal, &color_profile, color_mode, theme) .context("failed to recolor ascii")? .lines; - if k == "Small" { - // vertical center - let pad_len = (asc.h as usize - v.len()) / 2; - let mut pad_v: Vec = vec![]; - for _ in 0..pad_len { - pad_v.push("".to_string()); - } - v.splice(0..0, pad_v.clone()); - v.extend(pad_v); - let pad_diff = asc.h as usize - v.len(); - v.extend(std::iter::repeat("".to_string()).take(pad_diff)); - v.push(format!("{k:^asc_width$}", asc_width = usize::from(small_asc.w))); - return Ok(v); - } - v.push(format!("{k:^asc_width$}", asc_width = usize::from(asc.w))); - Ok(v) + let pad_len = max_h.saturating_sub(v.len()) / 2; + let empty_line = " ".repeat(usize::from(a.w)); + let pad_v: Vec = vec![empty_line.clone(); pad_len]; + v.splice(0..0, pad_v); + + let pad_diff = max_h.saturating_sub(v.len()); + v.extend(std::iter::repeat(empty_line).take(pad_diff)); + v.push(format!("{k:^asc_width$}", asc_width = usize::from(a.w))); + Ok(v) }) .collect::>()?; - // prints small logo w/ big logo for row in &asciis.into_iter().chunks(usize::from(ascii_per_row)) { - let row: Vec> = row.collect(); - for i in 0..usize::from(asc.h).checked_add(1).unwrap() { + for i in 0..=max_h { let mut line = Vec::new(); for lines in &row { - line.push(&*lines[i]); + line.push(&*lines[i]); } - printc(line.join(" "), color_mode).context("failed to print ascii line")?; + printc(line.join(" "), color_mode).context("failed to print ascii line")?; } println!(); } - print_title_prompt(option_counter, "Do you want the default logo, or the small logo?"); - let opts: Vec> = ["default", "small"].map(Into::into).into(); + let prompt_text = match (has_old, has_small) { + (true, true) => "Do you want the default logo, the old logo, or the small logo?", + (true, false) => "Do you want the default logo, or the old logo?", + (false, true) => "Do you want the default logo, or the small logo?", + (false, false) => unreachable!(), + }; + + print_title_prompt(option_counter, prompt_text); + + let mut opts: Vec> = vec!["default".into()]; + if has_old { + opts.push("old".into()); + } + if has_small { + opts.push("small".into()); + } + let choice = literal_input("Your choice?", &opts[..], "default", true, color_mode) .context("failed to ask for choice input") - .context("failed to select logo type").context("failed to ask for choice input")?; + .context("failed to select logo type")?; - if choice.to_lowercase() == "small" { - logo_chosen = Some(running_dst_sml); - asc = small_asc; + match choice.to_lowercase().as_str() { + "old" => { + logo_chosen = Some(running_dst_old); + if let Some(o) = old_asc { + asc = o; + } + } + "small" => { + logo_chosen = Some(running_dst_sml); + if let Some(s) = small_asc { + asc = s; + } + } + _ => { + logo_chosen = distro.map(|_| base_distro.to_string()); + asc = default_asc; + } } update_title( diff --git a/crates/hyfetch/src/neofetch_util.rs b/crates/hyfetch/src/neofetch_util.rs index 5af676f4..cac77ded 100644 --- a/crates/hyfetch/src/neofetch_util.rs +++ b/crates/hyfetch/src/neofetch_util.rs @@ -142,6 +142,18 @@ where return Some(selected.as_ref()); } + // Aliases + if sel == "original" { + if let Some(selected) = options.iter().find(|&o| o.as_ref().to_lowercase() == "old") { + return Some(selected.as_ref()); + } + } + if sel == "new" || sel == "regular" { + if let Some(selected) = options.iter().find(|&o| o.as_ref().to_lowercase() == "default") { + return Some(selected.as_ref()); + } + } + // Find starting abbreviation if let Some(selected) = options .iter() @@ -190,6 +202,13 @@ pub fn add_pkg_path() -> Result<()> { /// [neofetch]: https://github.com/hykilpikonna/hyfetch#running-updated-original-neofetch pub fn neofetch_path() -> Result { if let Ok(p) = which("neowofetch") { + if let Ok(first_line) = fs::read_to_string(&p).map(|s| s.lines().next().unwrap_or("").to_owned()) { + if !first_line.contains("python") { + return Ok(p); + } + } + } + if let Ok(p) = which("neofetch") { return Ok(p); } diff --git a/hyfetch/data/distros/void_old.ascii b/hyfetch/data/distros/void_old.ascii index 7afb27d5..ea9e4a40 100644 --- a/hyfetch/data/distros/void_old.ascii +++ b/hyfetch/data/distros/void_old.ascii @@ -1,4 +1,4 @@ -{"match": "\"void_old\"", "color": "2 8"} +{"match": "\"void_old\"", "color": "2 8", "foreground": [2], "background": 1} ${c1} __.;=====;.__ _.=+==++=++=+=+===;. -=+++=+===+=+=+++++=_ @@ -6,7 +6,7 @@ ${c1} __.;=====;.__ _vi, ` --+=++++: .uvnvi. _._ -==+==+. .vvnvnI` .;==|==;. :|=||=|. -${c2}+QmQQm${c1}pvvnv; ${c2}_yYsyQQWUUQQQm #QmQ#${c1}:${c2}QQQWUV$QQmL +${c2}+QmQQm${c1}pvvnv; ${c2}_yYsyQQWUUQQQm #QmQ#${c1}:${c2}QQQWUV$QQm. ${c2} -QQWQW${c1}pvvo${c2}wZ?.wQQQE${c1}==<${c2}QWWQ/QWQW.QQWW${c1}(: ${c2}jQWQE ${c2} -$QQQQmmU' jQQQ@${c1}+=<${c2}QWQQ)mQQQ.mQQQC${c1}+;${c2}jWQQ@' ${c2} -$WQ8Y${c1}nI: ${c2}QWQQwgQQWV${c1}`${c2}mWQQ.jQWQQgyyWW@! @@ -16,4 +16,4 @@ ${c1} -1vvnvv. `~+++` ++|+++ -Invnvvnsi..___..=sv=. ` +Invnvnvnnnnnnnnvvnn;. ~|Invnvnvvnvvvnnv}+` - -~|{*l}*| \ No newline at end of file + -~|{*l}*|~ \ No newline at end of file diff --git a/hyfetch/main.py b/hyfetch/main.py index 4c7ebc4e..5c03c50f 100755 --- a/hyfetch/main.py +++ b/hyfetch/main.py @@ -17,6 +17,7 @@ from . import termenv, neofetch_util, pride_month from .color_scale import Scale from .color_util import clear_screen from .constants import * +from .distros import distro_detector from .font_logo import get_font_logo from .models import build_hex_color_profile, Config from .neofetch_util import * @@ -51,12 +52,12 @@ def check_config(path) -> Config: try: return Config.from_dict(json.loads(path.read_text('utf-8'))) except KeyError: - return create_config() + return create_config(path) - return create_config() + return create_config(path) -def create_config() -> Config: +def create_config(path: Path | None = None) -> Config: """ Create config interactively @@ -432,10 +433,102 @@ def create_config() -> Config: _prs = _prs.set_light_dl(lightness, light_dark) update_title('Selected Brightness', f"{lightness:.2f}") + ############################## + # 5. Choose Default, Old, or Small Logo + tmp_dst = GLOBAL_CFG.override_distro or get_distro_name() + detected_dst = GLOBAL_CFG.override_distro + if not detected_dst: + det = distro_detector.detect(tmp_dst) + detected_dst = det.name if det else tmp_dst + detected_dst = (detected_dst or '').lower() + + raw_dst = detected_dst + base_distro = raw_dst + for suffix in ('_small', '_old'): + if base_distro.endswith(suffix): + base_distro = base_distro[:-len(suffix)] + break + + running_dst_old = f"{base_distro}_old" if distro_detector.detect(f"{base_distro}_old") else "" + running_dst_sml = f"{base_distro}_small" if distro_detector.detect(f"{base_distro}_small") else "" + + default_asc = get_distro_ascii(base_distro) if (raw_dst.endswith('_old') or raw_dst.endswith('_small')) else asc + old_asc = get_distro_ascii(running_dst_old) if running_dst_old else None + small_asc = get_distro_ascii(running_dst_sml) if running_dst_sml else None + + logo_chosen: str | None = GLOBAL_CFG.override_distro + + ds_arrangements = [ + ('Default', default_asc) + ] + if old_asc and old_asc.strip().splitlines() != default_asc.strip().splitlines(): + ds_arrangements.append(('Old', old_asc)) + if small_asc and small_asc.strip().splitlines() != default_asc.strip().splitlines(): + ds_arrangements.append(('Small', small_asc)) + + if len(ds_arrangements) > 1: + has_old = 'Old' in [k for k, _ in ds_arrangements] + has_small = 'Small' in [k for k, _ in ds_arrangements] + + max_h = max(ascii_size(a)[1] for _, a in ds_arrangements) + max_w = max(ascii_size(a)[0] for _, a in ds_arrangements) + + while True: + clear_screen(title) + + term_w = term_size()[0] + ascii_per_row = max(1, term_w // (max_w + 6)) + + asciis = [] + for k, a in ds_arrangements: + w, h = ascii_size(a) + v_dst = running_dst_old if k == 'Old' else (running_dst_sml if k == 'Small' else base_distro) + v_ca = ColorAlignment('horizontal', fore_back=get_fore_back(v_dst)) + lines = v_ca.recolor_ascii(a, _prs).split('\n') + pad_top = (max_h - len(lines)) // 2 + pad_bottom = max_h - len(lines) - pad_top + padded = [' ' * w] * pad_top + lines + [' ' * w] * pad_bottom + padded.append(k.center(w)) + asciis.append(padded) + + while asciis: + current = asciis[:ascii_per_row] + asciis = asciis[ascii_per_row:] + for line in zip(*current): + printc(' '.join(line)) + print() + + if has_old and has_small: + prompt_text = "Do you want the default logo, the old logo, or the small logo?" + opts = ['default', 'old', 'small'] + elif has_old: + prompt_text = "Do you want the default logo, or the old logo?" + opts = ['default', 'old'] + else: + prompt_text = "Do you want the default logo, or the small logo?" + opts = ['default', 'small'] + + print_title_prompt(prompt_text) + choice = literal_input('Your choice?', opts, 'default') + + if choice == 'old': + logo_chosen = running_dst_old + asc = old_asc + elif choice == 'small': + logo_chosen = running_dst_sml + asc = small_asc + else: + logo_chosen = base_distro if GLOBAL_CFG.override_distro else None + asc = default_asc + + asc_width, asc_lines = ascii_size(asc) + update_title('Selected logo type', choice) + break + ############################# - # 5. Color arrangement + # 6. Color arrangement color_alignment = None - fore_back = get_fore_back() + fore_back = get_fore_back(logo_chosen or base_distro) # Calculate amount of row/column that can be displayed on screen ascii_per_row = max(1, term_size()[0] // (asc_width + 2)) @@ -571,13 +664,13 @@ def create_config() -> Config: # Create config clear_screen(title) - c = Config(preset, color_system, light_dark, lightness, color_alignment, backend, custom_ascii_path=custom_ascii_path) + c = Config(preset, color_system, light_dark, lightness, color_alignment, backend, distro=logo_chosen, custom_ascii_path=custom_ascii_path) # Save config print() save = literal_input(f'Save config?', ['y', 'n'], 'y') if save == 'y': - c.save() + c.save(path) return c @@ -661,7 +754,7 @@ def run(): args.config_file = CONFIG_PATH # Load config or create config - config = create_config() if args.config else check_config(args.config_file) + config = create_config(args.config_file) if args.config else check_config(args.config_file) # Check if it's June (pride month) now = datetime.datetime.now() @@ -682,7 +775,7 @@ def run(): june_path.touch() # Use a custom distro - GLOBAL_CFG.override_distro = args.distro or config.distro + GLOBAL_CFG.override_distro = (config.distro if args.config else (args.distro or config.distro)) # Param overwrite config if args.preset: diff --git a/hyfetch/models.py b/hyfetch/models.py index 34da3d07..68699baa 100644 --- a/hyfetch/models.py +++ b/hyfetch/models.py @@ -1,6 +1,7 @@ from __future__ import annotations from dataclasses import dataclass, field +from pathlib import Path from .constants import CONFIG_PATH from .neofetch_util import ColorAlignment @@ -43,9 +44,10 @@ class Config: d['color_align'] = ColorAlignment.from_dict(d['color_align']) return from_dict(cls, d) - def save(self): - CONFIG_PATH.parent.mkdir(exist_ok=True, parents=True) - CONFIG_PATH.write_text(json_stringify(self, indent=4), 'utf-8') + def save(self, path: Path | None = None): + target = Path(path) if path else CONFIG_PATH + target.parent.mkdir(exist_ok=True, parents=True) + target.write_text(json_stringify(self, indent=4), 'utf-8') def custom_preset_profiles(self) -> dict[str, ColorProfile]: profiles: dict[str, ColorProfile] = {} diff --git a/hyfetch/neofetch_util.py b/hyfetch/neofetch_util.py index e0656ec9..2cadefd6 100644 --- a/hyfetch/neofetch_util.py +++ b/hyfetch/neofetch_util.py @@ -50,6 +50,12 @@ def literal_input(prompt: str, options: Iterable[str], default: str, show_ops: b if sel in lows: return options[lows.index(sel)] + # Aliases + if sel == 'original' and 'old' in lows: + return options[lows.index('old')] + if sel in ('new', 'regular') and 'default' in lows: + return options[lows.index('default')] + # Find starting abbreviation for i, op in enumerate(lows): if op.startswith(sel): @@ -182,7 +188,10 @@ class ColorAlignment: # Add new colors if self.mode == 'horizontal': colors = preset.with_length(len(lines)) - asc = '\n'.join([l.replace(f'${{c{back}}}', colors[i].to_ansi()) + color('&~&*') for i, l in enumerate(lines)]) + if back: + asc = '\n'.join([l.replace(f'${{c{back}}}', colors[i].to_ansi()) + color('&~&*') for i, l in enumerate(lines)]) + else: + asc = '\n'.join([re.sub(RE_NEOFETCH_COLOR, colors[i].to_ansi(), l) + color('&~&*') for i, l in enumerate(lines)]) else: raise NotImplementedError() diff --git a/neofetch b/neofetch index a231ac5a..1ff41ae8 100755 --- a/neofetch +++ b/neofetch @@ -16152,7 +16152,7 @@ ${c1} __.;=====;.__ _vi, ` --+=++++: .uvnvi. _._ -==+==+. .vvnvnI` .;==|==;. :|=||=|. -${c2}+QmQQm${c1}pvvnv; ${c2}_yYsyQQWUUQQQm #QmQ#${c1}:${c2}QQQWUV$QQmL +${c2}+QmQQm${c1}pvvnv; ${c2}_yYsyQQWUUQQQm #QmQ#${c1}:${c2}QQQWUV$QQm. ${c2} -QQWQW${c1}pvvo${c2}wZ?.wQQQE${c1}==<${c2}QWWQ/QWQW.QQWW${c1}(: ${c2}jQWQE ${c2} -$QQQQmmU' jQQQ@${c1}+=<${c2}QWQQ)mQQQ.mQQQC${c1}+;${c2}jWQQ@' ${c2} -$WQ8Y${c1}nI: ${c2}QWQQwgQQWV${c1}`${c2}mWQQ.jQWQQgyyWW@! @@ -16162,7 +16162,7 @@ ${c1} -1vvnvv. `~+++` ++|+++ -Invnvvnsi..___..=sv=. ` +Invnvnvnnnnnnnnvvnn;. ~|Invnvnvvnvvvnnv}+` - -~|{*l}*| + -~|{*l}*|~ EOF ;; "Void"*)