main.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. #!/usr/bin/env python3
  2. from __future__ import annotations
  3. import argparse
  4. import importlib
  5. import json
  6. import os
  7. from dataclasses import dataclass
  8. from pathlib import Path
  9. from typing import Iterable
  10. from .color_util import AnsiMode, printc, color
  11. from .neofetch_util import run_neofetch, replace_colors, get_custom_distro_ascii
  12. from .presets import PRESETS, ColorProfile
  13. from .serializer import json_stringify
  14. CONFIG_PATH = Path.home() / '.config/hyfetch.json'
  15. VERSION = '1.0.7'
  16. # Obtain terminal size
  17. try:
  18. term_len = os.get_terminal_size().columns
  19. except Exception:
  20. term_len = 40
  21. @dataclass
  22. class Config:
  23. preset: str
  24. mode: AnsiMode
  25. def save(self):
  26. CONFIG_PATH.parent.mkdir(exist_ok=True, parents=True)
  27. CONFIG_PATH.write_text(json_stringify(self), 'utf-8')
  28. def check_config() -> Config:
  29. """
  30. Check if the configuration exists. Return the config object if it exists. If not, call the
  31. config creator
  32. TODO: Config path param
  33. :return: Config object
  34. """
  35. if CONFIG_PATH.is_file():
  36. return Config(**json.loads(CONFIG_PATH.read_text('utf-8')))
  37. return create_config()
  38. def literal_input(prompt: str, options: Iterable[str], default: str) -> str:
  39. """
  40. Ask the user to provide an input among a list of options
  41. :param prompt: Input prompt
  42. :param options: Options
  43. :param default: Default option
  44. :return: Selection
  45. """
  46. options = list(options)
  47. lows = [o.lower() for o in options]
  48. op_text = '|'.join([f'&l&n{o}&r' if o == default else o for o in options])
  49. printc(f'{prompt} ({op_text})')
  50. selection = input('> ') or default
  51. while not selection.lower() in lows:
  52. print(f'Invalid selection! {selection} is not one of {"|".join(options)}')
  53. selection = input('> ') or default
  54. print()
  55. return options[lows.index(selection)]
  56. def center_text(txt: str, spaces: int) -> str:
  57. """
  58. Put the text in the center in a defined space
  59. >>> center_text('meow', 9)
  60. ' meow '
  61. :param txt: Text
  62. :param spaces: Total space of the text
  63. :return: Text with length spaces
  64. """
  65. spaces -= len(txt)
  66. if spaces % 2 == 1:
  67. spaces -= 1
  68. txt += ' '
  69. while spaces > 0:
  70. spaces -= 2
  71. txt = f' {txt} '
  72. return txt
  73. def create_config() -> Config:
  74. """
  75. Create config interactively
  76. :return: Config object (automatically stored)
  77. """
  78. printc('\nWelcome to &b&lhy&f&lfetch&r! Let\'s set up some colors first.\n')
  79. # Select color system
  80. try:
  81. # Demonstrate RGB with a gradient. This requires numpy
  82. from .color_scale import Scale
  83. scale2 = Scale(['#12c2e9', '#c471ed', '#f7797d'])
  84. _8bit = [scale2(i / term_len).to_ansi_8bit(False) for i in range(term_len)]
  85. _rgb = [scale2(i / term_len).to_ansi_rgb(False) for i in range(term_len)]
  86. printc('&f' + ''.join(c + t for c, t in zip(_8bit, '8bit Color Testing'.center(term_len))))
  87. printc('&f' + ''.join(c + t for c, t in zip(_rgb, 'RGB Color Testing'.center(term_len))))
  88. print()
  89. printc(f'1. Which &acolor &bsystem &rdo you want to use?')
  90. printc(f'(If you can\'t see colors under "RGB Color Testing", please choose 8bit)')
  91. print()
  92. color_system = literal_input('Your choice?', ['8bit', 'rgb'], 'rgb')
  93. except ModuleNotFoundError:
  94. # Numpy not found, skip gradient test, use fallback
  95. color_system = literal_input('Which &acolor &bsystem &rdo you want to use?',
  96. ['8bit', 'rgb'], 'rgb')
  97. # Print preset
  98. print('Available presets:\n')
  99. spacing = max(max(len(k) for k in PRESETS.keys()), 30)
  100. flags = []
  101. for name, preset in PRESETS.items():
  102. flags.append([preset.color_text(' ' * spacing, foreground=False),
  103. '&0' + preset.color_text(center_text(name, spacing), foreground=False),
  104. preset.color_text(' ' * spacing, foreground=False)])
  105. flags_per_row = 3
  106. while flags:
  107. current = flags[:flags_per_row]
  108. flags = flags[flags_per_row:]
  109. for line in range(len(current[0])):
  110. printc(' '.join(flag[line] for flag in current))
  111. print()
  112. print()
  113. tmp = PRESETS['rainbow'].color_text('preset')
  114. preset = literal_input(f'Which {tmp} do you want to use?', PRESETS.keys(), 'rainbow')
  115. # Create config
  116. c = Config(preset, color_system)
  117. # Save config
  118. save = literal_input(f'Save config?', ['y', 'n'], 'y')
  119. if save == 'y':
  120. c.save()
  121. return c
  122. def run():
  123. # Create CLI
  124. hyfetch = color('&b&lhy&f&lfetch&r')
  125. parser = argparse.ArgumentParser(description=color(f'{hyfetch} - neofetch with flags <3'))
  126. parser.add_argument('-c', '--config', action='store_true', help=color(f'Configure {hyfetch}'))
  127. parser.add_argument('-p', '--preset', help=f'Use preset', choices=PRESETS.keys())
  128. parser.add_argument('-m', '--mode', help=f'Color mode', choices=['8bit', 'rgb'])
  129. parser.add_argument('--c-scale', dest='scale', help=f'Lighten colors by a multiplier', type=float)
  130. parser.add_argument('--c-set-l', dest='light', help=f'Set lightness value of the colors', type=float)
  131. parser.add_argument('-V', '--version', dest='version', action='store_true', help=f'Check version')
  132. parser.add_argument('--debug', action='store_true', help=color(f'Debug mode'))
  133. parser.add_argument('--test-distro', help=color(f'Test print a specific distro\'s ascii art'))
  134. args = parser.parse_args()
  135. if args.version:
  136. print(f'Version is {VERSION}')
  137. return
  138. # Load config
  139. config = check_config()
  140. # Reset config
  141. if args.config:
  142. config = create_config()
  143. # Param overwrite config
  144. if args.preset:
  145. config.preset = args.preset
  146. if args.mode:
  147. config.mode = args.mode
  148. preset = PRESETS.get(config.preset)
  149. # Lighten
  150. if args.scale:
  151. preset = ColorProfile([c.lighten(args.scale) for c in preset.colors])
  152. if args.light:
  153. preset = ColorProfile([c.set_light(args.light) for c in preset.colors])
  154. # Test distro ascii art
  155. if args.test_distro:
  156. asc = get_custom_distro_ascii(args.test_distro)
  157. print(asc)
  158. print(replace_colors(asc, preset, config.mode)[0])
  159. exit(0)
  160. # Run
  161. run_neofetch(preset, config.mode)