main.py 6.6 KB

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