507 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			507 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import os
 | |
| from collections import Callable
 | |
| from typing import Union, Optional
 | |
| 
 | |
| import pyfiglet
 | |
| from pynput import keyboard
 | |
| from pynput.keyboard import Key
 | |
| from tabulate import tabulate
 | |
| from termcolor import colored
 | |
| 
 | |
| from cpl.console.background_color_enum import BackgroundColorEnum
 | |
| from cpl.console.console_call import ConsoleCall
 | |
| from cpl.console.foreground_color_enum import ForegroundColorEnum
 | |
| from cpl.console.spinner_thread import SpinnerThread
 | |
| 
 | |
| 
 | |
| class Console:
 | |
|     """
 | |
|     Useful functions for handling with input and output
 | |
|     """
 | |
|     _is_first_write = True
 | |
| 
 | |
|     _background_color: BackgroundColorEnum = BackgroundColorEnum.default
 | |
|     _foreground_color: ForegroundColorEnum = ForegroundColorEnum.default
 | |
|     _x: Optional[int] = None
 | |
|     _y: Optional[int] = None
 | |
|     _disabled: bool = False
 | |
| 
 | |
|     _hold_back = False
 | |
|     _hold_back_calls: list[ConsoleCall] = []
 | |
| 
 | |
|     _select_menu_items: list[str] = []
 | |
|     _is_first_select_menu_output = True
 | |
|     _selected_menu_item_index: int = 0
 | |
|     _selected_menu_item_char: str = ''
 | |
|     _selected_menu_option_foreground_color: ForegroundColorEnum = ForegroundColorEnum.default
 | |
|     _selected_menu_option_background_color: BackgroundColorEnum = BackgroundColorEnum.default
 | |
|     _selected_menu_cursor_foreground_color: ForegroundColorEnum = ForegroundColorEnum.default
 | |
|     _selected_menu_cursor_background_color: BackgroundColorEnum = BackgroundColorEnum.default
 | |
| 
 | |
|     """
 | |
|         Properties
 | |
|     """
 | |
| 
 | |
|     @classmethod
 | |
|     @property
 | |
|     def background_color(cls) -> str:
 | |
|         return str(cls._background_color.value)
 | |
| 
 | |
|     @classmethod
 | |
|     @property
 | |
|     def foreground_color(cls) -> str:
 | |
|         return str(cls._foreground_color.value)
 | |
| 
 | |
|     """
 | |
|         Settings
 | |
|     """
 | |
| 
 | |
|     @classmethod
 | |
|     def set_hold_back(cls, value: bool):
 | |
|         cls._hold_back = value
 | |
| 
 | |
|     @classmethod
 | |
|     def set_background_color(cls, color: Union[BackgroundColorEnum, str]):
 | |
|         """
 | |
|         Sets the background color
 | |
|         :param color:
 | |
|         :return:
 | |
|         """
 | |
|         if type(color) is str:
 | |
|             cls._background_color = BackgroundColorEnum[color]
 | |
|         else:
 | |
|             cls._background_color = color
 | |
| 
 | |
|     @classmethod
 | |
|     def set_foreground_color(cls, color: Union[ForegroundColorEnum, str]):
 | |
|         """
 | |
|         Sets the foreground color
 | |
|         :param color:
 | |
|         :return:
 | |
|         """
 | |
|         if type(color) is str:
 | |
|             cls._foreground_color = ForegroundColorEnum[color]
 | |
|         else:
 | |
|             cls._foreground_color = color
 | |
| 
 | |
|     @classmethod
 | |
|     def reset_cursor_position(cls):
 | |
|         """
 | |
|         Resets cursor position
 | |
|         :return:
 | |
|         """
 | |
|         cls._x = None
 | |
|         cls._y = None
 | |
| 
 | |
|     @classmethod
 | |
|     def set_cursor_position(cls, x: int, y: int):
 | |
|         """
 | |
|         Sets cursor position
 | |
|         :param x:
 | |
|         :param y:
 | |
|         :return:
 | |
|         """
 | |
|         cls._x = x
 | |
|         cls._y = y
 | |
| 
 | |
|     """
 | |
|         Useful protected methods
 | |
|     """
 | |
| 
 | |
|     @classmethod
 | |
|     def _output(cls, string: str, x: int = None, y: int = None, end='\n'):
 | |
|         """
 | |
|         Prints given output with given format
 | |
|         :param string:
 | |
|         :param x:
 | |
|         :param y:
 | |
|         :param end:
 | |
|         :return:
 | |
|         """
 | |
|         if cls._is_first_write:
 | |
|             cls._is_first_write = False
 | |
| 
 | |
|         args = []
 | |
|         colored_args = []
 | |
|         if x is not None and y is not None:
 | |
|             args.append(f'\033[{y};{x}H')
 | |
|         elif cls._x is not None and cls._y is not None:
 | |
|             args.append(f'\033[{cls._y};{cls._x}H')
 | |
| 
 | |
|         colored_args.append(string)
 | |
|         if cls._foreground_color != ForegroundColorEnum.default and cls._background_color == BackgroundColorEnum.default:
 | |
|             colored_args.append(cls._foreground_color.value)
 | |
|         elif cls._foreground_color == ForegroundColorEnum.default and cls._background_color != BackgroundColorEnum.default:
 | |
|             colored_args.append(cls._background_color.value)
 | |
|         elif cls._foreground_color != ForegroundColorEnum.default and cls._background_color != BackgroundColorEnum.default:
 | |
|             colored_args.append(cls._foreground_color.value)
 | |
|             colored_args.append(cls._background_color.value)
 | |
| 
 | |
|         args.append(colored(*colored_args))
 | |
|         print(*args, end=end)
 | |
| 
 | |
|     @classmethod
 | |
|     def _show_select_menu(cls):
 | |
|         """
 | |
|         Shows the select menu
 | |
|         :return:
 | |
|         """
 | |
|         if not cls._is_first_select_menu_output:
 | |
|             for _ in range(0, len(cls._select_menu_items) + 1):
 | |
|                 print('\b', end="\r")
 | |
|         else:
 | |
|             cls._is_first_select_menu_output = False
 | |
| 
 | |
|         for i in range(0, len(cls._select_menu_items)):
 | |
|             Console.set_foreground_color(cls._selected_menu_cursor_foreground_color)
 | |
|             Console.set_background_color(cls._selected_menu_cursor_background_color)
 | |
|             Console.write_line(f'{cls._selected_menu_item_char if cls._selected_menu_item_index == i else " "} ')
 | |
|             Console.set_foreground_color(cls._selected_menu_option_foreground_color)
 | |
|             Console.set_background_color(cls._selected_menu_option_background_color)
 | |
|             Console.write(f'{cls._select_menu_items[i]}')
 | |
| 
 | |
|         Console.write_line()
 | |
| 
 | |
|     @classmethod
 | |
|     def _select_menu_key_press(cls, key: Key):
 | |
|         """
 | |
|         Event function when key press is detected
 | |
|         :param key:
 | |
|         :return:
 | |
|         """
 | |
|         if key == Key.down:
 | |
|             if cls._selected_menu_item_index == len(cls._select_menu_items) - 1:
 | |
|                 return
 | |
|             cls._selected_menu_item_index += 1
 | |
|             cls._show_select_menu()
 | |
| 
 | |
|         elif key == Key.up:
 | |
|             if cls._selected_menu_item_index == 0:
 | |
|                 return
 | |
|             cls._selected_menu_item_index -= 1
 | |
|             cls._show_select_menu()
 | |
| 
 | |
|         elif key == Key.enter:
 | |
|             return False
 | |
| 
 | |
|     """
 | |
|         Useful public methods
 | |
|     """
 | |
| 
 | |
|     @classmethod
 | |
|     def banner(cls, string: str):
 | |
|         """
 | |
|         Prints the string as a banner
 | |
|         :param string:
 | |
|         :return:
 | |
|         """
 | |
|         if cls._disabled:
 | |
|             return
 | |
| 
 | |
|         if cls._hold_back:
 | |
|             cls._hold_back_calls.append(ConsoleCall(cls.banner, string))
 | |
|             return
 | |
| 
 | |
|         ascii_banner = pyfiglet.figlet_format(string)
 | |
|         cls.write_line(ascii_banner)
 | |
| 
 | |
|     @classmethod
 | |
|     def color_reset(cls):
 | |
|         """
 | |
|         Resets color
 | |
|         :return:
 | |
|         """
 | |
|         cls._background_color = BackgroundColorEnum.default
 | |
|         cls._foreground_color = ForegroundColorEnum.default
 | |
| 
 | |
|     @classmethod
 | |
|     def clear(cls):
 | |
|         """
 | |
|         Clears the console
 | |
|         :return:
 | |
|         """
 | |
|         if cls._hold_back:
 | |
|             cls._hold_back_calls.append(ConsoleCall(cls.clear))
 | |
|             return
 | |
| 
 | |
|         os.system('cls' if os.name == 'nt' else 'clear')
 | |
| 
 | |
|     @classmethod
 | |
|     def close(cls):
 | |
|         """
 | |
|         Close the application
 | |
|         :return:
 | |
|         """
 | |
|         if cls._disabled:
 | |
|             return
 | |
| 
 | |
|         if cls._hold_back:
 | |
|             cls._hold_back_calls.append(ConsoleCall(cls.close))
 | |
|             return
 | |
| 
 | |
|         Console.color_reset()
 | |
|         Console.write('\n\n\nPress any key to continue...')
 | |
|         Console.read()
 | |
|         exit()
 | |
| 
 | |
|     @classmethod
 | |
|     def disable(cls):
 | |
|         """
 | |
|         Disable console interaction
 | |
|         :return:
 | |
|         """
 | |
|         cls._disabled = True
 | |
| 
 | |
|     @classmethod
 | |
|     def error(cls, string: str, tb: str = None):
 | |
|         """
 | |
|         Prints an error with traceback
 | |
|         :param string:
 | |
|         :param tb:
 | |
|         :return:
 | |
|         """
 | |
|         if cls._disabled:
 | |
|             return
 | |
| 
 | |
|         if cls._hold_back:
 | |
|             cls._hold_back_calls.append(ConsoleCall(cls.error, string, tb))
 | |
|             return
 | |
| 
 | |
|         cls.set_foreground_color('red')
 | |
|         if tb is not None:
 | |
|             cls.write_line(f'{string} -> {tb}')
 | |
|         else:
 | |
|             cls.write_line(string)
 | |
|         cls.set_foreground_color('default')
 | |
| 
 | |
|     @classmethod
 | |
|     def enable(cls):
 | |
|         """
 | |
|         Enable console interaction
 | |
|         :return:
 | |
|         """
 | |
|         cls._disabled = False
 | |
| 
 | |
|     @classmethod
 | |
|     def read(cls, output: str = None) -> str:
 | |
|         """
 | |
|         Read in line
 | |
|         :param output:
 | |
|         :return:
 | |
|         """
 | |
|         if output is not None and not cls._hold_back:
 | |
|             cls.write_line(output)
 | |
| 
 | |
|         return input()
 | |
| 
 | |
|     @classmethod
 | |
|     def read_line(cls, output: str = None) -> str:
 | |
|         """
 | |
|         Reads in next line
 | |
|         :param output:
 | |
|         :return:
 | |
|         """
 | |
|         if cls._disabled and not cls._hold_back:
 | |
|             return ''
 | |
| 
 | |
|         if output is not None:
 | |
|             cls.write_line(output)
 | |
| 
 | |
|         cls._output('\n', end='')
 | |
| 
 | |
|         return input()
 | |
| 
 | |
|     @classmethod
 | |
|     def table(cls, header: list[str], values: list[list[str]]):
 | |
|         """
 | |
|         Prints a table with header and values
 | |
|         :param header:
 | |
|         :param values:
 | |
|         :return:
 | |
|         """
 | |
|         if cls._disabled:
 | |
|             return
 | |
| 
 | |
|         if cls._hold_back:
 | |
|             cls._hold_back_calls.append(ConsoleCall(cls.table, header, values))
 | |
|             return
 | |
| 
 | |
|         table = tabulate(values, headers=header)
 | |
| 
 | |
|         Console.write_line(table)
 | |
|         Console.write('\n')
 | |
| 
 | |
|     @classmethod
 | |
|     def select(cls, char: str, message: str, options: list[str],
 | |
|                header_foreground_color: Union[str, ForegroundColorEnum] = ForegroundColorEnum.default,
 | |
|                header_background_color: Union[str, BackgroundColorEnum] = BackgroundColorEnum.default,
 | |
|                option_foreground_color: Union[str, ForegroundColorEnum] = ForegroundColorEnum.default,
 | |
|                option_background_color: Union[str, BackgroundColorEnum] = BackgroundColorEnum.default,
 | |
|                cursor_foreground_color: Union[str, ForegroundColorEnum] = ForegroundColorEnum.default,
 | |
|                cursor_background_color: Union[str, BackgroundColorEnum] = BackgroundColorEnum.default
 | |
|                ) -> str:
 | |
|         """
 | |
|         Prints select menu
 | |
|         :param char:
 | |
|         :param message:
 | |
|         :param options:
 | |
|         :param header_foreground_color:
 | |
|         :param header_background_color:
 | |
|         :param option_foreground_color:
 | |
|         :param option_background_color:
 | |
|         :param cursor_foreground_color:
 | |
|         :param cursor_background_color:
 | |
|         :return: Selected option as str
 | |
|         """
 | |
|         cls._selected_menu_item_char = char
 | |
|         cls.options = options
 | |
|         cls._select_menu_items = cls.options
 | |
| 
 | |
|         if option_foreground_color is not None:
 | |
|             cls._selected_menu_option_foreground_color = option_foreground_color
 | |
|         if option_background_color is not None:
 | |
|             cls._selected_menu_option_background_color = option_background_color
 | |
| 
 | |
|         if cursor_foreground_color is not None:
 | |
|             cls._selected_menu_cursor_foreground_color = cursor_foreground_color
 | |
|         if cursor_background_color is not None:
 | |
|             cls._selected_menu_cursor_background_color = cursor_background_color
 | |
| 
 | |
|         Console.set_foreground_color(header_foreground_color)
 | |
|         Console.set_background_color(header_background_color)
 | |
|         Console.write_line(message)
 | |
|         cls._show_select_menu()
 | |
| 
 | |
|         with keyboard.Listener(
 | |
|                 on_press=cls._select_menu_key_press, suppress=True) as listener:
 | |
|             listener.join()
 | |
| 
 | |
|         Console.color_reset()
 | |
|         return cls._select_menu_items[cls._selected_menu_item_index]
 | |
| 
 | |
|     @classmethod
 | |
|     def spinner(cls, message: str, call: Callable, *args, text_foreground_color: Union[str, ForegroundColorEnum] = None,
 | |
|                 spinner_foreground_color: Union[str, ForegroundColorEnum] = None,
 | |
|                 text_background_color: Union[str, BackgroundColorEnum] = None,
 | |
|                 spinner_background_color: Union[str, BackgroundColorEnum] = None, **kwargs) -> any:
 | |
|         """
 | |
|         Shows spinner and calls given function
 | |
|         When function has ended the spinner stops
 | |
|         :param message:
 | |
|         :param call:
 | |
|         :param args:
 | |
|         :param text_foreground_color:
 | |
|         :param spinner_foreground_color:
 | |
|         :param text_background_color:
 | |
|         :param spinner_background_color:
 | |
|         :param kwargs:
 | |
|         :return: Return value of call
 | |
|         """
 | |
|         if cls._hold_back:
 | |
|             cls._hold_back_calls.append(ConsoleCall(cls.spinner, message, call, *args))
 | |
|             return
 | |
| 
 | |
|         if text_foreground_color is not None:
 | |
|             cls.set_foreground_color(text_foreground_color)
 | |
| 
 | |
|         if text_background_color is not None:
 | |
|             cls.set_background_color(text_background_color)
 | |
| 
 | |
|         if type(spinner_foreground_color) is str:
 | |
|             spinner_foreground_color = ForegroundColorEnum[spinner_foreground_color]
 | |
| 
 | |
|         if type(spinner_background_color) is str:
 | |
|             spinner_background_color = BackgroundColorEnum[spinner_background_color]
 | |
| 
 | |
|         cls.write_line(message)
 | |
|         cls.set_hold_back(True)
 | |
|         spinner = SpinnerThread(len(message), spinner_foreground_color, spinner_background_color)
 | |
|         spinner.start()
 | |
|         return_value = call(*args, **kwargs)
 | |
|         spinner.stop_spinning()
 | |
|         cls.set_hold_back(False)
 | |
| 
 | |
|         cls.set_foreground_color(ForegroundColorEnum.default)
 | |
|         cls.set_background_color(BackgroundColorEnum.default)
 | |
| 
 | |
|         for call in cls._hold_back_calls:
 | |
|             call.function(*call.args)
 | |
| 
 | |
|         return return_value
 | |
| 
 | |
|     @classmethod
 | |
|     def write(cls, *args):
 | |
|         """
 | |
|         Prints in active line
 | |
|         :param args:
 | |
|         :return:
 | |
|         """
 | |
|         if cls._disabled:
 | |
|             return
 | |
| 
 | |
|         if cls._hold_back:
 | |
|             cls._hold_back_calls.append(ConsoleCall(cls.write, args))
 | |
|             return
 | |
| 
 | |
|         string = ' '.join(map(str, args))
 | |
|         cls._output(string, end='')
 | |
| 
 | |
|     @classmethod
 | |
|     def write_at(cls, x: int, y: int, *args):
 | |
|         """
 | |
|         Prints at given position
 | |
|         :param x:
 | |
|         :param y:
 | |
|         :param args:
 | |
|         :return:
 | |
|         """
 | |
|         if cls._disabled:
 | |
|             return
 | |
| 
 | |
|         if cls._hold_back:
 | |
|             cls._hold_back_calls.append(ConsoleCall(cls.write_at, x, y, args))
 | |
|             return
 | |
| 
 | |
|         string = ' '.join(map(str, args))
 | |
|         cls._output(string, x, y, end='')
 | |
| 
 | |
|     @classmethod
 | |
|     def write_line(cls, *args):
 | |
|         """
 | |
|         Prints to new line
 | |
|         :param args:
 | |
|         :return:
 | |
|         """
 | |
|         if cls._disabled:
 | |
|             return
 | |
| 
 | |
|         if cls._hold_back:
 | |
|             cls._hold_back_calls.append(ConsoleCall(cls.write_line, args))
 | |
|             return
 | |
| 
 | |
|         string = ' '.join(map(str, args))
 | |
|         if not cls._is_first_write:
 | |
|             cls._output('')
 | |
|         cls._output(string, end='')
 | |
| 
 | |
|     @classmethod
 | |
|     def write_line_at(cls, x: int, y: int, *args):
 | |
|         """
 | |
|         Prints new line at given position
 | |
|         :param x:
 | |
|         :param y:
 | |
|         :param args:
 | |
|         :return:
 | |
|         """
 | |
|         if cls._disabled:
 | |
|             return
 | |
| 
 | |
|         if cls._hold_back:
 | |
|             cls._hold_back_calls.append(ConsoleCall(cls.write_line_at, x, y, args))
 | |
|             return
 | |
| 
 | |
|         string = ' '.join(map(str, args))
 | |
|         if not cls._is_first_write:
 | |
|             cls._output('', end='')
 | |
|         cls._output(string, x, y, end='')
 |