main.py 6.3 KB

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