Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c0c9024
Fix Homebrew's Cellar path for brew package counts (#1656)
j4d35t0n3 Jun 11, 2021
b38d141
Replaced broken link
j4d35t0n3 Sep 14, 2023
51a0a12
Added image utilities
j4d35t0n3 Sep 14, 2023
37742d7
Minor changes
j4d35t0n3 Sep 14, 2023
8af2563
Added color util function
j4d35t0n3 Sep 14, 2023
c433f4f
Added image dependencies
j4d35t0n3 Sep 14, 2023
4bf1850
Renamed to flag_utils
j4d35t0n3 Sep 14, 2023
f7350e7
Removed ColorProfile
j4d35t0n3 Sep 14, 2023
554ae5f
Formatting changes
j4d35t0n3 Sep 14, 2023
186ebe5
Removed numpy dependency
j4d35t0n3 Sep 14, 2023
c8a1c3d
Removed unnecessary imports
j4d35t0n3 Sep 14, 2023
326206e
Minor changes
j4d35t0n3 Sep 14, 2023
9908f51
Added flags
j4d35t0n3 Sep 14, 2023
2d123b7
Removed unnecessary parts
j4d35t0n3 Sep 14, 2023
411424b
Added neurodivergent flag
j4d35t0n3 Sep 14, 2023
1df3032
Major changes
j4d35t0n3 Sep 14, 2023
43e1db3
Configuration more like original
j4d35t0n3 Sep 14, 2023
be6ad2b
Animation includes other flags, more like original
j4d35t0n3 Sep 14, 2023
fad2139
Resized images
j4d35t0n3 Sep 14, 2023
6f89554
Added check to speed up recoloring
j4d35t0n3 Sep 14, 2023
6511eb3
Fixed check
j4d35t0n3 Sep 14, 2023
13526db
Removed debug print
j4d35t0n3 Sep 14, 2023
d31ad38
Fixed jpeg artifacts
j4d35t0n3 Sep 14, 2023
b1097a1
Randomized flag order in pride month animation
j4d35t0n3 Sep 14, 2023
c6b38c6
Improved documentation
j4d35t0n3 Sep 14, 2023
f7f3bb1
Added progressive flag
j4d35t0n3 Sep 14, 2023
169ed6f
Minor changes
j4d35t0n3 Sep 14, 2023
6fbfe08
Minor changes
j4d35t0n3 Sep 14, 2023
0e4b2af
Fixed Windows errors
j4d35t0n3 Sep 14, 2023
73fc2dc
Minor changes
j4d35t0n3 Sep 14, 2023
baa0c96
Minor changes
j4d35t0n3 Sep 14, 2023
35b0344
Apply merges
j4d35t0n3 Sep 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ request on the repo and our Travis.ci hook will run ShellCheck for you.
- Don’t use GNU conventions in commands.
- Use POSIX arguments and flags.
- Don’t use `cut`.
- Use `bash`'s built-in [parameter expansion](http://wiki.bash-hackers.org/syntax/pe).
- Use `bash`'s built-in [parameter expansion](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html).
- Don’t use `echo`.
- Use `printf "%s\n"`
- Don’t use `bc`.
- Don’t use `sed`.
- Use `bash`'s built-in [parameter expansion](http://wiki.bash-hackers.org/syntax/pe).
- Use `bash`'s built-in [parameter expansion](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html).
- Don’t use `cat`.
- Use `bash`'s built-in syntax (`file="$(< /path/to/file.txt)")`).
- Don’t use `grep "pattern" | awk '{ printf }'`.
Expand Down
8 changes: 4 additions & 4 deletions hyfetch/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from __future__ import annotations

from . import main, constants

__version__ = constants.VERSION
from .main import run
from .__version__ import VERSION

__version__ = VERSION

if __name__ == '__main__':
main.run()
run()
4 changes: 3 additions & 1 deletion hyfetch/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from . import main
from .color_util import printc

Expand All @@ -6,4 +8,4 @@
main.run()
except KeyboardInterrupt:
printc('&cThe program is interrupted by ^C, exiting...')
exit(0)
exit(0)
37 changes: 26 additions & 11 deletions hyfetch/color_scale.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""
This version of color_scale is a special version made without numpy dependency. The numpy version
would be faster, but numpy is 11 MB large. In comparison, hyfetch 1.0.7 is only 105 kB, so it's not
would be faster, but numpy is 11 MB. In comparison, hyfetch 1.0.7 is only 105 kB, so it's not
a good idea to depend on numpy.
"""

from __future__ import annotations

from .color_util import RGB
Expand All @@ -26,15 +27,14 @@ def create_gradient(colors: list[RGB], resolution: int) -> list[RGB]:

# Create gradient mapping
for i in range(len(colors) - 1):
c1 = colors[i]
c2 = colors[i + 1]
bi = i * resolution

for ri in range(resolution):
ratio = ri / resolution
r = int(c2.r * ratio + c1.r * (1 - ratio))
g = int(c2.g * ratio + c1.g * (1 - ratio))
b = int(c2.b * ratio + c1.b * (1 - ratio))
color_1 = colors[i]
color_2 = colors[i + 1]

for point in range(resolution):
ratio = point / resolution
r = int(color_2.r * ratio + color_1.r * (1 - ratio))
g = int(color_2.g * ratio + color_1.g * (1 - ratio))
b = int(color_2.b * ratio + color_1.b * (1 - ratio))
result.append(RGB(r, g, b))

return result
Expand All @@ -54,21 +54,36 @@ def get_raw(gradient: list[RGB], ratio: float) -> RGB:


class Scale:
"""
A simple color scale using linear interpolation
"""
colors: list[RGB]
rgb: list[RGB]

def __init__(self, scale: list[str], resolution: int = 300):
self.resolution = resolution
self.colors = [RGB.from_hex(s) for s in scale]
self.rgb = create_gradient(self.colors, resolution)
self.rgb = create_gradient(self.colors, self.resolution)

def __call__(self, ratio: float) -> RGB:
"""
:param ratio: Between 0-1
"""
return get_raw(self.rgb, ratio)

def __len__(self):
return self.resolution


def test_color_scale():
"""
Display a gradient

Returns
-------
None.

"""
scale = Scale(['#232323', '#4F1879', '#B43A78', '#F98766', '#FCFAC0'])

colors = 100
Expand Down
93 changes: 43 additions & 50 deletions hyfetch/color_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import colorsys
from dataclasses import dataclass, astuple

from .constants import GLOBAL_CFG
from .types import *
from .types import AnsiMode

MINECRAFT_COLORS = [

Expand Down Expand Up @@ -83,35 +82,30 @@ def clear_screen(title: str = ''):
print()


def redistribute_rgb(r: int, g: int, b: int) -> tuple[int, int, int]:
def redistribute_rgb(rgb: list[int]) -> tuple[int, int, int]:
"""
Redistribute RGB after lightening

Credit: https://stackoverflow.com/a/141943/7346633
"""
threshold = 255.999
m = max(r, g, b)
if m <= threshold:
return int(r), int(g), int(b)
total = r + g + b
if total >= 3 * threshold:
return int(threshold), int(threshold), int(threshold)
x = (3 * threshold - total) / (3 * m - total)
gray = threshold - x * m
return int(gray + x * r), int(gray + x * g), int(gray + x * b)
threshold = 256
rgb_max = max(rgb)
if rgb_max < threshold:
return tuple(int(c) for c in rgb)
total = sum(rgb)
if total > 3 * threshold:
return 255, 255, 255
x = (3 * threshold - total) / (3 * rgb_max - total)
grey = threshold - x * rgb_max
return tuple(grey + x * c for c in rgb)


@dataclass(unsafe_hash=True)
class HSL:
h: float
s: float
l: float
def rgb_to_hls(rgb) -> list:
return [*colorsys.rgb_to_hls(*[v / 255.0 for v in rgb])]

def __iter__(self):
return iter(astuple(self))

def rgb(self) -> RGB:
return RGB(*[round(v * 255.0) for v in colorsys.hls_to_rgb(self.h, self.l, self.s)])
def hls_to_rgb(hls: list):
return RGB(*[round(v * 255.0) for v in colorsys.hls_to_rgb(*hls)])


@dataclass(unsafe_hash=True)
Expand All @@ -124,20 +118,20 @@ def __iter__(self):
return iter(astuple(self))

@classmethod
def from_hex(cls, hex: str) -> "RGB":
def from_hex(cls, hex_val: str) -> "RGB":
"""
Create color from hex code

>>> RGB.from_hex('#FFAAB7')
RGB(r=255, g=170, b=183)

:param hex: Hex color code
:param hex_val: Hex color code
:return: RGB object
"""
hex = hex.lstrip("#")
r = int(hex[0:2], 16)
g = int(hex[2:4], 16)
b = int(hex[4:6], 16)
hex_val = hex_val.lstrip("#")
r = int(hex_val[0:2], 16)
g = int(hex_val[2:4], 16)
b = int(hex_val[4:6], 16)
return cls(r, g, b)

def to_ansi_rgb(self, foreground: bool = True) -> str:
Expand Down Expand Up @@ -175,12 +169,13 @@ def to_ansi_8bit(self, foreground: bool = True) -> str:
sep += 42.5

if gray:
color = 232 + (r + g + b) / 33
rgb_color = 232 + (r + g + b) / 33
else:
color = 16 + int(r / 256. * 6) * 36 + int(g / 256. * 6) * 6 + int(b / 256. * 6)
rgb_color = 16 + int(r / 256. * 6) * 36 + \
int(g / 256. * 6) * 6 + int(b / 256. * 6)

c = '38' if foreground else '48'
return f'\033[{c};5;{int(color)}m'
code = '38' if foreground else '48'
return f'\033[{code};5;{int(rgb_color)}m'

def to_ansi_16(self, foreground: bool = True) -> str:
"""
Expand All @@ -191,7 +186,7 @@ def to_ansi_16(self, foreground: bool = True) -> str:
raise NotImplementedError()

def to_ansi(self, mode: AnsiMode | None = None, foreground: bool = True):
if not mode:
if mode is None:
mode = GLOBAL_CFG.color_mode
if mode == 'rgb':
return self.to_ansi_rgb(foreground)
Expand All @@ -207,45 +202,43 @@ def lighten(self, multiplier: float) -> 'RGB':
:param multiplier: Multiplier
:return: Lightened color (original isn't modified)
"""
return RGB(*redistribute_rgb(*[v * multiplier for v in self]))

def hsl(self) -> HSL:
h, l, s = colorsys.rgb_to_hls(*[v / 255.0 for v in self])
return HSL(h, s, l)
return RGB(*redistribute_rgb([v * multiplier for v in self]))

def set_light(self, light: float, at_least: bool | None = None, at_most: bool | None = None) -> 'RGB':
def set_light(self, light: float, at_least: bool | None = None,
at_most: bool | None = None) -> 'RGB':
"""
Set HSL lightness value
Set HLS 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)
"""
# Convert to HSL
hsl = self.hsl()
# Convert to HLS
hls = rgb_to_hls(self)

# Modify light value
if at_least is None and at_most is None:
hsl.l = light
hls[1] = light
else:
if at_most:
hsl.l = min(hsl.l, light)
hls[1] = min(hls[1], light)
if at_least:
hsl.l = max(hsl.l, light)
hls[1] = max(hls[1], light)

# Convert back to RGB
return hsl.rgb()
rgb = hls_to_rgb(hls)
return rgb

def is_light(self):
return self.hsl().l > 0.5
return rgb_to_hls(self)[1] > 0.5

def overlay(self, color: 'RGB', alpha: float) -> 'RGB':
def overlay(self, rgb_color: 'RGB', alpha: float) -> 'RGB':
"""
Overlay a color on top of this color

:param color: Overlay color
:param rgb_color: Overlay color
:param alpha: Overlay alpha
:return: New color (original isn't modified)
"""
return RGB(*[round((1 - alpha) * v1 + alpha * v2) for v1, v2 in zip(self, color)])
return RGB(*[round((1 - alpha) * v1 + alpha * v2) for v1, v2 in zip(self, rgb_color)])
43 changes: 39 additions & 4 deletions hyfetch/constants.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
from __future__ import annotations

import os
import shutil
import platform
from dataclasses import dataclass
from pathlib import Path

from .types import LightDark
from .__version__ import VERSION


TERM_WIDTH, TERM_HEIGHT = shutil.get_terminal_size(fallback=(100, 20))

CONFIGURE_FLAG_WIDTH = 18
CONFIGURE_FLAG_HEIGHT = 3


CONFIG_PATH = Path.home() / '.config/hyfetch.json'

Expand All @@ -29,10 +35,15 @@
DEFAULT_DARK_L = 0.
IS_WINDOWS = platform.system() == 'Windows'

CACHE_PATH = Path(os.getenv("LOCALAPPDATA") or os.getenv("XDG_CACHE_HOME") or Path.home() / '.cache') / 'hyfetch'
CACHE_PATH = Path(os.getenv("LOCALAPPDATA") or os.getenv(
"XDG_CACHE_HOME") or Path.home() / '.cache') / 'hyfetch'


@dataclass
class GlobalConfig:
"""
Global configuration
"""
# Global color mode default to 8-bit for compatibility
color_mode: str
override_distro: str | None
Expand All @@ -41,14 +52,38 @@ class GlobalConfig:
use_overlay: bool

def light_dark(self) -> LightDark:
"""
Get color mode

Returns
-------
LightDark
string 'light' or string 'dark'.

"""
return 'light' if self.is_light else 'dark'

def default_lightness(self, term: LightDark | None = None) -> float:
"""
Get default lightness for a color mode

Parameters
----------
term : LightDark | None, optional
string 'light' or string 'dark'. The default is None.

Returns
-------
float
default lightness.

"""
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, use_overlay=False)
GLOBAL_CFG = GlobalConfig(color_mode='8bit', override_distro=None,
debug=False, is_light=False, use_overlay=False)

MINGIT_URL = 'https://github.com/git-for-windows/git/releases/download/v2.37.2.windows.2/MinGit-2.37.2.2-busybox-32-bit.zip'
Loading