main.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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. light_dark = literal_input(f'1. Is your terminal in &gf(#85e7e9)light mode&r or &gf(#c471ed)dark mode&r?',
  93. ['light', 'dark'], 'dark')
  94. ##############################
  95. # 3. Choose preset
  96. clear_screen(title)
  97. print('2. Let\'s choose a flag! Available flags:\n')
  98. # Create flags = [[lines]]
  99. flags = []
  100. spacing = max(max(len(k) for k in PRESETS.keys()), 20)
  101. for name, preset in PRESETS.items():
  102. flag = preset.color_text(' ' * spacing, foreground=False)
  103. flags.append([name.center(spacing), flag, flag, flag])
  104. # Calculate flags per row
  105. flags_per_row = term_len // (spacing + 2)
  106. while flags:
  107. current = flags[:flags_per_row]
  108. flags = flags[flags_per_row:]
  109. # Print by row
  110. for line in range(len(current[0])):
  111. printc(' '.join(flag[line] for flag in current))
  112. print()
  113. print()
  114. tmp = PRESETS['rainbow'].color_text('preset')
  115. preset = literal_input(f'Which {tmp} do you want to use?', PRESETS.keys(), 'rainbow')
  116. # Create config
  117. c = Config(preset, color_system, light_dark)
  118. # Save config
  119. save = literal_input(f'Save config?', ['y', 'n'], 'y')
  120. if save == 'y':
  121. c.save()
  122. return c
  123. def run():
  124. # Create CLI
  125. hyfetch = color('&b&lhy&f&lfetch&r')
  126. parser = argparse.ArgumentParser(description=color(f'{hyfetch} - neofetch with flags <3'))
  127. parser.add_argument('-c', '--config', action='store_true', help=color(f'Configure {hyfetch}'))
  128. parser.add_argument('-p', '--preset', help=f'Use preset', choices=PRESETS.keys())
  129. parser.add_argument('-m', '--mode', help=f'Color mode', choices=['8bit', 'rgb'])
  130. parser.add_argument('--c-scale', dest='scale', help=f'Lighten colors by a multiplier', type=float)
  131. parser.add_argument('--c-set-l', dest='light', help=f'Set lightness value of the colors', type=float)
  132. parser.add_argument('-V', '--version', dest='version', action='store_true', help=f'Check version')
  133. parser.add_argument('--debug', action='store_true', help=color(f'Debug mode'))
  134. parser.add_argument('--test-distro', help=color(f'Test print a specific distro\'s ascii art'))
  135. args = parser.parse_args()
  136. if args.version:
  137. print(f'Version is {VERSION}')
  138. return
  139. # Load config
  140. config = check_config()
  141. # Reset config
  142. if args.config:
  143. config = create_config()
  144. # Param overwrite config
  145. if args.preset:
  146. config.preset = args.preset
  147. if args.mode:
  148. config.mode = args.mode
  149. # Override global color mode
  150. constants.COLOR_MODE = config.mode
  151. # Get preset
  152. preset = PRESETS.get(config.preset)
  153. # Lighten
  154. if args.scale:
  155. preset = ColorProfile([c.lighten(args.scale) for c in preset.colors])
  156. if args.light:
  157. preset = ColorProfile([c.set_light(args.light) for c in preset.colors])
  158. # Test distro ascii art
  159. if args.test_distro:
  160. asc = get_custom_distro_ascii(args.test_distro)
  161. print(asc)
  162. print(replace_colors(asc, preset, config.mode)[0])
  163. return
  164. # Run
  165. run_neofetch(preset, config.mode)