From a1d687d3bdc58a20a95f804af41c069c2b38b522 Mon Sep 17 00:00:00 2001 From: "Azalea (on HyDEV-Daisy)" Date: Sun, 19 Jun 2022 20:50:09 -0400 Subject: [PATCH] [+] Random custom coloring --- hyfetch/main.py | 84 +++++++++++++++++++++---- hyfetch/neofetch_util.py | 128 +++++++++++++++++++++++++++++---------- hyfetch/presets.py | 17 ++++++ 3 files changed, 184 insertions(+), 45 deletions(-) diff --git a/hyfetch/main.py b/hyfetch/main.py index 6400ccdd..bf98b533 100755 --- a/hyfetch/main.py +++ b/hyfetch/main.py @@ -2,20 +2,19 @@ from __future__ import annotations import argparse -import importlib import json -import os +import random from dataclasses import dataclass -from pathlib import Path +from itertools import permutations from typing import Iterable from typing_extensions import Literal from . import constants -from .color_util import AnsiMode, printc, color, clear_screen, RGB +from .color_util import AnsiMode, printc, color, clear_screen from .constants import CONFIG_PATH, VERSION, TERM_LEN, TEST_ASCII_WIDTH, TEST_ASCII -from .neofetch_util import run_neofetch, replace_colors, get_custom_distro_ascii -from .presets import PRESETS, ColorProfile +from .neofetch_util import run_neofetch, get_distro_ascii, ColorAlignment, ascii_size +from .presets import PRESETS from .serializer import json_stringify @@ -25,6 +24,7 @@ class Config: mode: AnsiMode light_dark: Literal['light', 'dark'] = 'dark' lightness: float | None = None + color_align: ColorAlignment = ColorAlignment('horizontal') def save(self): CONFIG_PATH.parent.mkdir(exist_ok=True, parents=True) @@ -160,8 +160,8 @@ def create_config() -> Config: num_cols = TERM_LEN // (TEST_ASCII_WIDTH + 2) ratios = [col / (num_cols - 1) for col in range(num_cols)] ratios = [r * 0.6 + 0.2 for r in ratios] - lines = [replace_colors(TEST_ASCII.replace('{txt}', f'{r * 100:.0f}%'.center(5)), - _prs.set_light(r))[0].split('\n') for r in ratios] + lines = [ColorAlignment('horizontal').recolor_ascii(TEST_ASCII.replace( + '{txt}', f'{r * 100:.0f}%'.center(5)), _prs.set_light(r)).split('\n') for r in ratios] [printc(' '.join(line)) for line in zip(*lines)] while True: @@ -182,8 +182,66 @@ def create_config() -> Config: except Exception: printc('&cUnable to parse lightness value, please input it as a decimal or percentage (e.g. 0.5 or 50%)') + if lightness: + _prs = _prs.set_light(lightness) + title += f'\n&e4. Brightness: &r{f"{lightness:.2f}" if lightness else "unset"}' + + ############################# + # 5. Color arrangement + while True: + clear_screen(title) + printc(f'&a5. Let\'s choose a color arrangement!') + printc(f'You can choose standard horizontal or vertical alignment, or use one of the random color schemes, or assign colors yourself (TODO).') + print() + + asc = get_distro_ascii() + asc_width = ascii_size(asc)[0] + asciis = [ + ['Horizontal'.center(asc_width), *ColorAlignment('horizontal').recolor_ascii(asc, _prs).split('\n')], + ['Vertical'.center(asc_width), *ColorAlignment('vertical').recolor_ascii(asc, _prs).split('\n')], + ] + + # Random color schemes + # ascii_indices = + pis = list(range(len(_prs.unique_colors().colors))) + while len(pis) < 6: + pis += pis + perm = list(permutations(pis)) + choices = random.sample(perm, 4) + choices = [{i: n for i, n in enumerate(c)} for c in choices] + asciis += [[f'Random {i}'.center(asc_width), *ColorAlignment('custom', r).recolor_ascii(asc, _prs).split('\n')] + for i, r in enumerate(choices)] + + ascii_per_row = TERM_LEN // (asc_width + 2) + while asciis: + current = asciis[:ascii_per_row] + asciis = asciis[ascii_per_row:] + + # Print by row + [printc(' '.join(line)) for line in zip(*current)] + print() + + print('You can type "roll" to randomize again.') + print() + choice = literal_input(f'Your choice?', ['horizontal', 'vertical', 'roll', 'random1', 'random2', 'random3', 'random4'], 'horizontal') + + if choice == 'roll': + continue + + if choice in ['horizontal', 'vertical']: + color_alignment = ColorAlignment(choice) + elif choice.startswith('random'): + color_alignment = ColorAlignment('custom', choices[int(choice[6]) - 1]) + else: + raise NotImplementedError() + + break + + title += f'\n&e5. Color Alignment: &r{color_alignment}' + # Create config - c = Config(preset, color_system, light_dark, lightness) + clear_screen(title) + c = Config(preset, color_system, light_dark, lightness, color_alignment) # Save config print() @@ -238,13 +296,15 @@ def run(): preset = preset.lighten(args.scale) if args.light: preset = preset.set_light(args.light) + if config.lightness: + preset = preset.set_light(config.lightness) # Test distro ascii art if args.test_distro: - asc = get_custom_distro_ascii(args.test_distro) + asc = get_distro_ascii(args.test_distro) print(asc) - print(replace_colors(asc, preset, config.mode)[0]) + print(ColorAlignment('horizontal').recolor_ascii(asc, preset)) return # Run - run_neofetch(preset, config.mode) + run_neofetch(preset, config.color_align) diff --git a/hyfetch/neofetch_util.py b/hyfetch/neofetch_util.py index cbfec8da..d4dc6634 100644 --- a/hyfetch/neofetch_util.py +++ b/hyfetch/neofetch_util.py @@ -4,17 +4,94 @@ import os import platform import re import subprocess +from dataclasses import dataclass from pathlib import Path from subprocess import check_output from tempfile import TemporaryDirectory import pkg_resources +from hyfetch.color_util import color +from typing_extensions import Literal -from .color_util import AnsiMode -from .constants import COLOR_MODE from .presets import ColorProfile +RE_NEOFETCH_COLOR = re.compile('\\${c[0-9]}') + + +def ascii_size(asc: str) -> tuple[int, int]: + """ + Get distro ascii width, height ignoring color code + + :param asc: Distro ascii + :return: Width, Height + """ + return max(len(line) for line in re.sub(RE_NEOFETCH_COLOR, '', asc).split('\n')), len(asc.split('\n')) + + +def normalize_ascii(asc: str) -> str: + """ + Make sure every line are the same width + """ + w = ascii_size(asc)[0] + return '\n'.join(line + ' ' * (w - ascii_size(line)[0]) for line in asc.split('\n')) + + +@dataclass +class ColorAlignment: + mode: Literal['horizontal', 'vertical', 'custom'] + + # custom_colors[ascii color index] = unique color index in preset + custom_colors: dict[int, int] = () + + def recolor_ascii(self, asc: str, preset: ColorProfile) -> str: + """ + Use the color alignment to recolor an ascii art + + :return Colored ascii, Uncolored lines + """ + if self.mode in ['horizontal', 'vertical']: + # Remove existing colors + asc = re.sub(RE_NEOFETCH_COLOR, '', asc) + lines = asc.split('\n') + + # Add new colors + if self.mode == 'horizontal': + colors = preset.with_length(len(lines)) + asc = '\n'.join([colors[i].to_ansi() + l + color('&r') for i, l in enumerate(lines)]) + else: + asc = '\n'.join(preset.color_text(line) + color('&r') for line in lines) + + else: + preset = preset.unique_colors() + + # Apply colors + new = [] + start_color = None + color_map = {ai: preset.colors[pi].to_ansi() for ai, pi in self.custom_colors.items()} + for line in asc.split('\n'): + # Line has color placeholders + if len(RE_NEOFETCH_COLOR.findall(line)) > 0: + # Get the last placeholder for the next line + last = int(RE_NEOFETCH_COLOR.findall(line)[-1][3]) + + # Replace placeholders + for ascii_i, c in color_map.items(): + line = line.replace(f'${{c{ascii_i}}}', c) + + # Add to new ascii + new.append(f'{start_color or ""}{line}') + + # Change next start color + start_color = color_map[last] + else: + new.append(f'{start_color or ""}{line}') + + asc = '\n'.join(new) + + return asc + + def get_command_path() -> str: """ Get the absolute path of the neofetch command @@ -24,39 +101,25 @@ def get_command_path() -> str: return pkg_resources.resource_filename(__name__, 'scripts/neofetch_mod.sh') -def get_distro_ascii() -> str: +def get_distro_ascii(distro: str | None = None) -> str: """ - Get the distro ascii + Get the distro ascii of the current distro. Or if distro is specified, get the specific distro's + ascii art instead. :return: Distro ascii """ - return check_output([get_command_path(), "print_ascii"]).decode().strip() + cmd = 'print_ascii' + if distro: + os.environ['CUSTOM_DISTRO'] = distro + cmd = 'print_custom_ascii' + + return normalize_ascii(check_output([get_command_path(), cmd]).decode().strip()) -def get_custom_distro_ascii(distro: str) -> str: - """ - Get the distro ascii of a specific distro - - :return: Distro ascii - """ - os.environ['CUSTOM_DISTRO'] = distro - return check_output([get_command_path(), "print_custom_ascii"]).decode().strip() - - -def replace_colors(asc: str, preset: ColorProfile, mode: AnsiMode = COLOR_MODE): - # Remove existing colors - asc = re.sub('\\${.*?}', '', asc) - - # Add new colors - lines = asc.split('\n') - colors = preset.with_length(len(lines)) - asc = '\n'.join([colors[i].to_ansi(mode) + l for i, l in enumerate(lines)]) - - return asc, lines - - -def run_neofetch(preset: ColorProfile, mode: AnsiMode): - asc, lines = replace_colors(get_distro_ascii(), preset, mode) +def run_neofetch(preset: ColorProfile, alignment: ColorAlignment): + asc = get_distro_ascii() + w, h = ascii_size(asc) + asc = alignment.recolor_ascii(asc, preset) # Write temp file with TemporaryDirectory() as tmp_dir: @@ -65,8 +128,8 @@ def run_neofetch(preset: ColorProfile, mode: AnsiMode): path.write_text(asc) # Call neofetch with the temp file - os.environ['ascii_len'] = str(max(len(l) for l in lines)) - os.environ['ascii_lines'] = str(len(lines)) + os.environ['ascii_len'] = str(w) + os.environ['ascii_lines'] = str(h) if platform.system() != 'Windows': os.system(f'{get_command_path()} --ascii --source {path.absolute()} --ascii-colors') @@ -75,8 +138,7 @@ def run_neofetch(preset: ColorProfile, mode: AnsiMode): cmd = get_command_path().replace("\\", "/").replace("C:/", "/c/") path_str = str(path.absolute()).replace('\\', '/').replace('C:/', '/c/') - cmd = f'ascii_len={max(len(l) for l in lines)} ascii_lines={len(lines)} ' \ - f'{cmd} --ascii --source {path_str} --ascii-colors' + cmd = f'ascii_len={w} ascii_lines={h} {cmd} --ascii --source {path_str} --ascii-colors' full_cmd = ['C:\\Program Files\\Git\\bin\\bash.exe', '-c', cmd] # print(full_cmd) diff --git a/hyfetch/presets.py b/hyfetch/presets.py index 415234b5..1a12d12d 100644 --- a/hyfetch/presets.py +++ b/hyfetch/presets.py @@ -1,10 +1,21 @@ from __future__ import annotations +from typing import Iterable + from typing_extensions import Literal from .color_util import RGB +def remove_duplicates(seq: Iterable) -> list: + """ + Remove duplicate items from a sequence while preserving the order + """ + seen = set() + seen_add = seen.add + return [x for x in seq if not (x in seen or seen_add(x))] + + class ColorProfile: raw: list[str] colors: list[RGB] @@ -98,6 +109,12 @@ class ColorProfile: """ return ColorProfile([c.set_light(light) for c in self.colors]) + def unique_colors(self) -> ColorProfile: + """ + Create another color profile with only the unique colors + """ + return ColorProfile(remove_duplicates(self.colors)) + PRESETS: dict[str, ColorProfile] = { 'rainbow': ColorProfile([