浏览代码

[+] Random custom coloring

Azalea (on HyDEV-Daisy) 3 年之前
父节点
当前提交
a1d687d3bd
共有 3 个文件被更改,包括 181 次插入42 次删除
  1. 72 12
      hyfetch/main.py
  2. 92 30
      hyfetch/neofetch_util.py
  3. 17 0
      hyfetch/presets.py

+ 72 - 12
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)

+ 92 - 30
hyfetch/neofetch_util.py

@@ -4,59 +4,122 @@ 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
 
 
-def get_command_path() -> str:
+RE_NEOFETCH_COLOR = re.compile('\\${c[0-9]}')
+
+
+def ascii_size(asc: str) -> tuple[int, int]:
     """
-    Get the absolute path of the neofetch command
+    Get distro ascii width, height ignoring color code
 
-    :return: Command path
+    :param asc: Distro ascii
+    :return: Width, Height
     """
-    return pkg_resources.resource_filename(__name__, 'scripts/neofetch_mod.sh')
+    return max(len(line) for line in re.sub(RE_NEOFETCH_COLOR, '', asc).split('\n')), len(asc.split('\n'))
 
 
-def get_distro_ascii() -> str:
+def normalize_ascii(asc: str) -> str:
     """
-    Get the distro ascii
-
-    :return: Distro ascii
+    Make sure every line are the same width
     """
-    return check_output([get_command_path(), "print_ascii"]).decode().strip()
+    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])
 
-def get_custom_distro_ascii(distro: str) -> str:
+                    # 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 distro ascii of a specific distro
+    Get the absolute path of the neofetch command
 
-    :return: Distro ascii
+    :return: Command path
     """
-    os.environ['CUSTOM_DISTRO'] = distro
-    return check_output([get_command_path(), "print_custom_ascii"]).decode().strip()
+    return pkg_resources.resource_filename(__name__, 'scripts/neofetch_mod.sh')
 
 
-def replace_colors(asc: str, preset: ColorProfile, mode: AnsiMode = COLOR_MODE):
-    # Remove existing colors
-    asc = re.sub('\\${.*?}', '', asc)
+def get_distro_ascii(distro: str | None = None) -> str:
+    """
+    Get the distro ascii of the current distro. Or if distro is specified, get the specific distro's
+    ascii art instead.
 
-    # 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: Distro ascii
+    """
+    cmd = 'print_ascii'
+    if distro:
+        os.environ['CUSTOM_DISTRO'] = distro
+        cmd = 'print_custom_ascii'
 
-    return asc, lines
+    return normalize_ascii(check_output([get_command_path(), cmd]).decode().strip())
 
 
-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)
 

+ 17 - 0
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([