neofetch_util.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. from __future__ import annotations
  2. import os
  3. import platform
  4. import re
  5. import subprocess
  6. from dataclasses import dataclass
  7. from pathlib import Path
  8. from subprocess import check_output
  9. from tempfile import TemporaryDirectory
  10. import pkg_resources
  11. from typing_extensions import Literal
  12. from hyfetch.color_util import color
  13. from .constants import GLOBAL_CFG
  14. from .presets import ColorProfile
  15. RE_NEOFETCH_COLOR = re.compile('\\${c[0-9]}')
  16. def ascii_size(asc: str) -> tuple[int, int]:
  17. """
  18. Get distro ascii width, height ignoring color code
  19. :param asc: Distro ascii
  20. :return: Width, Height
  21. """
  22. return max(len(line) for line in re.sub(RE_NEOFETCH_COLOR, '', asc).split('\n')), len(asc.split('\n'))
  23. def normalize_ascii(asc: str) -> str:
  24. """
  25. Make sure every line are the same width
  26. """
  27. w = ascii_size(asc)[0]
  28. return '\n'.join(line + ' ' * (w - ascii_size(line)[0]) for line in asc.split('\n'))
  29. def fill_starting(asc: str) -> str:
  30. """
  31. Fill the missing starting placeholders.
  32. E.g. "${c1}...\n..." -> "${c1}...\n${c1}..."
  33. """
  34. new = []
  35. last = ''
  36. for line in asc.split('\n'):
  37. new.append(last + line)
  38. # Line has color placeholders
  39. matches = RE_NEOFETCH_COLOR.findall(line)
  40. if len(matches) > 0:
  41. # Get the last placeholder for the next line
  42. last = matches[-1]
  43. return '\n'.join(new)
  44. @dataclass
  45. class ColorAlignment:
  46. mode: Literal['horizontal', 'vertical', 'custom']
  47. # custom_colors[ascii color index] = unique color index in preset
  48. custom_colors: dict[int, int] = ()
  49. # Foreground/background ascii color index
  50. fore_back: tuple[int, int] = ()
  51. def recolor_ascii(self, asc: str, preset: ColorProfile) -> str:
  52. """
  53. Use the color alignment to recolor an ascii art
  54. :return Colored ascii, Uncolored lines
  55. """
  56. asc = fill_starting(asc)
  57. if self.fore_back and self.mode in ['horizontal', 'vertical']:
  58. fore, back = self.fore_back
  59. # Replace foreground colors
  60. asc = asc.replace(f'${{c{fore}}}', color('&0' if GLOBAL_CFG.is_light else '&f'))
  61. lines = asc.split('\n')
  62. # Add new colors
  63. if self.mode == 'horizontal':
  64. colors = preset.with_length(len(lines))
  65. asc = '\n'.join([l.replace(f'${{c{back}}}', colors[i].to_ansi()) + color('&r') for i, l in enumerate(lines)])
  66. else:
  67. raise NotImplementedError()
  68. # Remove existing colors
  69. asc = re.sub(RE_NEOFETCH_COLOR, '', asc)
  70. elif self.mode in ['horizontal', 'vertical']:
  71. # Remove existing colors
  72. asc = re.sub(RE_NEOFETCH_COLOR, '', asc)
  73. lines = asc.split('\n')
  74. # Add new colors
  75. if self.mode == 'horizontal':
  76. colors = preset.with_length(len(lines))
  77. asc = '\n'.join([colors[i].to_ansi() + l + color('&r') for i, l in enumerate(lines)])
  78. else:
  79. asc = '\n'.join(preset.color_text(line) + color('&r') for line in lines)
  80. else:
  81. preset = preset.unique_colors()
  82. # Apply colors
  83. color_map = {ai: preset.colors[pi].to_ansi() for ai, pi in self.custom_colors.items()}
  84. for ascii_i, c in color_map.items():
  85. asc = asc.replace(f'${{c{ascii_i}}}', c)
  86. return asc
  87. def get_command_path() -> str:
  88. """
  89. Get the absolute path of the neofetch command
  90. :return: Command path
  91. """
  92. return pkg_resources.resource_filename(__name__, 'scripts/neofetch_mod.sh')
  93. def get_distro_ascii(distro: str | None = None) -> str:
  94. """
  95. Get the distro ascii of the current distro. Or if distro is specified, get the specific distro's
  96. ascii art instead.
  97. :return: Distro ascii
  98. """
  99. if not distro and GLOBAL_CFG.override_distro:
  100. distro = GLOBAL_CFG.override_distro
  101. if GLOBAL_CFG.debug:
  102. print(distro)
  103. print(GLOBAL_CFG)
  104. cmd = 'print_ascii'
  105. if distro:
  106. os.environ['CUSTOM_DISTRO'] = distro
  107. cmd = 'print_custom_ascii'
  108. return normalize_ascii(check_output([get_command_path(), cmd]).decode().strip())
  109. def run_neofetch(preset: ColorProfile, alignment: ColorAlignment):
  110. asc = get_distro_ascii()
  111. w, h = ascii_size(asc)
  112. asc = alignment.recolor_ascii(asc, preset)
  113. # Write temp file
  114. with TemporaryDirectory() as tmp_dir:
  115. tmp_dir = Path(tmp_dir)
  116. path = tmp_dir / 'ascii.txt'
  117. path.write_text(asc)
  118. # Call neofetch with the temp file
  119. os.environ['ascii_len'] = str(w)
  120. os.environ['ascii_lines'] = str(h)
  121. if platform.system() != 'Windows':
  122. os.system(f'{get_command_path()} --ascii --source {path.absolute()} --ascii-colors')
  123. if platform.system() == 'Windows':
  124. cmd = get_command_path().replace("\\", "/").replace("C:/", "/c/")
  125. path_str = str(path.absolute()).replace('\\', '/').replace('C:/', '/c/')
  126. cmd = f'ascii_len={w} ascii_lines={h} {cmd} --ascii --source {path_str} --ascii-colors'
  127. full_cmd = ['C:\\Program Files\\Git\\bin\\bash.exe', '-c', cmd]
  128. # print(full_cmd)
  129. subprocess.run(full_cmd)
  130. # Color alignment recommendations
  131. color_alignments = {
  132. 'fedora': ColorAlignment('horizontal', fore_back=(2, 1)),
  133. 'ubuntu': ColorAlignment('horizontal', fore_back=(2, 1)),
  134. 'nixos': ColorAlignment('custom', {1: 1, 2: 0}),
  135. # 'arch': ColorAlignment('horizontal'),
  136. # 'centos': ColorAlignment('horizontal'),
  137. }