Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eb76b2f474 | |||
| c9ed8e2088 | |||
| b68e0ccf52 | |||
| 143bfd2db7 | |||
| 2e20d4e529 | |||
| 66ff8d06d9 | |||
| 887f766464 | |||
| 85eaad14f2 | |||
| d11796ef02 | |||
| 59f5fd5651 | |||
| a1d687d3bd | |||
| 7bd96e422f | |||
| d1f601048b | |||
| 3b2fb773d3 | |||
| fed771d930 | |||
| 8930f921b4 | |||
| 5de1bf65e4 | |||
| b46d0e96f4 | |||
| 1c67333b45 | |||
| 5e26a44c19 | |||
| 865dea5dc3 | |||
| d5eea9a8ef | |||
| 365aedafe6 | |||
| 399fcd3c64 | |||
| 299adb8e4d | |||
| f86792356e | |||
| 892ed4e141 | |||
| 5364e22f39 | |||
| 6d77ebc7a7 | |||
| 1eab4bb157 | |||
| d59e4a64d3 | |||
| 0dd67146d7 | |||
| 46b7771f2c | |||
| 002697b49a | |||
| 9a77c39170 | |||
| b744be3255 | |||
| e97c64e7c2 | |||
| b572e5d35f | |||
| 86af5598c6 | |||
| d7273909f6 | |||
| 7b4f209ebe | |||
| 46d00666e2 | |||
| 1cffe3556a | |||
| 07d94eec8b | |||
| 62469e6f52 | |||
| 2c8261dc9a | |||
| 3219547c92 | |||
| b0bc4e64a9 |
@@ -19,8 +19,11 @@ pip install hyfetch
|
|||||||
Currently, these distributions have existing packages for HyFetch:
|
Currently, these distributions have existing packages for HyFetch:
|
||||||
|
|
||||||
* ArchLinux: `yay -S hyfetch` (Thanks to @ Aleksana)
|
* ArchLinux: `yay -S hyfetch` (Thanks to @ Aleksana)
|
||||||
|
* NixOS: `nix-env -i hyfetch` ([In Progress](https://github.com/NixOS/nixpkgs/pull/170309))
|
||||||
* Guix: [In progress](https://issues.guix.gnu.org/54847#8-lineno27)
|
* Guix: [In progress](https://issues.guix.gnu.org/54847#8-lineno27)
|
||||||
|
|
||||||
|
Currently, if you're using NixOS, you can use HyFetch with `nix-env -if https://github.com/hykilpikonna/hyfetch/tarball/master -A hyfetch`
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
When you run `hyfetch` for the first time, it will prompt you to choose a color system and a preset. Just follow the prompt, and everything should work (hopefully). If something doesn't work, feel free to submit an issue!
|
When you run `hyfetch` for the first time, it will prompt you to choose a color system and a preset. Just follow the prompt, and everything should work (hopefully). If something doesn't work, feel free to submit an issue!
|
||||||
@@ -48,6 +51,14 @@ Feel free to experiment with it!
|
|||||||
* [ ] Add interactive configurator for adjusting brightness
|
* [ ] Add interactive configurator for adjusting brightness
|
||||||
* [ ] Add configuration to emphasize certain parts of the original ASCII art (to make icons like Fedora and Ubuntu look nicer)
|
* [ ] Add configuration to emphasize certain parts of the original ASCII art (to make icons like Fedora and Ubuntu look nicer)
|
||||||
|
|
||||||
|
### 1.0.7
|
||||||
|
|
||||||
|
* Fix: Make config path not on init but when it's actually needed.
|
||||||
|
|
||||||
|
### 1.0.6
|
||||||
|
|
||||||
|
* Remove `hypy_utils` dependency to make packaging easier.
|
||||||
|
|
||||||
### 1.0.5
|
### 1.0.5
|
||||||
|
|
||||||
* Fix terminal emulator detection ([PR #2](https://github.com/hykilpikonna/hyfetch/pull/2))
|
* Fix terminal emulator detection ([PR #2](https://github.com/hykilpikonna/hyfetch/pull/2))
|
||||||
|
|||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
with import <nixpkgs> {};
|
||||||
|
|
||||||
|
rec {
|
||||||
|
hyfetch = python3Packages.buildPythonPackage rec {
|
||||||
|
pname = "HyFetch";
|
||||||
|
version = "1.0.7";
|
||||||
|
|
||||||
|
src = pythonPackages.fetchPypi {
|
||||||
|
inherit pname version;
|
||||||
|
hash = "sha256-3/6/3EtTqHXTMuRIo2nclIxYSzOFvQegR29OJsKMQU4=";
|
||||||
|
};
|
||||||
|
|
||||||
|
propagatedBuildInputs = with python3Packages; [
|
||||||
|
typing-extensions
|
||||||
|
setuptools
|
||||||
|
];
|
||||||
|
|
||||||
|
doCheck = false;
|
||||||
|
|
||||||
|
meta = with lib; {
|
||||||
|
description = "neofetch with pride flags <3";
|
||||||
|
longDescription = ''
|
||||||
|
HyFetch is a command-line system information tool fork of neofetch.
|
||||||
|
HyFetch displays information about your system next to your OS logo
|
||||||
|
in ASCII representation. The ASCII representation is then colored in
|
||||||
|
the pattern of the pride flag of your choice. The main purpose of
|
||||||
|
HyFetch is to be used in screenshots to show other users what
|
||||||
|
operating system or distribution you are running, what theme or
|
||||||
|
icon set you are using, etc.
|
||||||
|
'';
|
||||||
|
homepage = "https://github.com/hykilpikonna/HyFetch";
|
||||||
|
license = licenses.mit;
|
||||||
|
mainProgram = "hyfetch";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from . import main
|
from . import main
|
||||||
|
|
||||||
|
__version__ = main.VERSION
|
||||||
__version__ = '1.0.5'
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from numpy import ndarray
|
||||||
|
|
||||||
|
from .color_util import RGB
|
||||||
|
|
||||||
|
|
||||||
|
def create_gradient_hex(colors: list[str], resolution: int = 300) -> ndarray:
|
||||||
|
"""
|
||||||
|
Create gradient array from hex
|
||||||
|
"""
|
||||||
|
colors = np.array([RGB.from_hex(s) for s in colors])
|
||||||
|
return create_gradient(colors, resolution)
|
||||||
|
|
||||||
|
|
||||||
|
def create_gradient(colors: ndarray, resolution: int) -> ndarray:
|
||||||
|
"""
|
||||||
|
Create gradient 2d array.
|
||||||
|
|
||||||
|
Usage: arr[ratio / len(arr), :] = Scaled gradient color at that point
|
||||||
|
"""
|
||||||
|
result = np.zeros((resolution * (len(colors) - 1), 3), dtype='uint8')
|
||||||
|
|
||||||
|
# Create gradient mapping
|
||||||
|
for i in range(len(colors) - 1):
|
||||||
|
c1 = colors[i, :]
|
||||||
|
c2 = colors[i + 1, :]
|
||||||
|
bi = i * resolution
|
||||||
|
|
||||||
|
for r in range(resolution):
|
||||||
|
ratio = r / resolution
|
||||||
|
result[bi + r, :] = c2 * ratio + c1 * (1 - ratio)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_raw(gradient: ndarray, ratio: float) -> ndarray:
|
||||||
|
"""
|
||||||
|
:param gradient: Gradient array (2d)
|
||||||
|
:param ratio: Between 0-1
|
||||||
|
:return: RGB subarray (1d, has 3 values)
|
||||||
|
"""
|
||||||
|
if ratio == 1:
|
||||||
|
return gradient[-1, :]
|
||||||
|
|
||||||
|
i = int(ratio * len(gradient))
|
||||||
|
return gradient[i, :]
|
||||||
|
|
||||||
|
|
||||||
|
class Scale:
|
||||||
|
colors: ndarray
|
||||||
|
rgb: ndarray
|
||||||
|
|
||||||
|
def __init__(self, scale: list[str], resolution: int = 300):
|
||||||
|
self.colors = np.array([RGB.from_hex(s) for s in scale])
|
||||||
|
self.rgb = create_gradient(self.colors, resolution)
|
||||||
|
|
||||||
|
def __call__(self, ratio: float) -> RGB:
|
||||||
|
"""
|
||||||
|
:param ratio: Between 0-1
|
||||||
|
"""
|
||||||
|
return RGB(*get_raw(self.rgb, ratio))
|
||||||
|
|
||||||
|
|
||||||
|
def test_color_scale():
|
||||||
|
scale = Scale(['#232323', '#4F1879', '#B43A78', '#F98766', '#FCFAC0'])
|
||||||
|
|
||||||
|
colors = 100
|
||||||
|
for i in range(colors + 1):
|
||||||
|
print(scale(i / colors).to_ansi_rgb(False), end=' ')
|
||||||
+80
-5
@@ -1,10 +1,69 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import colorsys
|
import colorsys
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple, Callable, Optional
|
||||||
|
|
||||||
from typing_extensions import Literal
|
from typing_extensions import Literal
|
||||||
|
|
||||||
|
from .constants import GLOBAL_CFG
|
||||||
|
|
||||||
AnsiMode = Literal['default', 'ansi', '8bit', 'rgb']
|
AnsiMode = Literal['default', 'ansi', '8bit', 'rgb']
|
||||||
|
LightDark = Literal['light', 'dark']
|
||||||
|
|
||||||
|
|
||||||
|
MINECRAFT_COLORS = ["&0/\033[0;30m", "&1/\033[0;34m", "&2/\033[0;32m", "&3/\033[0;36m", "&4/\033[0;31m",
|
||||||
|
"&5/\033[0;35m", "&6/\033[0;33m", "&7/\033[0;37m", "&8/\033[1;30m", "&9/\033[1;34m",
|
||||||
|
"&a/\033[1;32m", "&b/\033[1;36m", "&c/\033[1;31m", "&d/\033[1;35m", "&e/\033[1;33m",
|
||||||
|
"&f/\033[1;37m",
|
||||||
|
"&r/\033[0m", "&l/\033[1m", "&o/\033[3m", "&n/\033[4m", "&-/\n"]
|
||||||
|
MINECRAFT_COLORS = [(r[:2], r[3:]) for r in MINECRAFT_COLORS]
|
||||||
|
|
||||||
|
|
||||||
|
def color(msg: str) -> str:
|
||||||
|
"""
|
||||||
|
Replace extended minecraft color codes in string
|
||||||
|
:param msg: Message with minecraft color codes
|
||||||
|
:return: Message with escape codes
|
||||||
|
"""
|
||||||
|
for code, esc in MINECRAFT_COLORS:
|
||||||
|
msg = msg.replace(code, esc)
|
||||||
|
|
||||||
|
while '&gf(' in msg or '&gb(' in msg:
|
||||||
|
i = msg.index('&gf(') if '&gf(' in msg else msg.index('&gb(')
|
||||||
|
end = msg.index(')', i)
|
||||||
|
code = msg[i + 4:end]
|
||||||
|
fore = msg[i + 2] == 'f'
|
||||||
|
|
||||||
|
if code.startswith('#'):
|
||||||
|
rgb = tuple(int(code.lstrip('#')[i:i+2], 16) for i in (0, 2, 4))
|
||||||
|
else:
|
||||||
|
code = code.replace(',', ' ').replace(';', ' ').replace(' ', ' ')
|
||||||
|
rgb = tuple(int(c) for c in code.split(' '))
|
||||||
|
|
||||||
|
msg = msg[:i] + RGB(*rgb).to_ansi(foreground=fore) + msg[end + 1:]
|
||||||
|
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
def printc(msg: str):
|
||||||
|
"""
|
||||||
|
Print with color
|
||||||
|
:param msg: Message with minecraft color codes
|
||||||
|
"""
|
||||||
|
print(color(msg + '&r'))
|
||||||
|
|
||||||
|
|
||||||
|
def clear_screen(title: str = ''):
|
||||||
|
"""
|
||||||
|
Clear screen using ANSI escape codes
|
||||||
|
"""
|
||||||
|
if not GLOBAL_CFG.debug:
|
||||||
|
print('\033[2J\033[H', end='')
|
||||||
|
|
||||||
|
if title:
|
||||||
|
print()
|
||||||
|
printc(title)
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
def redistribute_rgb(r: int, g: int, b: int) -> tuple[int, int, int]:
|
def redistribute_rgb(r: int, g: int, b: int) -> tuple[int, int, int]:
|
||||||
@@ -99,7 +158,9 @@ class RGB(NamedTuple):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def to_ansi(self, mode: AnsiMode, foreground: bool = True):
|
def to_ansi(self, mode: AnsiMode | None = None, foreground: bool = True):
|
||||||
|
if not mode:
|
||||||
|
mode = GLOBAL_CFG.color_mode
|
||||||
if mode == 'rgb':
|
if mode == 'rgb':
|
||||||
return self.to_ansi_rgb(foreground)
|
return self.to_ansi_rgb(foreground)
|
||||||
if mode == '8bit':
|
if mode == '8bit':
|
||||||
@@ -116,12 +177,26 @@ class RGB(NamedTuple):
|
|||||||
"""
|
"""
|
||||||
return RGB(*redistribute_rgb(*[v * multiplier for v in self]))
|
return RGB(*redistribute_rgb(*[v * multiplier for v in self]))
|
||||||
|
|
||||||
def set_light(self, light: int) -> 'RGB':
|
def set_light(self, light: float, at_least: bool | None = None, at_most: bool | None = None) -> 'RGB':
|
||||||
"""
|
"""
|
||||||
Set HSL lightness value
|
Set HSL lightness value
|
||||||
|
|
||||||
:param light: Lightness value
|
:param light: Lightness value (0-1)
|
||||||
|
:param at_least: Set the lightness to at least this value (no change if greater)
|
||||||
|
:param at_most: Set the lightness to at most this value (no change if lesser)
|
||||||
:return: New color (original isn't modified)
|
:return: New color (original isn't modified)
|
||||||
"""
|
"""
|
||||||
|
# Convert to HSL
|
||||||
h, l, s = colorsys.rgb_to_hls(*[v / 255.0 for v in self])
|
h, l, s = colorsys.rgb_to_hls(*[v / 255.0 for v in self])
|
||||||
return RGB(*[round(v * 255.0) for v in colorsys.hls_to_rgb(h, light, s)])
|
|
||||||
|
# Modify light value
|
||||||
|
if at_least is None and at_most is None:
|
||||||
|
l = light
|
||||||
|
else:
|
||||||
|
if at_most:
|
||||||
|
l = min(l, light)
|
||||||
|
if at_least:
|
||||||
|
l = max(l, light)
|
||||||
|
|
||||||
|
# Convert back to RGB
|
||||||
|
return RGB(*[round(v * 255.0) for v in colorsys.hls_to_rgb(h, l, s)])
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from typing_extensions import Literal
|
||||||
|
|
||||||
|
CONFIG_PATH = Path.home() / '.config/hyfetch.json'
|
||||||
|
VERSION = '1.1.0'
|
||||||
|
|
||||||
|
# Obtain terminal size
|
||||||
|
try:
|
||||||
|
TERM_LEN = os.get_terminal_size().columns
|
||||||
|
except Exception:
|
||||||
|
TERM_LEN = 40
|
||||||
|
|
||||||
|
TEST_ASCII = r"""
|
||||||
|
### |\___/| ###
|
||||||
|
### ) ( ###
|
||||||
|
## =\ /= ##
|
||||||
|
#### )===( ####
|
||||||
|
### / \ ###
|
||||||
|
### | | ###
|
||||||
|
## / {txt} \ ##
|
||||||
|
## \ / ##
|
||||||
|
_/\_\_ _/_/\_
|
||||||
|
|##| ( ( |##|
|
||||||
|
|##| ) ) |##|
|
||||||
|
|##| (_( |##|""".strip('\n')
|
||||||
|
|
||||||
|
TEST_ASCII_WIDTH = max(len(line) for line in TEST_ASCII.split('\n'))
|
||||||
|
DEFAULT_DARK_L = 0.
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class GlobalConfig:
|
||||||
|
# Global color mode default to 8-bit for compatibility
|
||||||
|
color_mode: str
|
||||||
|
override_distro: str | None
|
||||||
|
debug: bool
|
||||||
|
is_light: bool
|
||||||
|
|
||||||
|
def light_dark(self) -> Literal['light', 'dark']:
|
||||||
|
return 'light' if self.is_light else 'dark'
|
||||||
|
|
||||||
|
def default_lightness(self, term: Literal['light', 'dark'] | None = None) -> float:
|
||||||
|
if term is None:
|
||||||
|
term = self.light_dark()
|
||||||
|
return 0.65 if term.lower() == 'dark' else 0.4
|
||||||
|
|
||||||
|
|
||||||
|
GLOBAL_CFG = GlobalConfig(color_mode='8bit', override_distro=None, debug=False, is_light=False)
|
||||||
+216
-66
@@ -3,27 +3,18 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
from dataclasses import dataclass
|
import random
|
||||||
from pathlib import Path
|
import re
|
||||||
|
from itertools import permutations
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
from hypy_utils import printc, json_stringify, color
|
from hyfetch import presets
|
||||||
|
|
||||||
from .color_util import AnsiMode
|
from .color_util import printc, color, clear_screen, LightDark
|
||||||
from .neofetch_util import run_neofetch
|
from .constants import CONFIG_PATH, VERSION, TERM_LEN, TEST_ASCII_WIDTH, TEST_ASCII, GLOBAL_CFG
|
||||||
from .presets import PRESETS, ColorProfile
|
from .models import Config
|
||||||
|
from .neofetch_util import run_neofetch, get_distro_ascii, ColorAlignment, ascii_size, color_alignments
|
||||||
CONFIG_PATH = Path.home() / '.config/hyfetch.json'
|
from .presets import PRESETS
|
||||||
CONFIG_PATH.parent.mkdir(exist_ok=True, parents=True)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Config:
|
|
||||||
preset: str
|
|
||||||
mode: AnsiMode
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
CONFIG_PATH.write_text(json_stringify(self), 'utf-8')
|
|
||||||
|
|
||||||
|
|
||||||
def check_config() -> Config:
|
def check_config() -> Config:
|
||||||
@@ -36,25 +27,30 @@ def check_config() -> Config:
|
|||||||
:return: Config object
|
:return: Config object
|
||||||
"""
|
"""
|
||||||
if CONFIG_PATH.is_file():
|
if CONFIG_PATH.is_file():
|
||||||
return Config(**json.loads(CONFIG_PATH.read_text('utf-8')))
|
return Config.from_dict(json.loads(CONFIG_PATH.read_text('utf-8')))
|
||||||
|
|
||||||
return create_config()
|
return create_config()
|
||||||
|
|
||||||
|
|
||||||
def literal_input(prompt: str, options: Iterable[str], default: str) -> str:
|
def literal_input(prompt: str, options: Iterable[str], default: str, show_ops: bool = True) -> str:
|
||||||
"""
|
"""
|
||||||
Ask the user to provide an input among a list of options
|
Ask the user to provide an input among a list of options
|
||||||
|
|
||||||
:param prompt: Input prompt
|
:param prompt: Input prompt
|
||||||
:param options: Options
|
:param options: Options
|
||||||
:param default: Default option
|
:param default: Default option
|
||||||
|
:param show_ops: Show options
|
||||||
:return: Selection
|
:return: Selection
|
||||||
"""
|
"""
|
||||||
options = list(options)
|
options = list(options)
|
||||||
lows = [o.lower() for o in options]
|
lows = [o.lower() for o in options]
|
||||||
|
|
||||||
op_text = '|'.join([f'&l&n{o}&r' if o == default else o for o in options])
|
if show_ops:
|
||||||
printc(f'{prompt} ({op_text})')
|
op_text = '|'.join([f'&l&n{o}&r' if o == default else o for o in options])
|
||||||
|
printc(f'{prompt} ({op_text})')
|
||||||
|
else:
|
||||||
|
printc(f'{prompt} (default: {default})')
|
||||||
|
|
||||||
selection = input('> ') or default
|
selection = input('> ') or default
|
||||||
while not selection.lower() in lows:
|
while not selection.lower() in lows:
|
||||||
print(f'Invalid selection! {selection} is not one of {"|".join(options)}')
|
print(f'Invalid selection! {selection} is not one of {"|".join(options)}')
|
||||||
@@ -64,65 +60,180 @@ def literal_input(prompt: str, options: Iterable[str], default: str) -> str:
|
|||||||
return options[lows.index(selection)]
|
return options[lows.index(selection)]
|
||||||
|
|
||||||
|
|
||||||
def center_text(txt: str, spaces: int) -> str:
|
|
||||||
"""
|
|
||||||
Put the text in the center in a defined space
|
|
||||||
|
|
||||||
>>> center_text('meow', 9)
|
|
||||||
' meow '
|
|
||||||
|
|
||||||
:param txt: Text
|
|
||||||
:param spaces: Total space of the text
|
|
||||||
:return: Text with length spaces
|
|
||||||
"""
|
|
||||||
spaces -= len(txt)
|
|
||||||
|
|
||||||
if spaces % 2 == 1:
|
|
||||||
spaces -= 1
|
|
||||||
txt += ' '
|
|
||||||
|
|
||||||
while spaces > 0:
|
|
||||||
spaces -= 2
|
|
||||||
txt = f' {txt} '
|
|
||||||
|
|
||||||
return txt
|
|
||||||
|
|
||||||
|
|
||||||
def create_config() -> Config:
|
def create_config() -> Config:
|
||||||
"""
|
"""
|
||||||
Create config interactively
|
Create config interactively
|
||||||
|
|
||||||
:return: Config object (automatically stored)
|
:return: Config object (automatically stored)
|
||||||
"""
|
"""
|
||||||
# Select color system
|
title = 'Welcome to &b&lhy&f&lfetch&r! Let\'s set up some colors first.'
|
||||||
# TODO: Demo of each color system
|
clear_screen(title)
|
||||||
color_system = literal_input('Which &acolor &bsystem &rdo you want to use?',
|
|
||||||
['8bit', 'rgb'], 'rgb')
|
|
||||||
|
|
||||||
# Print preset
|
##############################
|
||||||
print('Available presets:\n')
|
# 1. Select color system
|
||||||
spacing = max(max(len(k) for k in PRESETS.keys()), 30)
|
try:
|
||||||
|
# Demonstrate RGB with a gradient. This requires numpy
|
||||||
|
from .color_scale import Scale
|
||||||
|
|
||||||
|
scale2 = Scale(['#12c2e9', '#c471ed', '#f7797d'])
|
||||||
|
_8bit = [scale2(i / TERM_LEN).to_ansi_8bit(False) for i in range(TERM_LEN)]
|
||||||
|
_rgb = [scale2(i / TERM_LEN).to_ansi_rgb(False) for i in range(TERM_LEN)]
|
||||||
|
|
||||||
|
printc('&f' + ''.join(c + t for c, t in zip(_8bit, '8bit Color Testing'.center(TERM_LEN))))
|
||||||
|
printc('&f' + ''.join(c + t for c, t in zip(_rgb, 'RGB Color Testing'.center(TERM_LEN))))
|
||||||
|
|
||||||
|
print()
|
||||||
|
printc(f'&a1. Which &bcolor system &ado you want to use?')
|
||||||
|
printc(f'(If you can\'t see colors under "RGB Color Testing", please choose 8bit)')
|
||||||
|
print()
|
||||||
|
color_system = literal_input('Your choice?', ['8bit', 'rgb'], 'rgb')
|
||||||
|
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
# Numpy not found, skip gradient test, use fallback
|
||||||
|
color_system = literal_input('Which &acolor &bsystem &rdo you want to use?',
|
||||||
|
['8bit', 'rgb'], 'rgb')
|
||||||
|
|
||||||
|
# Override global color mode
|
||||||
|
GLOBAL_CFG.color_mode = color_system
|
||||||
|
title += f'\n&e1. Selected color mode: &r{color_system}'
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# 2. Select light/dark mode
|
||||||
|
clear_screen(title)
|
||||||
|
light_dark = literal_input(f'2. Is your terminal in &gf(#85e7e9)light mode&r or &gf(#c471ed)dark mode&r?',
|
||||||
|
['light', 'dark'], 'dark')
|
||||||
|
is_light = light_dark == 'light'
|
||||||
|
GLOBAL_CFG.is_light = is_light
|
||||||
|
title += f'\n&e2. Light/Dark: &r{light_dark}'
|
||||||
|
|
||||||
|
##############################
|
||||||
|
# 3. Choose preset
|
||||||
|
clear_screen(title)
|
||||||
|
printc('&a3. Let\'s choose a flag!')
|
||||||
|
printc('Available flag presets:')
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Create flags = [[lines]]
|
||||||
flags = []
|
flags = []
|
||||||
|
spacing = max(max(len(k) for k in PRESETS.keys()), 20)
|
||||||
for name, preset in PRESETS.items():
|
for name, preset in PRESETS.items():
|
||||||
flags.append([preset.color_text(' ' * spacing, foreground=False),
|
flag = preset.color_text(' ' * spacing, foreground=False)
|
||||||
'&0' + preset.color_text(center_text(name, spacing), foreground=False),
|
flags.append([name.center(spacing), flag, flag, flag])
|
||||||
preset.color_text(' ' * spacing, foreground=False)])
|
|
||||||
flags_per_row = 3
|
# Calculate flags per row
|
||||||
|
flags_per_row = TERM_LEN // (spacing + 2)
|
||||||
while flags:
|
while flags:
|
||||||
current = flags[:flags_per_row]
|
current = flags[:flags_per_row]
|
||||||
flags = flags[flags_per_row:]
|
flags = flags[flags_per_row:]
|
||||||
for line in range(len(current[0])):
|
|
||||||
printc(' '.join(flag[line] for flag in current))
|
# Print by row
|
||||||
|
[printc(' '.join(line)) for line in zip(*current)]
|
||||||
print()
|
print()
|
||||||
|
|
||||||
print()
|
print()
|
||||||
tmp = PRESETS['rainbow'].color_text('preset')
|
tmp = PRESETS['rainbow'].set_light_dl_def(light_dark).color_text('preset')
|
||||||
preset = literal_input(f'Which {tmp} do you want to use?', PRESETS.keys(), 'rainbow')
|
preset = literal_input(f'Which {tmp} do you want to use?', PRESETS.keys(), 'rainbow', show_ops=False)
|
||||||
|
_prs = PRESETS[preset]
|
||||||
|
title += f'\n&e3. Selected flag: &r{_prs.color_text(preset)}'
|
||||||
|
|
||||||
|
#############################
|
||||||
|
# 4. Dim/lighten colors
|
||||||
|
clear_screen(title)
|
||||||
|
printc(f'&a4. Let\'s adjust the color brightness!')
|
||||||
|
printc(f'The colors might be a little bit too {"bright" if is_light else "dark"} for {light_dark} mode.')
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Print cats
|
||||||
|
num_cols = TERM_LEN // (TEST_ASCII_WIDTH + 2)
|
||||||
|
ratios = [col / (num_cols - 1) for col in range(num_cols)]
|
||||||
|
ratios = [(r * 0.4 + 0.1) if is_light else (r * 0.4 + 0.5) for r in ratios]
|
||||||
|
lines = [ColorAlignment('horizontal').recolor_ascii(TEST_ASCII.replace(
|
||||||
|
'{txt}', f'{r * 100:.0f}%'.center(5)), _prs.set_light_dl(r, light_dark)).split('\n') for r in ratios]
|
||||||
|
[printc(' '.join(line)) for line in zip(*lines)]
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print()
|
||||||
|
printc(f'Which brightness level look the best? (Default: left blank = {GLOBAL_CFG.default_lightness(light_dark):.2f} for {light_dark} mode)')
|
||||||
|
lightness = input('> ').strip().lower() or None
|
||||||
|
|
||||||
|
# Parse lightness
|
||||||
|
if not lightness or lightness in ['unset', 'none']:
|
||||||
|
lightness = None
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
lightness = int(lightness[:-1]) / 100 if lightness.endswith('%') else float(lightness)
|
||||||
|
assert 0 <= lightness <= 1
|
||||||
|
break
|
||||||
|
|
||||||
|
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_dl(lightness, light_dark)
|
||||||
|
title += f'\n&e4. Brightness: &r{f"{lightness:.2f}" if lightness else "unset"}'
|
||||||
|
|
||||||
|
#############################
|
||||||
|
# 5. Color arrangement
|
||||||
|
color_alignment = None
|
||||||
|
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 = [
|
||||||
|
[*ColorAlignment('horizontal').recolor_ascii(asc, _prs).split('\n'), 'Horizontal'.center(asc_width)],
|
||||||
|
[*ColorAlignment('vertical').recolor_ascii(asc, _prs).split('\n'), 'Vertical'.center(asc_width)],
|
||||||
|
]
|
||||||
|
ascii_per_row = TERM_LEN // (asc_width + 2)
|
||||||
|
|
||||||
|
# Random color schemes
|
||||||
|
# ascii_indices =
|
||||||
|
pis = list(range(len(_prs.unique_colors().colors)))
|
||||||
|
while len(pis) < len(set(re.findall('(?<=\\${c)[0-9](?=})', asc))):
|
||||||
|
pis += pis
|
||||||
|
perm = list(permutations(pis))
|
||||||
|
random_count = ascii_per_row * 2 - 2
|
||||||
|
choices = random.sample(perm, random_count)
|
||||||
|
choices = [{i: n for i, n in enumerate(c)} for c in choices]
|
||||||
|
asciis += [[*ColorAlignment('custom', r).recolor_ascii(asc, _prs).split('\n'), f'random{i}'.center(asc_width)]
|
||||||
|
for i, r in enumerate(choices)]
|
||||||
|
|
||||||
|
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'] + [f'random{i}' for i in range(random_count)], '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])])
|
||||||
|
else:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
title += f'\n&e5. Color Alignment: &r{color_alignment}'
|
||||||
|
|
||||||
# Create config
|
# Create config
|
||||||
c = Config(preset, color_system)
|
clear_screen(title)
|
||||||
|
c = Config(preset, color_system, light_dark, lightness, color_alignment)
|
||||||
|
|
||||||
# Save config
|
# Save config
|
||||||
|
print()
|
||||||
save = literal_input(f'Save config?', ['y', 'n'], 'y')
|
save = literal_input(f'Save config?', ['y', 'n'], 'y')
|
||||||
if save == 'y':
|
if save == 'y':
|
||||||
c.save()
|
c.save()
|
||||||
@@ -132,7 +243,7 @@ def create_config() -> Config:
|
|||||||
|
|
||||||
def run():
|
def run():
|
||||||
# Create CLI
|
# Create CLI
|
||||||
hyfetch = color('&b&lhy&f&lfetch&r')
|
hyfetch = color('&b&lhyfetch&r')
|
||||||
parser = argparse.ArgumentParser(description=color(f'{hyfetch} - neofetch with flags <3'))
|
parser = argparse.ArgumentParser(description=color(f'{hyfetch} - neofetch with flags <3'))
|
||||||
|
|
||||||
parser.add_argument('-c', '--config', action='store_true', help=color(f'Configure {hyfetch}'))
|
parser.add_argument('-c', '--config', action='store_true', help=color(f'Configure {hyfetch}'))
|
||||||
@@ -140,9 +251,30 @@ def run():
|
|||||||
parser.add_argument('-m', '--mode', help=f'Color mode', choices=['8bit', 'rgb'])
|
parser.add_argument('-m', '--mode', help=f'Color mode', choices=['8bit', 'rgb'])
|
||||||
parser.add_argument('--c-scale', dest='scale', help=f'Lighten colors by a multiplier', type=float)
|
parser.add_argument('--c-scale', dest='scale', help=f'Lighten colors by a multiplier', type=float)
|
||||||
parser.add_argument('--c-set-l', dest='light', help=f'Set lightness value of the colors', type=float)
|
parser.add_argument('--c-set-l', dest='light', help=f'Set lightness value of the colors', type=float)
|
||||||
|
parser.add_argument('-V', '--version', dest='version', action='store_true', help=f'Check version')
|
||||||
|
parser.add_argument('--debug', action='store_true', help=color(f'Debug mode'))
|
||||||
|
parser.add_argument('--debug-list', help=color(f'Debug recommendations'))
|
||||||
|
parser.add_argument('--test-distro', help=color(f'Test for a specific distro'))
|
||||||
|
parser.add_argument('--test-print', action='store_true', help=color(f'Test print distro ascii art only'))
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.version:
|
||||||
|
print(f'Version is {VERSION}')
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test distro ascii art
|
||||||
|
if args.test_distro:
|
||||||
|
print(f'Setting distro to {args.test_distro}')
|
||||||
|
GLOBAL_CFG.override_distro = args.test_distro
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
GLOBAL_CFG.debug = True
|
||||||
|
|
||||||
|
if args.test_print:
|
||||||
|
print(get_distro_ascii())
|
||||||
|
return
|
||||||
|
|
||||||
# Load config
|
# Load config
|
||||||
config = check_config()
|
config = check_config()
|
||||||
|
|
||||||
@@ -156,13 +288,31 @@ def run():
|
|||||||
if args.mode:
|
if args.mode:
|
||||||
config.mode = args.mode
|
config.mode = args.mode
|
||||||
|
|
||||||
|
# Override global color mode
|
||||||
|
GLOBAL_CFG.color_mode = config.mode
|
||||||
|
GLOBAL_CFG.is_light = config.light_dark == 'light'
|
||||||
|
|
||||||
|
# Get preset
|
||||||
preset = PRESETS.get(config.preset)
|
preset = PRESETS.get(config.preset)
|
||||||
|
|
||||||
# Lighten
|
# Lighten
|
||||||
if args.scale:
|
if args.scale:
|
||||||
preset = ColorProfile([c.lighten(args.scale) for c in preset.colors])
|
preset = preset.lighten(args.scale)
|
||||||
if args.light:
|
if args.light:
|
||||||
preset = ColorProfile([c.set_light(args.light) for c in preset.colors])
|
preset = preset.set_light_raw(args.light)
|
||||||
|
if config.lightness:
|
||||||
|
preset = preset.set_light_dl(config.lightness)
|
||||||
|
|
||||||
|
# Debug recommendations
|
||||||
|
if args.debug_list:
|
||||||
|
distro = args.debug_list
|
||||||
|
ca = color_alignments[distro]
|
||||||
|
|
||||||
|
print(distro)
|
||||||
|
GLOBAL_CFG.override_distro = distro
|
||||||
|
asciis = [ca.recolor_ascii(get_distro_ascii(distro), p).split('\n') for p in list(PRESETS.values())[:3]]
|
||||||
|
[printc(' '.join(line)) for line in zip(*asciis)]
|
||||||
|
return
|
||||||
|
|
||||||
# Run
|
# Run
|
||||||
run_neofetch(preset, config.mode)
|
run_neofetch(preset, config.color_align)
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from typing_extensions import Literal
|
||||||
|
|
||||||
|
from .color_util import AnsiMode, LightDark
|
||||||
|
from .constants import CONFIG_PATH
|
||||||
|
from .neofetch_util import ColorAlignment
|
||||||
|
from .serializer import json_stringify, from_dict
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Config:
|
||||||
|
preset: str
|
||||||
|
mode: AnsiMode
|
||||||
|
light_dark: LightDark = 'dark'
|
||||||
|
lightness: float | None = None
|
||||||
|
color_align: ColorAlignment = ColorAlignment('horizontal')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, d: dict):
|
||||||
|
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), 'utf-8')
|
||||||
+153
-17
@@ -1,15 +1,124 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import inspect
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import check_output
|
from subprocess import check_output
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
|
from typing_extensions import Literal
|
||||||
|
|
||||||
from .color_util import AnsiMode
|
from hyfetch.color_util import color
|
||||||
|
from .constants import GLOBAL_CFG
|
||||||
from .presets import ColorProfile
|
from .presets import ColorProfile
|
||||||
|
from .serializer import from_dict
|
||||||
|
|
||||||
|
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'))
|
||||||
|
|
||||||
|
|
||||||
|
def fill_starting(asc: str) -> str:
|
||||||
|
"""
|
||||||
|
Fill the missing starting placeholders.
|
||||||
|
|
||||||
|
E.g. "${c1}...\n..." -> "${c1}...\n${c1}..."
|
||||||
|
"""
|
||||||
|
new = []
|
||||||
|
last = ''
|
||||||
|
for line in asc.split('\n'):
|
||||||
|
new.append(last + line)
|
||||||
|
|
||||||
|
# Line has color placeholders
|
||||||
|
matches = RE_NEOFETCH_COLOR.findall(line)
|
||||||
|
if len(matches) > 0:
|
||||||
|
# Get the last placeholder for the next line
|
||||||
|
last = matches[-1]
|
||||||
|
|
||||||
|
return '\n'.join(new)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ColorAlignment:
|
||||||
|
mode: Literal['horizontal', 'vertical', 'custom']
|
||||||
|
|
||||||
|
# custom_colors[ascii color index] = unique color index in preset
|
||||||
|
custom_colors: dict[int, int] = ()
|
||||||
|
|
||||||
|
# Foreground/background ascii color index
|
||||||
|
fore_back: tuple[int, int] = ()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, d: dict):
|
||||||
|
return from_dict(cls, d)
|
||||||
|
|
||||||
|
def recolor_ascii(self, asc: str, preset: ColorProfile) -> str:
|
||||||
|
"""
|
||||||
|
Use the color alignment to recolor an ascii art
|
||||||
|
|
||||||
|
:return Colored ascii, Uncolored lines
|
||||||
|
"""
|
||||||
|
asc = fill_starting(asc)
|
||||||
|
|
||||||
|
if self.fore_back and self.mode in ['horizontal', 'vertical']:
|
||||||
|
fore, back = self.fore_back
|
||||||
|
|
||||||
|
# Replace foreground colors
|
||||||
|
asc = asc.replace(f'${{c{fore}}}', color('&0' if GLOBAL_CFG.is_light else '&f'))
|
||||||
|
lines = asc.split('\n')
|
||||||
|
|
||||||
|
# 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('&r') for i, l in enumerate(lines)])
|
||||||
|
else:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
# Remove existing colors
|
||||||
|
asc = re.sub(RE_NEOFETCH_COLOR, '', asc)
|
||||||
|
|
||||||
|
elif 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
|
||||||
|
color_map = {ai: preset.colors[pi].to_ansi() for ai, pi in self.custom_colors.items()}
|
||||||
|
for ascii_i, c in color_map.items():
|
||||||
|
asc = asc.replace(f'${{c{ascii_i}}}', c)
|
||||||
|
|
||||||
|
return asc
|
||||||
|
|
||||||
|
|
||||||
def get_command_path() -> str:
|
def get_command_path() -> str:
|
||||||
@@ -21,26 +130,30 @@ def get_command_path() -> str:
|
|||||||
return pkg_resources.resource_filename(__name__, 'scripts/neofetch_mod.sh')
|
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: Distro ascii
|
||||||
"""
|
"""
|
||||||
return check_output([get_command_path(), "print_ascii"]).decode().strip()
|
if not distro and GLOBAL_CFG.override_distro:
|
||||||
|
distro = GLOBAL_CFG.override_distro
|
||||||
|
if GLOBAL_CFG.debug:
|
||||||
|
print(distro)
|
||||||
|
print(GLOBAL_CFG)
|
||||||
|
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 run_neofetch(preset: ColorProfile, mode: AnsiMode):
|
def run_neofetch(preset: ColorProfile, alignment: ColorAlignment):
|
||||||
# Get existing ascii
|
|
||||||
asc = get_distro_ascii()
|
asc = get_distro_ascii()
|
||||||
|
w, h = ascii_size(asc)
|
||||||
# Remove existing colors
|
asc = alignment.recolor_ascii(asc, preset)
|
||||||
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)])
|
|
||||||
|
|
||||||
# Write temp file
|
# Write temp file
|
||||||
with TemporaryDirectory() as tmp_dir:
|
with TemporaryDirectory() as tmp_dir:
|
||||||
@@ -49,6 +162,29 @@ def run_neofetch(preset: ColorProfile, mode: AnsiMode):
|
|||||||
path.write_text(asc)
|
path.write_text(asc)
|
||||||
|
|
||||||
# Call neofetch with the temp file
|
# Call neofetch with the temp file
|
||||||
os.environ['ascii_len'] = str(max(len(l) for l in lines))
|
os.environ['ascii_len'] = str(w)
|
||||||
os.environ['ascii_lines'] = str(len(lines))
|
os.environ['ascii_lines'] = str(h)
|
||||||
os.system(get_command_path() + f' --ascii --source {path.absolute()} --ascii-colors')
|
|
||||||
|
if platform.system() != 'Windows':
|
||||||
|
os.system(f'{get_command_path()} --ascii --source {path.absolute()} --ascii-colors')
|
||||||
|
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
cmd = get_command_path().replace("\\", "/").replace("C:/", "/c/")
|
||||||
|
path_str = str(path.absolute()).replace('\\', '/').replace('C:/', '/c/')
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
subprocess.run(full_cmd)
|
||||||
|
|
||||||
|
|
||||||
|
# Color alignment recommendations
|
||||||
|
color_alignments = {
|
||||||
|
'fedora': ColorAlignment('horizontal', fore_back=(2, 1)),
|
||||||
|
'ubuntu': ColorAlignment('horizontal', fore_back=(2, 1)),
|
||||||
|
'NixOS.*': ColorAlignment('custom', {1: 1, 2: 0}),
|
||||||
|
# 'arch': ColorAlignment('horizontal'),
|
||||||
|
# 'centos': ColorAlignment('horizontal'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
+91
-7
@@ -1,8 +1,20 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
from typing_extensions import Literal
|
from typing_extensions import Literal
|
||||||
|
|
||||||
from .color_util import RGB
|
from .color_util import RGB, LightDark
|
||||||
|
from .constants import GLOBAL_CFG
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
class ColorProfile:
|
||||||
@@ -17,7 +29,6 @@ class ColorProfile:
|
|||||||
else:
|
else:
|
||||||
self.colors = colors
|
self.colors = colors
|
||||||
|
|
||||||
|
|
||||||
def with_weights(self, weights: list[int]) -> list[RGB]:
|
def with_weights(self, weights: list[int]) -> list[RGB]:
|
||||||
"""
|
"""
|
||||||
Map colors based on weights
|
Map colors based on weights
|
||||||
@@ -76,11 +87,59 @@ class ColorProfile:
|
|||||||
result += '\033[0m'
|
result += '\033[0m'
|
||||||
result += t
|
result += t
|
||||||
else:
|
else:
|
||||||
result += colors[i].to_ansi_rgb(foreground) + t
|
result += colors[i].to_ansi(foreground=foreground) + t
|
||||||
|
|
||||||
result += '\033[0m'
|
result += '\033[0m'
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def lighten(self, multiplier: float) -> ColorProfile:
|
||||||
|
"""
|
||||||
|
Lighten the color profile by a multiplier
|
||||||
|
|
||||||
|
:param multiplier: Multiplier
|
||||||
|
:return: Lightened color profile (original isn't modified)
|
||||||
|
"""
|
||||||
|
return ColorProfile([c.lighten(multiplier) for c in self.colors])
|
||||||
|
|
||||||
|
def set_light_raw(self, light: float, at_least: bool | None = None, at_most: bool | None = None) -> 'ColorProfile':
|
||||||
|
"""
|
||||||
|
Set HSL lightness value
|
||||||
|
|
||||||
|
:param light: Lightness value (0-1)
|
||||||
|
:param at_least: Set the lightness to at least this value (no change if greater)
|
||||||
|
:param at_most: Set the lightness to at most this value (no change if lesser)
|
||||||
|
:return: New color profile (original isn't modified)
|
||||||
|
"""
|
||||||
|
return ColorProfile([c.set_light(light, at_least, at_most) for c in self.colors])
|
||||||
|
|
||||||
|
def set_light_dl(self, light: float, term: LightDark = GLOBAL_CFG.light_dark()):
|
||||||
|
"""
|
||||||
|
Set HSL lightness value with respect to dark/light terminals
|
||||||
|
|
||||||
|
:param light: Lightness value (0-1)
|
||||||
|
:param term: Terminal color (can be "dark" or "light")
|
||||||
|
:return: New color profile (original isn't modified)
|
||||||
|
"""
|
||||||
|
assert term.lower() in ['light', 'dark']
|
||||||
|
at_least, at_most = (True, None) if term.lower() == 'dark' else (None, True)
|
||||||
|
return self.set_light_raw(light, at_least, at_most)
|
||||||
|
|
||||||
|
def set_light_dl_def(self, term: LightDark | None = None):
|
||||||
|
"""
|
||||||
|
Set default lightness with respect to dark/light terminals
|
||||||
|
|
||||||
|
:param term: Terminal color (can be "dark" or "light")
|
||||||
|
:return: New color profile (original isn't modified)
|
||||||
|
"""
|
||||||
|
return self.set_light_dl(GLOBAL_CFG.default_lightness(term), term)
|
||||||
|
|
||||||
|
def unique_colors(self) -> ColorProfile:
|
||||||
|
"""
|
||||||
|
Create another color profile with only the unique colors
|
||||||
|
"""
|
||||||
|
return ColorProfile(remove_duplicates(self.colors))
|
||||||
|
|
||||||
|
|
||||||
PRESETS: dict[str, ColorProfile] = {
|
PRESETS: dict[str, ColorProfile] = {
|
||||||
'rainbow': ColorProfile([
|
'rainbow': ColorProfile([
|
||||||
'#E50000',
|
'#E50000',
|
||||||
@@ -98,12 +157,14 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#F6AAB7',
|
'#F6AAB7',
|
||||||
'#55CDFD'
|
'#55CDFD'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'nonbinary': ColorProfile([
|
'nonbinary': ColorProfile([
|
||||||
'#FCF431',
|
'#FCF431',
|
||||||
'#FCFCFC',
|
'#FCFCFC',
|
||||||
'#9D59D2',
|
'#9D59D2',
|
||||||
'#282828'
|
'#282828'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'agender': ColorProfile([
|
'agender': ColorProfile([
|
||||||
'#000000',
|
'#000000',
|
||||||
'#BABABA',
|
'#BABABA',
|
||||||
@@ -113,11 +174,13 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#BABABA',
|
'#BABABA',
|
||||||
'#000000'
|
'#000000'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'queer': ColorProfile([
|
'queer': ColorProfile([
|
||||||
'#B57FDD',
|
'#B57FDD',
|
||||||
'#FFFFFF',
|
'#FFFFFF',
|
||||||
'#49821E'
|
'#49821E'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'genderfluid': ColorProfile([
|
'genderfluid': ColorProfile([
|
||||||
'#FE76A2',
|
'#FE76A2',
|
||||||
'#FFFFFF',
|
'#FFFFFF',
|
||||||
@@ -131,11 +194,13 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#9B4F96',
|
'#9B4F96',
|
||||||
'#0038A8'
|
'#0038A8'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'pansexual': ColorProfile([
|
'pansexual': ColorProfile([
|
||||||
'#FF1C8D',
|
'#FF1C8D',
|
||||||
'#FFD700',
|
'#FFD700',
|
||||||
'#1AB3FF'
|
'#1AB3FF'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'lesbian': ColorProfile([
|
'lesbian': ColorProfile([
|
||||||
'#D62800',
|
'#D62800',
|
||||||
'#FF9B56',
|
'#FF9B56',
|
||||||
@@ -143,12 +208,14 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#D462A6',
|
'#D462A6',
|
||||||
'#A40062'
|
'#A40062'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'asexual': ColorProfile([
|
'asexual': ColorProfile([
|
||||||
'#000000',
|
'#000000',
|
||||||
'#A4A4A4',
|
'#A4A4A4',
|
||||||
'#FFFFFF',
|
'#FFFFFF',
|
||||||
'#810081'
|
'#810081'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'aromantic': ColorProfile([
|
'aromantic': ColorProfile([
|
||||||
'#3BA740',
|
'#3BA740',
|
||||||
'#A8D47A',
|
'#A8D47A',
|
||||||
@@ -156,6 +223,7 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#ABABAB',
|
'#ABABAB',
|
||||||
'#000000'
|
'#000000'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
# below sourced from https://www.flagcolorcodes.com/flags/pride
|
# below sourced from https://www.flagcolorcodes.com/flags/pride
|
||||||
# goto f"https://www.flagcolorcodes.com/{preset}" for info
|
# goto f"https://www.flagcolorcodes.com/{preset}" for info
|
||||||
# todo: sane sorting
|
# todo: sane sorting
|
||||||
@@ -163,6 +231,7 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#99D9EA',
|
'#99D9EA',
|
||||||
'#7F7F7F'
|
'#7F7F7F'
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'intergender': ColorProfile([
|
'intergender': ColorProfile([
|
||||||
# todo: use weighted spacing
|
# todo: use weighted spacing
|
||||||
'#900DC2',
|
'#900DC2',
|
||||||
@@ -171,6 +240,7 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#900DC2',
|
'#900DC2',
|
||||||
'#900DC2',
|
'#900DC2',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'greygender': ColorProfile([
|
'greygender': ColorProfile([
|
||||||
'#B3B3B3',
|
'#B3B3B3',
|
||||||
'#B3B3B3',
|
'#B3B3B3',
|
||||||
@@ -181,6 +251,7 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#535353',
|
'#535353',
|
||||||
'#535353',
|
'#535353',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'akiosexual': ColorProfile([
|
'akiosexual': ColorProfile([
|
||||||
'#F9485E',
|
'#F9485E',
|
||||||
'#FEA06A',
|
'#FEA06A',
|
||||||
@@ -188,6 +259,7 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#FFFFFF',
|
'#FFFFFF',
|
||||||
'#000000',
|
'#000000',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'transmasculine': ColorProfile([
|
'transmasculine': ColorProfile([
|
||||||
'#FF8ABD',
|
'#FF8ABD',
|
||||||
'#CDF5FE',
|
'#CDF5FE',
|
||||||
@@ -197,6 +269,7 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#CDF5FE',
|
'#CDF5FE',
|
||||||
'#FF8ABD',
|
'#FF8ABD',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'demifaun': ColorProfile([
|
'demifaun': ColorProfile([
|
||||||
'#7F7F7F',
|
'#7F7F7F',
|
||||||
'#7F7F7F',
|
'#7F7F7F',
|
||||||
@@ -212,25 +285,29 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#7F7F7F',
|
'#7F7F7F',
|
||||||
'#7F7F7F',
|
'#7F7F7F',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'neutrois': ColorProfile([
|
'neutrois': ColorProfile([
|
||||||
'#FFFFFF',
|
'#FFFFFF',
|
||||||
'#1F9F00',
|
'#1F9F00',
|
||||||
'#000000'
|
'#000000'
|
||||||
]),
|
]),
|
||||||
'biromantic alt 2': ColorProfile([
|
|
||||||
|
'biromantic1': ColorProfile([
|
||||||
'#8869A5',
|
'#8869A5',
|
||||||
'#D8A7D8',
|
'#D8A7D8',
|
||||||
'#FFFFFF',
|
'#FFFFFF',
|
||||||
'#FDB18D',
|
'#FDB18D',
|
||||||
'#151638',
|
'#151638',
|
||||||
]),
|
]),
|
||||||
'biromantic alt 2': ColorProfile([
|
|
||||||
|
'biromantic2': ColorProfile([
|
||||||
'#740194',
|
'#740194',
|
||||||
'#AEB1AA',
|
'#AEB1AA',
|
||||||
'#FFFFFF',
|
'#FFFFFF',
|
||||||
'#AEB1AA',
|
'#AEB1AA',
|
||||||
'#740194',
|
'#740194',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
'autoromantic': ColorProfile([ # symbol interpreted
|
'autoromantic': ColorProfile([ # symbol interpreted
|
||||||
'#99D9EA',
|
'#99D9EA',
|
||||||
'#99D9EA',
|
'#99D9EA',
|
||||||
@@ -247,8 +324,9 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#7F7F7F',
|
'#7F7F7F',
|
||||||
'#7F7F7F',
|
'#7F7F7F',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
# i didn't expect this one to work. cool!
|
# i didn't expect this one to work. cool!
|
||||||
'boyflux alt 2': ColorProfile([
|
'boyflux2': ColorProfile([
|
||||||
'#E48AE4',
|
'#E48AE4',
|
||||||
'#9A81B4',
|
'#9A81B4',
|
||||||
'#55BFAB',
|
'#55BFAB',
|
||||||
@@ -281,5 +359,11 @@ PRESETS: dict[str, ColorProfile] = {
|
|||||||
'#5276D4',
|
'#5276D4',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
'beiyang': ColorProfile([
|
||||||
|
'#DF1B12',
|
||||||
|
'#FFC600',
|
||||||
|
'#01639D',
|
||||||
|
'#FFFFFF',
|
||||||
|
'#000000',
|
||||||
|
]),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
|
import inspect
|
||||||
|
import json
|
||||||
|
from datetime import datetime, date
|
||||||
|
|
||||||
|
|
||||||
|
class EnhancedJSONEncoder(json.JSONEncoder):
|
||||||
|
"""
|
||||||
|
An improvement to the json.JSONEncoder class, which supports:
|
||||||
|
encoding for dataclasses, encoding for datetime, and sets
|
||||||
|
"""
|
||||||
|
|
||||||
|
def default(self, o: object) -> object:
|
||||||
|
|
||||||
|
# Support encoding dataclasses
|
||||||
|
# https://stackoverflow.com/a/51286749/7346633
|
||||||
|
if dataclasses.is_dataclass(o):
|
||||||
|
return dataclasses.asdict(o)
|
||||||
|
|
||||||
|
# Support encoding datetime
|
||||||
|
if isinstance(o, (datetime, date)):
|
||||||
|
return o.isoformat()
|
||||||
|
|
||||||
|
# Support for sets
|
||||||
|
# https://stackoverflow.com/a/8230505/7346633
|
||||||
|
if isinstance(o, set):
|
||||||
|
return list(o)
|
||||||
|
|
||||||
|
return super().default(o)
|
||||||
|
|
||||||
|
|
||||||
|
def json_stringify(obj: object, indent: int | None = None) -> str:
|
||||||
|
"""
|
||||||
|
Serialize json string with support for dataclasses and datetime and sets and with custom
|
||||||
|
configuration.
|
||||||
|
Preconditions:
|
||||||
|
- obj != None
|
||||||
|
:param obj: Objects
|
||||||
|
:param indent: Indent size or none
|
||||||
|
:return: Json strings
|
||||||
|
"""
|
||||||
|
return json.dumps(obj, indent=indent, cls=EnhancedJSONEncoder, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
def from_dict(cls, d: dict):
|
||||||
|
return cls(**{k: v for k, v in d.items() if k in inspect.signature(cls).parameters})
|
||||||
@@ -1677,8 +1677,12 @@ get_packages() {
|
|||||||
has scoop && pkgs_h=1 dir ~/scoop/apps/* && ((packages-=1))
|
has scoop && pkgs_h=1 dir ~/scoop/apps/* && ((packages-=1))
|
||||||
|
|
||||||
# Count chocolatey packages.
|
# Count chocolatey packages.
|
||||||
[[ -d /cygdrive/c/ProgramData/chocolatey/lib ]] && \
|
# [[ -d /c/ProgramData/chocolatey/lib ]] && \
|
||||||
dir /cygdrive/c/ProgramData/chocolatey/lib/*
|
# dir /c/ProgramData/chocolatey/lib/*
|
||||||
|
has choco && tot choco list --localonly
|
||||||
|
|
||||||
|
# Count winget
|
||||||
|
has winget && tot winget list
|
||||||
;;
|
;;
|
||||||
|
|
||||||
Haiku)
|
Haiku)
|
||||||
@@ -11594,6 +11598,11 @@ main() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_ascii_distro_name() {
|
||||||
|
get_distro
|
||||||
|
echo "$ascii_distro"
|
||||||
|
}
|
||||||
|
|
||||||
get_print_ascii() {
|
get_print_ascii() {
|
||||||
cache_uname
|
cache_uname
|
||||||
get_os
|
get_os
|
||||||
@@ -11603,4 +11612,24 @@ get_print_ascii() {
|
|||||||
echo "$ascii_data"
|
echo "$ascii_data"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_print_custom_ascii() {
|
||||||
|
distro="$CUSTOM_DISTRO"
|
||||||
|
ascii_distro=$distro
|
||||||
|
get_distro_ascii
|
||||||
|
echo "$ascii_data"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_test_distro_ascii() {
|
||||||
|
# Load default config.
|
||||||
|
eval "$config"
|
||||||
|
|
||||||
|
distro="$CUSTOM_DISTRO"
|
||||||
|
ascii_distro=$distro
|
||||||
|
get_bold
|
||||||
|
get_distro_ascii
|
||||||
|
|
||||||
|
image_backend
|
||||||
|
dynamic_prompt
|
||||||
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import hyfetch
|
import hyfetch
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ setup(
|
|||||||
packages=['hyfetch'],
|
packages=['hyfetch'],
|
||||||
package_data={'hyfetch': ['hyfetch/*']},
|
package_data={'hyfetch': ['hyfetch/*']},
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=['setuptools', 'hypy_utils>=1.0.6', 'typing_extensions'],
|
install_requires=['setuptools', 'typing_extensions', 'numpy'],
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": [
|
"console_scripts": [
|
||||||
"hyfetch=hyfetch.main:run",
|
"hyfetch=hyfetch.main:run",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from hypy_utils import printc
|
from hyfetch.color_scale import test_color_scale
|
||||||
|
from hyfetch.color_util import RGB, printc
|
||||||
from hyfetch.color_util import RGB
|
|
||||||
from hyfetch.neofetch_util import get_command_path, run_neofetch
|
from hyfetch.neofetch_util import get_command_path, run_neofetch
|
||||||
from hyfetch.presets import PRESETS
|
from hyfetch.presets import PRESETS
|
||||||
|
|
||||||
@@ -35,6 +34,6 @@ def test_rgb_8bit_conversion():
|
|||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
test_rgb_8bit_conversion()
|
test_rgb_8bit_conversion()
|
||||||
|
test_color_scale()
|
||||||
|
|||||||
Reference in New Issue
Block a user